Skip to content

Commit d9ad919

Browse files
committed
Document advanced use cases
1 parent 3ab8ed9 commit d9ad919

28 files changed

+340
-17
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ To build the code from this repository locally, run:
256256
dotnet build
257257
```
258258

259-
Running tests locally requires access to a PostgreSQL database. If you have docker installed, this can started via:
259+
Running tests locally requires access to a PostgreSQL database. If you have docker installed, this can be started via:
260260

261261
```bash
262262
pwsh run-docker-postgres.ps1

docs/getting-started/faq.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ The [OpenAPI support in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/c
1111
and doesn't provide the level of extensibility needed for JsonApiDotNetCore.
1212

1313
#### What's available to implement a JSON:API client?
14-
It depends on the programming language used. There's an overwhelming list of client libraries at https://jsonapi.org/implementations/#client-libraries.
14+
To generate a typed client (specific to the resource types in your project), consider using our [OpenAPI](https://www.jsonapi.net/usage/openapi.html) NuGet package.
15+
16+
If you need a generic client, it depends on the programming language used. There's an overwhelming list of client libraries at https://jsonapi.org/implementations/#client-libraries.
1517

1618
The JSON object model inside JsonApiDotNetCore is tweaked for server-side handling (be tolerant at inputs and strict at outputs).
17-
While you technically *could* use our `JsonSerializer` converters from a .NET client application with some hacks, we don't recommend it.
19+
While you technically *could* use our `JsonSerializer` converters from a .NET client application with some hacks, we don't recommend doing so.
1820
You'll need to build the resource graph on the client and rely on internal implementation details that are subject to change in future versions.
1921

20-
In the long term, we'd like to solve this through OpenAPI, which enables the generation of a (statically typed) client library in various languages.
21-
2222
#### How can I debug my API project?
2323
Due to auto-generated controllers, you may find it hard to determine where to put your breakpoints.
2424
In Visual Studio, controllers are accessible below **Solution Explorer > Project > Dependencies > Analyzers > JsonApiDotNetCore.SourceGenerators**.

docs/request-examples/index.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
Runnable example projects can be found [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/src/Examples):
44

5-
- GettingStarted: A simple project with minimal configuration to have a runnable project in minutes.
5+
- GettingStarted: A simple project with minimal configuration to develop a runnable project in minutes.
66
- JsonApiDotNetCoreExample: Showcases commonly-used features, such as resource definitions, atomic operations, and OpenAPI.
77
- OpenApiNSwagClientExample: Uses [NSwag](https://github.com/RicoSuter/NSwag) to generate a typed OpenAPI client.
88
- OpenApiKiotaClientExample: Uses [Kiota](https://learn.microsoft.com/en-us/openapi/kiota/) to generate a typed OpenAPI client.
99
- MultiDbContextExample: Shows how to use multiple `DbContext` classes, for connecting to multiple databases.
10-
- DatabasePerTenantExample: Uses a different database per tenant. See [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy) for using multiple tenants in the same database.
11-
- NoEntityFrameworkExample: Uses a read-only in-memory repository instead of a real database.
10+
- DatabasePerTenantExample: Uses a different database per tenant. See [here](~/usage/advanced/multi-tenancy.md) for using multiple tenants in the same database.
11+
- NoEntityFrameworkExample: Uses a read-only in-memory repository, instead of a real database.
1212
- DapperExample: Uses [Dapper](https://github.com/DapperLib/Dapper) to execute SQL queries.
1313
- ReportsExample: Uses a resource service that returns aggregated data.
1414

15-
Additional use cases are provided as integration tests [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests).
15+
> [!NOTE]
16+
> The example projects only cover highly-requested features. More advanced use cases can be found [here](~/usage/advanced/index.md).
1617
1718
# Example requests
1819

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Alternate Routes
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes) shows how the default JSON:API routes can be changed.
4+
5+
The classes `TownsController` and `CiviliansController`:
6+
- Are decorated with `[DisableRoutingConvention]` to turn off the default JSON:API routing convention.
7+
- Are decorated with the ASP.NET `[Route]` attribute to specify at which route the controller is exposed.
8+
- Are augmented with non-standard JSON:API action methods, whose `[HttpGet]` attributes specify a custom route.

docs/usage/advanced/archiving.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Archiving
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving) demonstrates how to implement archived resources.
4+
5+
> [!TIP]
6+
> This scenario is comparable with [Soft Deletion](~/usage/advanced/soft-deletion.md).
7+
> The difference is that archived resources are accessible to JSON:API clients, whereas soft-deleted resources _never_ are.
8+
9+
- Archived resources can be fetched by ID, but don't show up in searches by default.
10+
- Resources can only be created in a non-archived state and then archived/unarchived using a PATCH resource request.
11+
- The archive date is stored in the database, but cannot be modified through JSON:API.
12+
- To delete a resource, it must be archived first.
13+
14+
This feature is implemented using a custom resource definition. It intercepts write operations and recursively scans incoming filters.

docs/usage/advanced/auth-scopes.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Authorization Scopes
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/Authorization/Scopes) shows how scope-based authorization can be used.
4+
5+
- For simplicity, this code assumes the granted scopes are passed in a plain-text HTTP header. A more realistic use case would be to obtain the scopes from an OAuth token.
6+
- The HTTP header lists which resource types can be read from and/or written to.
7+
- An [ASP.NET Action Filter](https://learn.microsoft.com/aspnet/core/mvc/controllers/filters) validates incoming JSON:API resource/relationship requests.
8+
- The incoming request path is validated against the permitted read/write permissions per resource type.
9+
- The resource types used in query string parameters are validated against the permitted set of resource types.
10+
- A customized operations controller verifies that all incoming operations are allowed.

docs/usage/advanced/blobs.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# BLOBs
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs) shows how Binary Large Objects (BLOBs) can be used.
4+
5+
- The `ImageContainer` resource type contains nullable and non-nullable `byte[]` properties.
6+
- BLOBs are queried and persisted using Entity Framework Core.
7+
- The BLOB data is returned as a base-64 encoded string in the JSON response.
8+
9+
Blobs are handled automatically; there's no need for custom code.

docs/usage/advanced/composite-keys.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Composite Keys
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys) shows how database tables with composite keys can be used.
4+
5+
- The `DbContext` configures `Car` to have a composite primary key consisting of the `RegionId` and `LicensePlate` columns.
6+
- The `Car.Id` property is overridden to provide a unique ID for JSON:API. It is marked with `[NotMapped]`, meaning no `Id` column exists in the database table.
7+
- The `Engine` and `Dealership` resource types define relationships that generate composite foreign keys in the database.
8+
- A custom resource repository is used to rewrite IDs from filter/sort query string parameters into `RegionId` and `LicensePlate` lookups.
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Content Negotiation
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation) demonstrates how content negotiation in JSON:API works.
4+
5+
Additionally, the code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/CustomExtensions) provides
6+
a custom "server-time" JSON:API extension that returns the local or UTC server time in top-level `meta`.
7+
- This extension can be used in the `Accept` and `Content-Type` HTTP headers.
8+
- In a request body, the optional `useLocalTime` property in top-level `meta` indicates whether to return the local or UTC time.
9+
10+
This feature is implemented using the following extensibility points:
11+
12+
- At startup, the "server-time" extension is added in `JsonApiOptions`, which permits clients to use it.
13+
- A custom `JsonApiContentNegotiator` chooses which extensions are active for an incoming request, taking the "server-time" extension into account.
14+
- A custom `IDocumentAdapter` captures the incoming request body, providing access to the `useLocalTime` property in `meta`.
15+
- A custom `IResponseMeta` adds the server time to the response, depending on the activated extensions in `IJsonApiRequest` and the captured request body.

