Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/build-dev.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

param(
# Specify -NoBuild to skip code build and examples generation. This runs faster, so handy when only editing Markdown files.
[switch] $NoBuild=$False
[switch] $NoBuild=$False,
# Specify -NoOpen to skip opening the documentation website in a web browser.
[switch] $NoOpen=$False
)

function VerifySuccessExitCode {
Expand Down Expand Up @@ -53,9 +55,12 @@ Copy-Item -Force -Recurse home/assets/* _site/styles/

cd _site
$webServerJob = httpserver &
Start-Process "http://localhost:8080/"
cd ..

if (-Not $NoOpen) {
Start-Process "http://localhost:8080/"
}

Write-Host ""
Write-Host "Web server started. Press Enter to close."
$key = [Console]::ReadKey()
Expand Down
53 changes: 53 additions & 0 deletions docs/usage/extensibility/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,56 @@ public class ReportsController : JsonApiController<Report, int>
```

For more information about resource service injection, see [Replacing injected services](~/usage/extensibility/layer-overview.md#replacing-injected-services) and [Resource Services](~/usage/extensibility/services.md).

## Custom action methods

Aside from adding custom ASP.NET controllers and Minimal API endpoints to your project that are unrelated to JSON:API,
you can also augment JsonApiDotNetCore controllers with custom action methods.
This applies to both auto-generated and explicit controllers.

When doing so, they participate in the JsonApiDotNetCore pipeline, which means that JSON:API query string parameters are available,
exceptions are handled, and the request/response bodies match the JSON:API structure. As a result, the following restrictions apply:

- The input/output resource types used must exist in the resource graph.
- For primary endpoints, the input/output resource types must match the controller resource type.
- An action method can only return a resource, a collection of resources, an error, or null.

For example, the following custom POST endpoint doesn't take a request body and returns a collection of resources:

```c#
partial class TagsController
{
// POST /tags/defaults
[HttpPost("defaults")]
public async Task<IActionResult> CreateDefaultTagsAsync()
{
List<string> defaultTagNames =
[
"Create design",
"Implement feature",
"Write tests",
"Update documentation",
"Deploy changes"
];

bool hasDefaultTags = await _appDbContext.Tags.AnyAsync(tag => defaultTagNames.Contains(tag.Name));
if (hasDefaultTags)
{
throw new JsonApiException(new ErrorObject(HttpStatusCode.Conflict)
{
Title = "Default tags already exist."
});
}

List<Tag> defaultTags = defaultTagNames.Select(name => new Tag
{
Name = name
}).ToList();

_appDbContext.Tags.AddRange(defaultTags);
await _appDbContext.SaveChangesAsync();

return Ok(defaultTags);
}
}
```
43 changes: 40 additions & 3 deletions docs/usage/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ provides OpenAPI support for JSON:API by integrating with [Swashbuckle](https://

By default, the OpenAPI document will be available at `http://localhost:<port>/swagger/v1/swagger.json`.

> [!TIP]
> In addition to the documentation here, various examples can be found in the [OpenApiTests project](https://github.com/json-api-dotnet/JsonApiDotNetCore/tree/master/test/OpenApiTests).

### Customizing the Route Template

Because Swashbuckle doesn't properly implement the ASP.NET Options pattern, you must *not* use its
[documented way](https://github.com/domaindrivendev/Swashbuckle.AspNetCore?tab=readme-ov-file#change-the-path-for-swagger-json-endpoints)
[documented way](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/docs/configure-and-customize-swagger.md#change-the-path-for-swagger-json-endpoints)
to change the route template:

```c#
Expand Down Expand Up @@ -78,6 +81,40 @@ The `NoWarn` line is optional, which suppresses build warnings for undocumented
</PropertyGroup>
```

You can combine this with the documentation that Swagger itself supports, by enabling it as described
[here](https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments).
You can combine this with the documentation that Swashbuckle itself supports, by enabling it as described
[here](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/docs/configure-and-customize-swaggergen.md#include-descriptions-from-xml-comments).
This adds documentation for additional types, such as triple-slash comments on enums used in your resource models.

## Custom JSON:API action methods

To express the metadata of [custom action methods](~/usage/extensibility/controllers.md#custom-action-methods) in OpenAPI,
use the following attributes on your controller action method:

- The `Name` property on `HttpMethodAttribute` to specify the OpenAPI operation ID, for example:
```c#
[HttpGet("active", Name = "get-active-users")]
```

- `EndpointDescriptionAttribute` to specify the OpenAPI endpoint description, for example:
```c#
[EndpointDescription("Provides access to user accounts.")]
```

- `ConsumesAttribute` to specify the resource type of the request body, for example:
```c#
[Consumes(typeof(UserAccount), "application/vnd.api+json")]
```
> [!NOTE]
> The `contentType` parameter is required, but effectively ignored.

- `ProducesResponseTypeAttribute` attribute(s) to specify the response types and status codes, for example:
```c#
[ProducesResponseType<ICollection<UserAccount>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
```
> [!NOTE]
> For non-success response status codes, the type should be omitted.

Custom parameters on action methods can be decorated with the usual attributes, such as `[Required]`, `[Description]`, etc.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.Swashbuckle;

internal static class ActionDescriptorExtensions
{
public static MethodInfo GetActionMethod(this ActionDescriptor descriptor)
public static MethodInfo? TryGetActionMethod(this ActionDescriptor descriptor)
{
ArgumentNullException.ThrowIfNull(descriptor);

Expand All @@ -16,10 +16,7 @@ public static MethodInfo GetActionMethod(this ActionDescriptor descriptor)
return controllerActionDescriptor.MethodInfo;
}

MethodInfo? methodInfo = descriptor.EndpointMetadata.OfType<MethodInfo>().FirstOrDefault();
ConsistencyGuard.ThrowIf(methodInfo == null);

return methodInfo;
return descriptor.EndpointMetadata.OfType<MethodInfo>().FirstOrDefault();
}

public static ControllerParameterDescriptor? GetBodyParameterDescriptor(this ActionDescriptor descriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,17 @@ private static void IncludeDerivedTypes(ResourceType baseType, List<Type> clrTyp

private static IList<string> GetOpenApiOperationTags(ApiDescription description, IControllerResourceMapping controllerResourceMapping)
{
var actionMethod = OpenApiActionMethod.Create(description.ActionDescriptor);
var actionMethod = JsonApiActionMethod.TryCreate(description.ActionDescriptor);

switch (actionMethod)
{
case AtomicOperationsActionMethod:
case OperationsActionMethod:
{
return ["operations"];
}
case JsonApiActionMethod jsonApiActionMethod:
case ResourceActionMethod resourceActionMethod:
{
ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(jsonApiActionMethod.ControllerType);
ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(resourceActionMethod.ControllerType);
ConsistencyGuard.ThrowIf(resourceType == null);

return [resourceType.PublicName];
Expand Down
Loading
Loading