diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4dfa71e085..b26b008078 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,41 +1,11 @@
{
"version": "0.2.0",
"configurations": [
- {
- "name": ".NET Core Launch (web)",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- "program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll",
- "args": [],
- "cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample",
- "stopAtEntry": false,
- "launchBrowser": {
- "enabled": false,
- "args": "${auto-detect-url}",
- "windows": {
- "command": "cmd.exe",
- "args": "/C start ${auto-detect-url}"
- },
- "osx": {
- "command": "open"
- },
- "linux": {
- "command": "xdg-open"
- }
- },
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "sourceFileMap": {
- "/Views": "${workspaceRoot}/Views"
- }
- },
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
- "processId": "${command.pickProcess}"
+ "processId": "${command:pickProcess}"
}
]
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 5fe174ae4d..e43b83d6f1 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
- [Sorting](#sorting)
- [Meta](#meta)
- [Client Generated Ids](#client-generated-ids)
+ - [Custom Errors](#custom-errors)
+ - [Sparse Fieldsets](#sparse-fieldsets)
- [Tests](#tests)
## Comprehensive Demo
@@ -44,14 +46,14 @@ Install-Package JsonApiDotnetCore
- project.json
```json
-"JsonApiDotNetCore": "1.1.0"
+"JsonApiDotNetCore": "1.2.0"
```
- *.csproj
```xml
-
+
```
@@ -326,6 +328,10 @@ Resources can be sorted by an attribute:
### Meta
+Meta objects can be assigned in two ways:
+ - Resource meta
+ - Request Meta
+
Resource meta can be defined by implementing `IHasMeta` on the model class:
```csharp
@@ -343,6 +349,9 @@ public class Person : Identifiable, IHasMeta
}
```
+Request Meta can be added by injecting a service that implements `IRequestMeta`.
+In the event of a key collision, the Request Meta will take precendence.
+
### Client Generated Ids
By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
@@ -357,6 +366,55 @@ services.AddJsonApi(opt =>
});
```
+### Custom Errors
+
+By default, errors will only contain the properties defined by the internal [Error](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/Error.cs) class. However, you can create your own by inheriting from `Error` and either throwing it in a `JsonApiException` or returning the error from your controller.
+
+```csharp
+// custom error definition
+public class CustomError : Error {
+ public CustomError(string status, string title, string detail, string myProp)
+ : base(status, title, detail)
+ {
+ MyCustomProperty = myProp;
+ }
+ public string MyCustomProperty { get; set; }
+}
+
+// throwing a custom error
+public void MyMethod() {
+ var error = new CustomError("507", "title", "detail", "custom");
+ throw new JsonApiException(error);
+}
+
+// returning from controller
+[HttpPost]
+public override async Task PostAsync([FromBody] MyEntity entity)
+{
+ if(_db.IsFull)
+ return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));
+
+ // ...
+}
+```
+
+### Sparse Fieldsets
+
+We currently support top-level field selection.
+What this means is you can restrict which fields are returned by a query using the `fields` query parameter, but this does not yet apply to included relationships.
+
+- Currently valid:
+```http
+GET /articles?fields[articles]=title,body HTTP/1.1
+Accept: application/vnd.api+json
+```
+
+- Not yet supported:
+```http
+GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
+Accept: application/vnd.api+json
+```
+
## Tests
I am using DotNetCoreDocs to generate sample requests and documentation.
diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
index 2df9eaca4f..92c785c5d0 100644
--- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
@@ -8,10 +8,11 @@
namespace JsonApiDotNetCore.Builders
{
- public class DocumentBuilder
+ public class DocumentBuilder : IDocumentBuilder
{
private IJsonApiContext _jsonApiContext;
private IContextGraph _contextGraph;
+ private readonly IRequestMeta _requestMeta;
public DocumentBuilder(IJsonApiContext jsonApiContext)
{
@@ -19,18 +20,25 @@ public DocumentBuilder(IJsonApiContext jsonApiContext)
_contextGraph = jsonApiContext.ContextGraph;
}
+ public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
+ {
+ _jsonApiContext = jsonApiContext;
+ _contextGraph = jsonApiContext.ContextGraph;
+ _requestMeta = requestMeta;
+ }
+
public Document Build(IIdentifiable entity)
{
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());
var document = new Document
{
- Data = _getData(contextEntity, entity),
- Meta = _getMeta(entity),
+ Data = GetData(contextEntity, entity),
+ Meta = GetMeta(entity),
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
};
- document.Included = _appendIncludedObject(document.Included, contextEntity, entity);
+ document.Included = AppendIncludedObject(document.Included, contextEntity, entity);
return document;
}
@@ -46,39 +54,42 @@ public Documents Build(IEnumerable entities)
var documents = new Documents
{
Data = new List(),
- Meta = _getMeta(entities.FirstOrDefault()),
+ Meta = GetMeta(entities.FirstOrDefault()),
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
};
foreach (var entity in entities)
{
- documents.Data.Add(_getData(contextEntity, entity));
- documents.Included = _appendIncludedObject(documents.Included, contextEntity, entity);
+ documents.Data.Add(GetData(contextEntity, entity));
+ documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity);
}
return documents;
}
- private Dictionary _getMeta(IIdentifiable entity)
+ private Dictionary GetMeta(IIdentifiable entity)
{
if (entity == null) return null;
-
- var meta = new Dictionary();
- var metaEntity = entity as IHasMeta;
- if(metaEntity != null)
- meta = metaEntity.GetMeta(_jsonApiContext);
+ var builder = _jsonApiContext.MetaBuilder;
+
+ if(entity is IHasMeta metaEntity)
+ builder.Add(metaEntity.GetMeta(_jsonApiContext));
if(_jsonApiContext.Options.IncludeTotalRecordCount)
- meta["total-records"] = _jsonApiContext.PageManager.TotalRecords;
+ builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords);
+ if(_requestMeta != null)
+ builder.Add(_requestMeta.GetMeta());
+
+ var meta = builder.Build();
if(meta.Count > 0) return meta;
return null;
}
- private List _appendIncludedObject(List includedObject, ContextEntity contextEntity, IIdentifiable entity)
+ private List AppendIncludedObject(List includedObject, ContextEntity contextEntity, IIdentifiable entity)
{
- var includedEntities = _getIncludedEntities(contextEntity, entity);
+ var includedEntities = GetIncludedEntities(contextEntity, entity);
if (includedEntities.Count > 0)
{
if (includedObject == null)
@@ -89,7 +100,7 @@ private List _appendIncludedObject(List includedObje
return includedObject;
}
- private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
+ private DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
{
var data = new DocumentData
{
@@ -104,16 +115,24 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
contextEntity.Attributes.ForEach(attr =>
{
- data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
+ if(ShouldIncludeAttribute(attr))
+ data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
});
if (contextEntity.Relationships.Count > 0)
- _addRelationships(data, contextEntity, entity);
+ AddRelationships(data, contextEntity, entity);
return data;
}
- private void _addRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
+ private bool ShouldIncludeAttribute(AttrAttribute attr)
+ {
+ return (_jsonApiContext.QuerySet == null
+ || _jsonApiContext.QuerySet.Fields.Count == 0
+ || _jsonApiContext.QuerySet.Fields.Contains(attr.InternalAttributeName));
+ }
+
+ private void AddRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
{
data.Relationships = new Dictionary();
var linkBuilder = new LinkBuilder(_jsonApiContext);
@@ -129,7 +148,7 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
}
};
- if (_relationshipIsIncluded(r.InternalRelationshipName))
+ if (RelationshipIsIncluded(r.InternalRelationshipName))
{
var navigationEntity = _jsonApiContext.ContextGraph
.GetRelationship(entity, r.InternalRelationshipName);
@@ -137,46 +156,49 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
if(navigationEntity == null)
relationshipData.SingleData = null;
else if (navigationEntity is IEnumerable)
- relationshipData.ManyData = _getRelationships((IEnumerable