diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index a32ac146aa..d22ebc6887 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -621,9 +621,12 @@ $left$ = $right$;
TrueTrueTrue
+ TrueTrueTrueTrueTrueTrue
+ True
+ True
diff --git a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
index 7c42e26685..3d9cefc600 100644
--- a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
+++ b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
@@ -36,8 +36,10 @@ public JsonApiSerializerBenchmarks()
var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, new ResourceObjectBuilderSettings());
- _jsonApiSerializer = new ResponseSerializer(metaBuilder, linkBuilder,
- includeBuilder, fieldsToSerialize, resourceObjectBuilder, options);
+ IResourceDefinitionAccessor resourceDefinitionAccessor = new Mock().Object;
+
+ _jsonApiSerializer = new ResponseSerializer(metaBuilder, linkBuilder, includeBuilder, fieldsToSerialize, resourceObjectBuilder,
+ resourceDefinitionAccessor, options);
}
private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
diff --git a/docs/diagrams/resource-definition-create-resource.svg b/docs/diagrams/resource-definition-create-resource.svg
new file mode 100644
index 0000000000..0336a5fd72
--- /dev/null
+++ b/docs/diagrams/resource-definition-create-resource.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/resource-definition-delete-resource.svg b/docs/diagrams/resource-definition-delete-resource.svg
new file mode 100644
index 0000000000..9d09789dae
--- /dev/null
+++ b/docs/diagrams/resource-definition-delete-resource.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/resource-definition-set-relationship.svg b/docs/diagrams/resource-definition-set-relationship.svg
new file mode 100644
index 0000000000..e8e87053ab
--- /dev/null
+++ b/docs/diagrams/resource-definition-set-relationship.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/resource-definition-update-resource.svg b/docs/diagrams/resource-definition-update-resource.svg
new file mode 100644
index 0000000000..5bafe1a876
--- /dev/null
+++ b/docs/diagrams/resource-definition-update-resource.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-blue-lines-blue-blue-blue-blue.svg b/docs/diagrams/source/glyphs/doc-key-blue-lines-blue-blue-blue-blue.svg
new file mode 100644
index 0000000000..222c833544
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-blue-lines-blue-blue-blue-blue.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-blue-lines-orange-orange-blue-blue.svg b/docs/diagrams/source/glyphs/doc-key-blue-lines-orange-orange-blue-blue.svg
new file mode 100644
index 0000000000..c4eed0c97f
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-blue-lines-orange-orange-blue-blue.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-orange-blue.svg b/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-orange-blue.svg
new file mode 100644
index 0000000000..5194fdbf77
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-orange-blue.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-yellow-blue.svg b/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-yellow-blue.svg
new file mode 100644
index 0000000000..ac1ef400dd
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-blue-lines-yellow-orange-yellow-blue.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-green-lines-green-green-green-green.svg b/docs/diagrams/source/glyphs/doc-key-green-lines-green-green-green-green.svg
new file mode 100644
index 0000000000..dd2fa2512c
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-green-lines-green-green-green-green.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-green.svg b/docs/diagrams/source/glyphs/doc-key-green.svg
new file mode 100644
index 0000000000..73640a34e1
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-green.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-yellow-lines-yellow-X-yellow-X.svg b/docs/diagrams/source/glyphs/doc-key-yellow-lines-yellow-X-yellow-X.svg
new file mode 100644
index 0000000000..eb9fee9311
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-yellow-lines-yellow-X-yellow-X.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-key-yellow.svg b/docs/diagrams/source/glyphs/doc-key-yellow.svg
new file mode 100644
index 0000000000..49d3ddb9f0
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-key-yellow.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-nokey-blue-lines-orange-orange-X-X.svg b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-orange-orange-X-X.svg
new file mode 100644
index 0000000000..6b4e27765e
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-orange-orange-X-X.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-orange-X.svg b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-orange-X.svg
new file mode 100644
index 0000000000..6e8b794420
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-orange-X.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-yellow-X.svg b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-yellow-X.svg
new file mode 100644
index 0000000000..e9062e5ec5
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-nokey-blue-lines-yellow-orange-yellow-X.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-nokey-blue.svg b/docs/diagrams/source/glyphs/doc-nokey-blue.svg
new file mode 100644
index 0000000000..9e3ce76580
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-nokey-blue.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/doc-nokey-yellow-lines-yellow-X-yellow-X.svg b/docs/diagrams/source/glyphs/doc-nokey-yellow-lines-yellow-X-yellow-X.svg
new file mode 100644
index 0000000000..62889a851d
--- /dev/null
+++ b/docs/diagrams/source/glyphs/doc-nokey-yellow-lines-yellow-X-yellow-X.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/equals-sign.svg b/docs/diagrams/source/glyphs/equals-sign.svg
new file mode 100644
index 0000000000..31d635b71a
--- /dev/null
+++ b/docs/diagrams/source/glyphs/equals-sign.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/docs/diagrams/source/glyphs/event.svg b/docs/diagrams/source/glyphs/event.svg
new file mode 100644
index 0000000000..ae3c88ab43
--- /dev/null
+++ b/docs/diagrams/source/glyphs/event.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/multidoc-key-green-lines-green-green-green-green.svg b/docs/diagrams/source/glyphs/multidoc-key-green-lines-green-green-green-green.svg
new file mode 100644
index 0000000000..7ab3a41679
--- /dev/null
+++ b/docs/diagrams/source/glyphs/multidoc-key-green-lines-green-green-green-green.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/multidoc-key-red-lines-red-red-red-red.svg b/docs/diagrams/source/glyphs/multidoc-key-red-lines-red-red-red-red.svg
new file mode 100644
index 0000000000..a8c7a7b5e9
--- /dev/null
+++ b/docs/diagrams/source/glyphs/multidoc-key-red-lines-red-red-red-red.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/plus-sign.svg b/docs/diagrams/source/glyphs/plus-sign.svg
new file mode 100644
index 0000000000..ae709c4883
--- /dev/null
+++ b/docs/diagrams/source/glyphs/plus-sign.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/docs/diagrams/source/glyphs/plus.svg b/docs/diagrams/source/glyphs/plus.svg
new file mode 100644
index 0000000000..bfcce5dda3
--- /dev/null
+++ b/docs/diagrams/source/glyphs/plus.svg
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/diagrams/source/glyphs/service-bus.svg b/docs/diagrams/source/glyphs/service-bus.svg
new file mode 100644
index 0000000000..d67b837f25
--- /dev/null
+++ b/docs/diagrams/source/glyphs/service-bus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/diagrams/source/resource-definition-write-callbacks.drawio b/docs/diagrams/source/resource-definition-write-callbacks.drawio
new file mode 100644
index 0000000000..391f538389
--- /dev/null
+++ b/docs/diagrams/source/resource-definition-write-callbacks.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/docfx.json b/docs/docfx.json
index 3a67909683..6acc17ce7a 100644
--- a/docs/docfx.json
+++ b/docs/docfx.json
@@ -32,12 +32,11 @@
],
"resource": [
{
- "files": [ "images/**" ]
+ "files": [ "diagrams/*.svg" ]
}
],
"overwrite": [
{
- "files": [ "apidoc/**.md" ],
"exclude": [ "obj/**", "_site/**" ]
}
],
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index f46c9c108e..1f9de7a473 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -54,7 +54,8 @@ public class ArticlesController : BaseJsonApiController
}
[HttpGet("{id}")]
- public override async Task GetAsync(int id, CancellationToken cancellationToken)
+ public override async Task GetAsync(int id,
+ CancellationToken cancellationToken)
{
return await base.GetAsync(id, cancellationToken);
}
diff --git a/docs/usage/extensibility/custom-query-formats.md b/docs/usage/extensibility/custom-query-formats.md
deleted file mode 100644
index 2653682fe6..0000000000
--- a/docs/usage/extensibility/custom-query-formats.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Custom QueryString parameters
-
-For information on the built-in query string parameters, see the documentation for them.
-In order to add parsing of custom query string parameters, you can implement the `IQueryStringParameterReader` interface and inject it.
-
-```c#
-public class YourQueryStringParameterReader : IQueryStringParameterReader
-{
- // ...
-}
-```
-
-```c#
-services.AddScoped();
-services.AddScoped(sp => sp.GetService());
-```
diff --git a/docs/usage/extensibility/layer-overview.md b/docs/usage/extensibility/layer-overview.md
index 6f3b622e07..ac2f7aa435 100644
--- a/docs/usage/extensibility/layer-overview.md
+++ b/docs/usage/extensibility/layer-overview.md
@@ -1,33 +1,42 @@
# Layer Overview
-By default, data retrieval is distributed across three layers:
+By default, data access flows through the next three layers:
```
JsonApiController (required)
+ JsonApiResourceService : IResourceService
+ EntityFrameworkCoreRepository : IResourceRepository
+```
-+-- JsonApiResourceService : IResourceService
+Aside from these pluggable endpoint-oriented layers, we provide a resource-oriented extensibility point:
- +-- EntityFrameworkCoreRepository : IResourceRepository
+```
+JsonApiResourceDefinition : IResourceDefinition
```
-Customization can be done at any of these layers. However, it is recommended that you make your customizations at the service or the repository layer when possible, to keep the controllers free of unnecessary logic.
-You can use the following as a general rule of thumb for where to put business logic:
+Resource definition callbacks are invoked from the built-in resource service/repository layers, as well as from the serializer.
+For example, `IResourceDefinition.OnSerialize` is invoked whenever a resource is sent back to the client, irrespective of the endpoint.
+Likewise, `IResourceDefinition.OnSetToOneRelationshipAsync` is called from a patch-resource-with-relationships endpoint, as well as from patch-relationship.
-- `Controller`: simple validation logic that should result in the return of specific HTTP status codes, such as model validation
-- `IResourceService`: advanced business logic and replacement of data access mechanisms
-- `IResourceRepository`: custom logic that builds on the Entity Framework Core APIs
+Customization can be done at any of these extensibility points. It is usually sufficient to place your business logic in a resource definition, but depending
+on your needs, you may want to replace other parts by deriving from the built-in classes and override virtual methods or call their protected base methods.
-## Replacing Services
+## Replacing injected services
-**Note:** If you are using auto-discovery, resource services and repositories will be automatically registered for you.
+**Note:** If you are using auto-discovery, then resource services, repositories and resource definitions will be automatically registered for you.
-Replacing services and repositories is done on a per-resource basis and can be done through dependency injection in your Startup.cs file.
+Replacing built-in services is done on a per-resource basis and can be done through dependency injection in your Startup.cs file.
+For convenience, extension methods are provided to register layers on all their implemented interfaces.
```c#
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
- services.AddScoped();
- services.AddScoped>();
+ services.AddResourceService();
+ services.AddResourceRepository();
+ services.AddResourceDefinition();
+
+ services.AddScoped();
+ services.AddScoped();
}
```
diff --git a/docs/usage/extensibility/middleware.md b/docs/usage/extensibility/middleware.md
index 7cbadd1442..9be350250a 100644
--- a/docs/usage/extensibility/middleware.md
+++ b/docs/usage/extensibility/middleware.md
@@ -1,6 +1,9 @@
# Middleware
-It is possible to replace JsonApiDotNetCore middleware components by configuring the IoC container and by configuring `MvcOptions`.
+The default middleware validates incoming `Content-Type` and `Accept` HTTP headers.
+Based on routing configuration, it fills `IJsonApiRequest`, an injectable object that contains JSON:API-related information about the request being processed.
+
+It is possible to replace the built-in middleware components by configuring the IoC container and by configuring `MvcOptions`.
## Configuring the IoC container
diff --git a/docs/usage/extensibility/query-strings.md b/docs/usage/extensibility/query-strings.md
new file mode 100644
index 0000000000..1411717ffd
--- /dev/null
+++ b/docs/usage/extensibility/query-strings.md
@@ -0,0 +1,38 @@
+# Query string parameters
+
+The parsing of built-in query string parameters is done by types that implement `IQueryStringParameterReader`, which accepts the incoming parameter value.
+Those types also implement `IQueryConstraintProvider`, which they use to expose the parse result.
+
+The parse result consists of an expression in a scope. For example:
+
+
+```
+?filter[articles]=lessThan(price,'1.23')
+```
+
+has expression `lessThan(price,'1.23')` in scope `articles`.
+
+For more information on the various built-in query string parameters, see the documentation for them.
+
+## Custom query string parameters
+
+When using Entity Framework Core, resource definitions provide a concise syntax to bind a LINQ expression to a query string parameter.
+See [here](~/usage/extensibility/resource-definitions.md#custom-query-string-parameters) for details.
+
+## Custom query string parsing
+
+In order to add parsing of custom query string parameters, you can implement the `IQueryStringParameterReader` interface and register your reader.
+
+```c#
+public class YourQueryStringParameterReader : IQueryStringParameterReader
+{
+ // ...
+}
+```
+
+```c#
+services.AddScoped();
+services.AddScoped(sp => sp.GetService());
+```
+
+Now you can inject your custom reader in resource services, repositories, resource definitions etc.
diff --git a/docs/usage/resources/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md
similarity index 61%
rename from docs/usage/resources/resource-definitions.md
rename to docs/usage/extensibility/resource-definitions.md
index e355a30d9a..e278c04fa9 100644
--- a/docs/usage/resources/resource-definitions.md
+++ b/docs/usage/extensibility/resource-definitions.md
@@ -1,11 +1,19 @@
# Resource Definitions
-In order to improve the developer experience, we have introduced a type that makes
-common modifications to the default API behavior easier. Resource definitions were first introduced in v2.3.4.
+_since v2.3.4_
-Resource definitions are resolved from the dependency injection container, so you can inject dependencies in their constructor.
+Resource definitions provide a resource-oriented way to handle custom business logic (irrespective of the originating endpoint).
-## Customizing query clauses
+They are resolved from the dependency injection container, so you can inject dependencies in their constructor.
+
+**Note:** Prior to the introduction of auto-discovery (in v3), you needed to register the
+`ResourceDefinition` on the container yourself:
+
+```c#
+services.AddScoped, ProductResource>();
+```
+
+## Customizing queries
_since v4.0_
@@ -21,7 +29,7 @@ from Entity Framework Core `IQueryable` execution.
There are some cases where you want attributes (or relationships) conditionally excluded from your resource response.
For example, you may accept some sensitive data that should only be exposed to administrators after creation.
-Note: to exclude attributes unconditionally, use `[Attr(Capabilities = ~AttrCapabilities.AllowView)]`.
+Note: to exclude attributes unconditionally, use `[Attr(Capabilities = ~AttrCapabilities.AllowView)]` on a resource class property.
```c#
public class UserDefinition : JsonApiResourceDefinition
@@ -78,7 +86,7 @@ Content-Type: application/vnd.api+json
}
```
-## Default sort order
+### Default sort order
You can define the default sort order if no `sort` query string parameter is provided.
@@ -106,7 +114,7 @@ public class AccountDefinition : JsonApiResourceDefinition
}
```
-## Enforce page size
+### Enforce page size
You may want to enforce pagination on large database tables.
@@ -137,9 +145,9 @@ public class AccessLogDefinition : JsonApiResourceDefinition
}
```
-## Exclude soft-deleted resources
+### Change filters
-Soft-deletion sets `IsSoftDeleted` to `true` instead of actually deleting the record, so you may want to always filter them out.
+The next example filters out `Account` resources that are suspended.
```c#
public class AccountDefinition : JsonApiResourceDefinition
@@ -153,23 +161,25 @@ public class AccountDefinition : JsonApiResourceDefinition
{
var resourceContext = ResourceGraph.GetResourceContext();
- var isSoftDeletedAttribute =
+ var isSuspendedAttribute =
resourceContext.Attributes.Single(account =>
- account.Property.Name == nameof(Account.IsSoftDeleted));
+ account.Property.Name == nameof(Account.IsSuspended));
- var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
- new ResourceFieldChainExpression(isSoftDeletedAttribute),
+ var isNotSuspended = new ComparisonExpression(ComparisonOperator.Equals,
+ new ResourceFieldChainExpression(isSuspendedAttribute),
new LiteralConstantExpression(bool.FalseString));
return existingFilter == null
- ? (FilterExpression) isNotSoftDeleted
+ ? (FilterExpression) isNotSuspended
: new LogicalExpression(LogicalOperator.And,
- new[] { isNotSoftDeleted, existingFilter });
+ new[] { isNotSuspended, existingFilter });
}
}
```
-## Block including related resources
+### Block including related resources
+
+In the example below, an error is returned when a user tries to include the manager of an employee.
```c#
public class EmployeeDefinition : JsonApiResourceDefinition
@@ -196,14 +206,14 @@ public class EmployeeDefinition : JsonApiResourceDefinition
}
```
-## Custom query string parameters
+### Custom query string parameters
_since v3_
You can define additional query string parameters with the LINQ expression that should be used.
If the key is present in a query string, the supplied LINQ expression will be added to the database query.
-Note this directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of EF Core functionality.
+Note this directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of EF Core operators.
But it only works on primary resource endpoints (for example: /articles, but not on /blogs/1/articles or /blogs?include=articles).
```c#
@@ -238,11 +248,69 @@ public class ItemDefinition : JsonApiResourceDefinition
}
```
-## Using Resource Definitions prior to v3
-
-Prior to the introduction of auto-discovery, you needed to register the
-`ResourceDefinition` on the container yourself:
-
-```c#
-services.AddScoped, ItemResource>();
-```
+## Handling resource changes
+
+_since v4.2_
+
+Without going into too much details, the diagrams below demonstrate a few scenarios where custom code interacts with write operations.
+Click on a diagram to open it full-size in a new window.
+
+### Create resource
+
+
+
+
+
+1. User sends request to create a resource
+2. An empty resource instance is created
+3. Developer sets default values for attribute 1 and 2
+4. Attribute 1 and 3 from incoming request are copied into default instance
+5. Developer overwrites attribute 3
+6. Row is inserted in database
+7. Developer sends notification to service bus
+8. The new resource is fetched
+9. Resource is sent back to the user
+
+### Update Resource
+
+
+
+
+
+1. User sends request to update resource with ID 1
+2. Existing resource is fetched from database
+3. Developer changes attribute 1 and 2
+4. Attribute 1 and 3 from incoming request are copied into fetched instance
+5. Developer overwrites attribute 3
+6. Row is updated in database
+7. Developer sends notification to service bus
+8. The resource is fetched, along with requested includes
+9. Resource with includes is sent back to the user
+
+### Delete Resource
+
+
+
+
+
+1. User sends request to delete resource with ID 1
+2. Developer runs custom validation logic
+3. Row is deleted from database
+4. Developer sends notification to service bus
+5. Success status is sent back to user
+
+### Set Relationship
+
+
+
+
+
+1. User sends request to assign two resources (green) to relationship 'name' (black) on resource 1 (yellow)
+2. Existing resource (blue) with related resources (red) is fetched from database
+3. Developer changes attributes (not shown in diagram for brevity)
+4. Developer removes one resource from the to-be-assigned set (green)
+5. Existing resources in relationship (red) are replaced with resource from previous step (green)
+6. Developer overwrites attributes (not shown in diagram for brevity)
+7. Resource and relationship are updated in database
+8. Developer sends notification to service bus
+9. Success status is sent back to user
diff --git a/docs/usage/extensibility/services.md b/docs/usage/extensibility/services.md
index 7880189eee..7233756062 100644
--- a/docs/usage/extensibility/services.md
+++ b/docs/usage/extensibility/services.md
@@ -102,14 +102,20 @@ IResourceService
+-- ICreateService
| POST /
|
+ +-- IUpdateService
+ | PATCH /{id}
+ |
+-- IDeleteService
| DELETE /{id}
|
- +-- IUpdateService
- | PATCH /{id}
+ +-- IAddToRelationshipService
+ | POST /{id}/relationships/{relationship}
+ |
+ +-- ISetRelationshipService
+ | PATCH /{id}/relationships/{relationship}
|
- +-- IUpdateRelationshipService
- PATCH /{id}/relationships/{relationship}
+ +-- IRemoveFromRelationshipService
+ DELETE /{id}/relationships/{relationship}
```
In order to take advantage of these interfaces you first need to register the service for each implemented interface.
diff --git a/docs/usage/reading/filtering.md b/docs/usage/reading/filtering.md
index 90b03462d8..c6b65f9867 100644
--- a/docs/usage/reading/filtering.md
+++ b/docs/usage/reading/filtering.md
@@ -119,8 +119,8 @@ GET /articles?filter[caption]=tech&filter=expr:equals(caption,'cooking')) HTTP/1
There are multiple ways you can add custom filters:
-1. Implementing `IResourceDefinition.OnApplyFilter` (see [here](~/usage/resources/resource-definitions.md#exclude-soft-deleted-resources)) and inject `IRequestQueryStringAccessor`, which works at all depths, but filter operations are constrained to what `FilterExpression` provides
-2. Implementing `IResourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters` as [described previously](~/usage/resources/resource-definitions.md#custom-query-string-parameters), which enables the full range of `IQueryable` functionality, but only works on primary endpoints
+1. Implementing `IResourceDefinition.OnApplyFilter` (see [here](~/usage/extensibility/resource-definitions.md#change-filters)) and inject `IRequestQueryStringAccessor`, which works at all depths, but filter operations are constrained to what `FilterExpression` provides
+2. Implementing `IResourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters` as described [here](~/usage/extensibility/resource-definitions.md#custom-query-string-parameters), which enables the full range of `IQueryable` functionality, but only works on primary endpoints
3. Add an implementation of `IQueryConstraintProvider` to supply additional `FilterExpression`s, which are combined with existing filters using AND operator
4. Override `EntityFrameworkCoreRepository.ApplyQueryLayer` to adapt the `IQueryable` expression just before execution
5. Take a deep dive and plug into reader/parser/tokenizer/visitor/builder for adding additional general-purpose filter operators
diff --git a/docs/usage/reading/pagination.md b/docs/usage/reading/pagination.md
index 7ab92e2dec..77772288b3 100644
--- a/docs/usage/reading/pagination.md
+++ b/docs/usage/reading/pagination.md
@@ -22,4 +22,4 @@ GET /api/blogs/1/articles?include=revisions&page[size]=10,revisions:5&page[numbe
## Configuring Default Behavior
-You can configure the global default behavior as [described previously](~/usage/options.md#pagination).
+You can configure the global default behavior as described [here](~/usage/options.md#pagination).
diff --git a/docs/usage/reading/sorting.md b/docs/usage/reading/sorting.md
index a6fcb4f771..dfadc325fa 100644
--- a/docs/usage/reading/sorting.md
+++ b/docs/usage/reading/sorting.md
@@ -52,5 +52,5 @@ This sorts the list of blogs by their captions and included revisions by their p
## Default Sort
-See the topic on [Resource Definitions](~/usage/resources/resource-definitions.md)
+See the topic on [Resource Definitions](~/usage/extensibility/resource-definitions.md)
for overriding the default sort behavior.
diff --git a/docs/usage/reading/sparse-fieldset-selection.md b/docs/usage/reading/sparse-fieldset-selection.md
index b5510be945..7d90bf9d26 100644
--- a/docs/usage/reading/sparse-fieldset-selection.md
+++ b/docs/usage/reading/sparse-fieldset-selection.md
@@ -41,4 +41,4 @@ When omitted, you'll get the included resources returned, but without full resou
## Overriding
-As a developer, you can force to include and/or exclude specific fields as [described previously](~/usage/resources/resource-definitions.md).
+As a developer, you can force to include and/or exclude specific fields as described [here](~/usage/extensibility/resource-definitions.md).
diff --git a/docs/usage/resources/hooks.md b/docs/usage/resources/hooks.md
index 68295fa44a..e34c4922fd 100644
--- a/docs/usage/resources/hooks.md
+++ b/docs/usage/resources/hooks.md
@@ -1,7 +1,7 @@
# Resource Hooks
-This section covers the usage of **Resource Hooks**, which is a feature of`ResourceHooksDefinition`. See the [ResourceDefinition usage guide](resource-definitions.md) for a general explanation on how to set up a `JsonApiResourceDefinition`. For a quick start, jump right to the [Getting started: most minimal example](#getting-started-most-minimal-example) section.
+This section covers the usage of **Resource Hooks**, which is a feature of`ResourceHooksDefinition`. See the [ResourceDefinition usage guide](~/usage/extensibility/resource-definitions.md) for a general explanation on how to set up a `JsonApiResourceDefinition`. For a quick start, jump right to the [Getting started: most minimal example](#getting-started-most-minimal-example) section.
> Note: Resource Hooks are an experimental feature and are turned off by default. They are subject to change or be replaced in a future version.
diff --git a/docs/usage/resources/index.md b/docs/usage/resources/index.md
index ad164d82d4..29f510e543 100644
--- a/docs/usage/resources/index.md
+++ b/docs/usage/resources/index.md
@@ -29,7 +29,7 @@ you can override the virtual property.
public class Person : Identifiable
{
[Key]
- [Column("person_id")]
+ [Column("PersonID")]
public override int Id { get; set; }
}
```
diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md
index ac31adf14a..2d515872e9 100644
--- a/docs/usage/resources/relationships.md
+++ b/docs/usage/resources/relationships.md
@@ -1,7 +1,10 @@
# Relationships
-In order for navigation properties to be identified in the model,
-they should be labeled with the appropriate attribute (either `HasOne`, `HasMany` or `HasManyThrough`).
+A relationship is a named link between two resource types, including a direction.
+They are similar to [navigation properties in Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/modeling/relationships).
+
+Relationships come in three flavors: to-one, to-many and many-to-many.
+The left side of a relationship is where the relationship is declared, the right side is the resource type it points to.
## HasOne
@@ -15,6 +18,9 @@ public class TodoItem : Identifiable
}
```
+The left side of this relationship is of type `TodoItem` (public name: "todoItems") and the right side is of type `Person` (public name: "persons").
+
+
## HasMany
This exposes a to-many relationship.
@@ -27,11 +33,14 @@ public class Person : Identifiable
}
```
+The left side of this relationship is of type `Person` (public name: "persons") and the right side is of type `TodoItem` (public name: "todoItems").
+
+
## HasManyThrough
-Currently, Entity Framework Core [does not support](https://github.com/aspnet/EntityFrameworkCore/issues/1368) many-to-many relationships without a join entity.
+Earlier versions of Entity Framework Core (up to v5) [did not support](https://github.com/aspnet/EntityFrameworkCore/issues/1368) many-to-many relationships without a join entity.
For this reason, we have decided to fill this gap by allowing applications to declare a relationship as `HasManyThrough`.
-JsonApiDotNetCore will expose this relationship to the client the same way as any other `HasMany` attribute.
+JsonApiDotNetCore will expose this relationship to the client the same way as any other `HasMany` relationship.
However, under the covers it will use the join type and Entity Framework Core's APIs to get and set the relationship.
```c#
@@ -49,6 +58,9 @@ public class Article : Identifiable
}
```
+The left side of this relationship is of type `Article` (public name: "articles") and the right side is of type `Tag` (public name: "tags").
+
+
## Name
There are two ways the exposed relationship name is determined:
diff --git a/docs/usage/routing.md b/docs/usage/routing.md
index 10a556173b..9d4ebe5853 100644
--- a/docs/usage/routing.md
+++ b/docs/usage/routing.md
@@ -1,5 +1,11 @@
# Routing
+An endpoint URL provides access to a resource or a relationship. Resource endpoints are divided into:
+- Primary endpoints, for example: "/articles" and "/articles/1".
+- Secondary endpoints, for example: "/articles/1/author" and "/articles/1/comments".
+
+In the relationship endpoint "/articles/1/relationships/comments", "articles" is the left side of the relationship and "comments" the right side.
+
## Namespacing and Versioning URLs
You can add a namespace to all URLs by specifying it in ConfigureServices.
diff --git a/docs/usage/toc.md b/docs/usage/toc.md
index 82ba96a42f..bcb76c2038 100644
--- a/docs/usage/toc.md
+++ b/docs/usage/toc.md
@@ -1,7 +1,6 @@
# [Resources](resources/index.md)
## [Attributes](resources/attributes.md)
## [Relationships](resources/relationships.md)
-## [Resource Definitions](resources/resource-definitions.md)
# Reading data
## [Filtering](reading/filtering.md)
@@ -24,8 +23,9 @@
# Extensibility
## [Layer Overview](extensibility/layer-overview.md)
+## [Resource Definitions](extensibility/resource-definitions.md)
## [Controllers](extensibility/controllers.md)
## [Resource Services](extensibility/services.md)
## [Resource Repositories](extensibility/repositories.md)
## [Middleware](extensibility/middleware.md)
-## [Custom Query Formats](extensibility/custom-query-formats.md)
+## [Query Strings](extensibility/query-strings.md)
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
index 3192ddd85f..815b44bd96 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
@@ -5,6 +5,7 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Serialization.Building;
using JsonApiDotNetCore.Serialization.Client.Internal;
using JsonApiDotNetCore.Services;
@@ -101,6 +102,19 @@ public static IServiceCollection AddResourceRepository(this IServic
return services;
}
+ ///
+ /// Adds IoC container registrations for the various JsonApiDotNetCore resource definition interfaces, such as
+ /// and .
+ ///
+ public static IServiceCollection AddResourceDefinition(this IServiceCollection services)
+ {
+ ArgumentGuard.NotNull(services, nameof(services));
+
+ RegisterForConstructedType(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionInterfaces);
+
+ return services;
+ }
+
private static void RegisterForConstructedType(IServiceCollection services, Type implementationType, IEnumerable openGenericInterfaces)
{
bool seenCompatibleInterface = false;
diff --git a/src/JsonApiDotNetCore/Middleware/OperationKind.cs b/src/JsonApiDotNetCore/Middleware/OperationKind.cs
index 7cfbe0f892..e3a528f2b0 100644
--- a/src/JsonApiDotNetCore/Middleware/OperationKind.cs
+++ b/src/JsonApiDotNetCore/Middleware/OperationKind.cs
@@ -1,15 +1,39 @@
namespace JsonApiDotNetCore.Middleware
{
///
- /// Lists the functional operation kinds from an atomic:operations request.
+ /// Lists the functional operation kinds of a resource request or an atomic:operations request.
///
public enum OperationKind
{
+ ///
+ /// Create a new resource with attributes, relationships or both.
+ ///
CreateResource,
+
+ ///
+ /// Update the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. And only the values of sent
+ /// relationships are replaced.
+ ///
UpdateResource,
+
+ ///
+ /// Delete an existing resource.
+ ///
DeleteResource,
+
+ ///
+ /// Perform a complete replacement of a relationship on an existing resource.
+ ///
SetRelationship,
+
+ ///
+ /// Add resources to a to-many relationship.
+ ///
AddToRelationship,
+
+ ///
+ /// Remove resources from a to-many relationship.
+ ///
RemoveFromRelationship
}
}
diff --git a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs
index c3fa8428e4..35094f0e43 100644
--- a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs
+++ b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs
@@ -1,8 +1,10 @@
+using System;
using System.Collections.Generic;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
+using JsonApiDotNetCore.Services;
namespace JsonApiDotNetCore.Queries
{
@@ -57,5 +59,12 @@ QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, Resourc
/// Builds a query for a to-many relationship with a filter to match on its left and right resource IDs.
///
QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds);
+
+ ///
+ /// Provides access to the request-scoped instance. This method has been added solely to prevent introducing a
+ /// breaking change in the constructor and will be removed in the next major version.
+ ///
+ [Obsolete]
+ IResourceDefinitionAccessor GetResourceDefinitionAccessor();
}
}
diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
index ffaab66790..58fc650321 100644
--- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
+++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
@@ -414,6 +414,12 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T
};
}
+ ///
+ public IResourceDefinitionAccessor GetResourceDefinitionAccessor()
+ {
+ return _resourceDefinitionAccessor;
+ }
+
protected virtual IReadOnlyCollection GetIncludeElements(IReadOnlyCollection includeElements,
ResourceContext resourceContext)
{
diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs
index 36afb6dd7c..24f835c6df 100644
--- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs
+++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs
@@ -35,6 +35,7 @@ public class EntityFrameworkCoreRepository : IResourceRepository
private readonly IResourceFactory _resourceFactory;
private readonly IEnumerable _constraintProviders;
private readonly TraceLogWriter> _traceWriter;
+ private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
///
public virtual string TransactionId => _dbContext.Database.CurrentTransaction?.TransactionId.ToString();
@@ -55,6 +56,10 @@ public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextR
_constraintProviders = constraintProviders;
_dbContext = contextResolver.GetContext();
_traceWriter = new TraceLogWriter>(loggerFactory);
+
+#pragma warning disable 612 // Method is obsolete
+ _resourceDefinitionAccessor = resourceFactory.GetResourceDefinitionAccessor();
+#pragma warning restore 612
}
///
@@ -167,7 +172,11 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
foreach (RelationshipAttribute relationship in _targetedFields.Relationships)
{
object rightResources = relationship.GetValue(resourceFromRequest);
- await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResources, collector, cancellationToken);
+
+ object rightResourcesEdited = await VisitSetRelationshipAsync(resourceForDatabase, relationship, rightResources, OperationKind.CreateResource,
+ cancellationToken);
+
+ await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResourcesEdited, collector, cancellationToken);
}
foreach (AttrAttribute attribute in _targetedFields.Attributes)
@@ -175,10 +184,36 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
}
+ await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken);
+
DbSet dbSet = _dbContext.Set();
await dbSet.AddAsync(resourceForDatabase, cancellationToken);
await SaveChangesAsync(cancellationToken);
+
+ await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken);
+ }
+
+ private async Task