docs/usage/advanced/eager-loading.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Eager Loading Related Resources
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading) uses the `[EagerLoad]` attribute to facilitate calculated properties that depend on related resources.
4+
The related resources are fetched from the database, but not returned to the client unless explicitly requested using the `include` query string parameter.
5+
6+
- The `Street` resource type uses `EagerLoad` on its `Buildings` to-many relationship because its `DoorTotalCount` calculated property depends on it.
7+
- The `Building` resource type uses `EagerLoad` on its `Windows` to-many relationship because its `WindowCount` calculated property depends on it.
8+
- The `Building` resource type uses `EagerLoad` on its `PrimaryDoor` to-one required relationship because its `PrimaryDoorColor` calculated property depends on it.
9+
- Because this is a required relationship, special handling occurs in `Building`, `BuildingRepository`, and `BuildingDefinition`.
10+
- The `Building` resource type uses `EagerLoad` on its `SecondaryDoor` to-one optional relationship because its `SecondaryDoorColor` calculated property depends on it.
11+
12+
As can be seen from the usages above, a chain of `EagerLoad` attributes can result in fetching a chain of related resources from the database.

docs/usage/advanced/error-handling.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Error Handling
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling) shows how to customize error handling.
4+
5+
A user-defined exception, `ConsumerArticleIsNoLongerAvailableException`, is thrown from a resource service to demonstrate handling it.
6+
Note that this exception can be thrown from anywhere during request execution; a resource service is just used here for simplicity.
7+
8+
To handle the user-defined exception, `AlternateExceptionHandler` inherits from `ExceptionHandler` to:
9+
- Customize the JSON:API error response by adding a `meta` entry when `ConsumerArticleIsNoLongerAvailableException` is thrown.
10+
- Indicate that `ConsumerArticleIsNoLongerAvailableException` must be logged at the Warning level.
11+
12+
Additionally, the `ThrowingArticle.Status` property throws an `InvalidOperationException`.
13+
This triggers the default error handling because `AlternateExceptionHandler` delegates to its base class.

docs/usage/advanced/hosting-iis.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Hosting in Internet Information Services (IIS)
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS) calls [UsePathBase](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.builder.usepathbaseextensions.usepathbase) to simulate hosting in IIS.
4+
For details on how `UsePathBase` works, see [Understanding PathBase in ASP.NET Core](https://andrewlock.net/understanding-pathbase-in-aspnetcore/).
5+
6+
- At startup, the line `app.UsePathBase("/iis-application-virtual-directory")` configures ASP.NET to use the base path.
7+
- `PaintingsController` uses a custom route to demonstrate that both features can be used together.

docs/usage/advanced/id-obfuscation.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# ID Obfuscation
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation) shows how to use obfuscated IDs.
4+
They are typically used to prevent clients from guessing primary key values.
5+
6+
All IDs sent by clients are transparently de-obfuscated into internal numeric values before accessing the database.
7+
Numeric IDs returned from the database are obfuscated before they are sent to the client.
8+
9+
> [!NOTE]
10+
> An alternate solution is to use GUIDs instead of numeric primary keys in the database.
11+
12+
ID obfuscation is achieved using the following extensibility points:
13+
14+
- For simplicity, `HexadecimalCodec` is used to obfuscate numeric IDs to a hexadecimal format. A more realistic use case would be to use a symmetric crypto algorithm.
15+
- `ObfuscatedIdentifiable` acts as the base class for resource types, handling the obfuscation and de-obfuscation of IDs.
16+
- `ObfuscatedIdentifiableController` acts as the base class for controllers. It inherits from `BaseJsonApiController`, changing the `id` parameter in action methods to type `string`.

docs/usage/advanced/index.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Advanced JSON:API features
2+
3+
This topic goes beyond the basics of what's possible with JsonApiDotNetCore.
4+
5+
Advanced use cases are provided in the form of integration tests [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests).
6+
This ensures they don't break during development of the framework.
7+
8+
Each directory typically contains:
9+
10+
- A set of resource types.
11+
- A `DbContext` class to register the resource types.
12+
- Fakers to generate deterministic test data.
13+
- Test classes that assert the feature works as expected.
14+
- Entities are inserted into a randomly named PostgreSQL database.
15+
- An HTTP request is sent.
16+
- The returned response is asserted on.
17+
- If applicable, the changes are fetched from the database and asserted on.
18+
19+
To run/debug the integration tests, follow the steps in [README.md](https://github.com/json-api-dotnet/JsonApiDotNetCore#build-from-source).

docs/usage/advanced/links.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Links
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/Links) shows various ways to configure which links are returned, and how they appear in responses.
4+
5+
> [!TIP]
6+
> By default, absolute links are returned. To return relative links, set [JsonApiOptions.UseRelativeLinks](~/usage/options.md#relative-links) at startup.
7+
8+
> [!TIP]
9+
> To add a global prefix to all routes, set `JsonApiOptions.Namespace` at startup.
10+
11+
Which links to render can be configured globally in options, then overridden per resource type, and then overridden per relationship.
12+
13+
- The `PhotoLocation` resource type turns off `TopLevelLinks` and `ResourceLinks`, and sets `RelationshipLinks` to `Related`.
14+
- The `PhotoLocation.Album` relationship turns off all links for this relationship.
15+
16+
The various tests set `JsonApiOptions.Namespace` and `JsonApiOptions.UseRelativeLinks` to verify that the proper links are rendered.
17+
This can't be set in the tests directly for technical reasons, so they use different `Startup` classes to control this.
18+
19+
Link rendering is fully controlled using attributes on your models. No further code is needed.

docs/usage/advanced/microservices.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Microservices
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices) shows patterns commonly used in microservices architecture:
4+
5+
- [Fire-and-forget](https://microservices.io/patterns/communication-style/messaging.html): Outgoing messages are sent to an external queue, without waiting for their processing to start. While this is the simplest solution, it is not very reliable when errors occur.
6+
- [Transactional Outbox Pattern](https://microservices.io/patterns/data/transactional-outbox.html): Outgoing messages are saved to a queue table within the same database transaction. A background job (omitted in this example) polls the queue table and sends the messages to an external queue.
7+
8+
> [!TIP]
9+
> Potential external queue systems you could use are [RabbitMQ](https://www.rabbitmq.com/), [MassTransit](https://masstransit.io/),
10+
> [Azure Service Bus](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview) and [Apache Kafka](https://kafka.apache.org/). However, this is beyond the scope of this topic.
11+
12+
The `Messages` directory lists the functional messages that are created from incoming JSON:API requests, which are typically processed by an external system that handles messages from the queue.
13+
Each message has a unique ID and type, and is versioned to support gradual deployments.
14+
Example payloads of messages are: user created, user login name changed, user moved to group, group created, group renamed, etc.
15+
16+
The abstract types `MessagingGroupDefinition` and `MessagingUserDefinition` are resource definitions that contain code shared by both patterns. They inspect the incoming request and produce one or more functional messages from it.
17+
The pattern-specific derived types inject their `DbContext`, which is used to query for additional information when determining what is being changed.
18+
19+
> [!NOTE]
20+
> Because networks are inherently unreliable, systems that consume messages from an external queue should be [idempotent](https://microservices.io/patterns/communication-style/idempotent-consumer.html).
21+
> Several years ago, a [prototype](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1132) was built to make JSON:API idempotent, but it was never finished due to a lack of community interest.
22+
> Please [open an issue](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/new?labels=enhancement) if idempotency matters to you.

docs/usage/advanced/model-state.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ASP.NET Model Validation
2+
3+
The code [here](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState) shows how to use [ASP.NET Model Validation](https://learn.microsoft.com/aspnet/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api) attributes.
4+
5+
> [!TIP]
6+
> See [Atomic Operations](~/usage/advanced/operations.md) for how to implement a custom model validator.
7+
8+
The resource types are decorated with Model Validation attributes, such as `[Required]`, `[RegularExpression]`, `[MinLength]`, and `[Range]`.
9+
10+
Only the fields that appear in a request body (partial POST/PATCH) are validated.
11+
When validation fails, the source pointer in the response indicates which attribute(s) are invalid.
12+
13+
Model Validation is enabled by default, but can be [turned off in options](~/usage/options.md#modelstate-validation).
14+
Aside from adding validation attributes to your resource properties, no further code is needed.

0 commit comments

Comments
 (0)