Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0915cfc

Browse files
committed
Add OpenAPI support for custom JSON:API action methods
Moves success/error status codes into metadata to support custom JSON:API action methods. Refactored internal type hierarchy: [Before] OpenApiActionMethod CustomControllerActionMethod BuiltinJsonApiActionMethod { ControllerType } AtomicOperationsActionMethod JsonApiActionMethod { Endpoint } [After] JsonApiActionMethod { ControllerType } OperationsActionMethod ResourceActionMethod BuiltinResourceActionMethod { Endpoint } CustomResourceActionMethod { Descriptor }
1 parent 2202019 commit 0915cfc

File tree

54 files changed

+3814
-384
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+3814
-384
lines changed

‎docs/usage/extensibility/controllers.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,56 @@ public class ReportsController : JsonApiController<Report, int>
137137
```
138138

139139
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).
140+
141+
## Custom action methods
142+
143+
Aside from adding custom ASP.NET controllers and Minimal API endpoints to your project that are unrelated to JSON:API,
144+
you can also augment JsonApiDotNetCore controllers with custom action methods.
145+
This applies to both auto-generated and explicit controllers.
146+
147+
When doing so, they participate in the JsonApiDotNetCore pipeline, which means that JSON:API query string parameters are available,
148+
exceptions are handled, and the request/response bodies match the JSON:API structure. As a result, the following restrictions apply:
149+
150+
- The input/output resource types used must exist in the resource graph.
151+
- For primary endpoints, the input/output resource types must match the controller resource type.
152+
- An action method can only return a resource, a collection of resources, an error, or null.
153+
154+
For example, the following custom POST endpoint doesn't take a request body and returns a collection of resources:
155+
156+
```c#
157+
partial class TagsController
158+
{
159+
// POST /tags/defaults
160+
[HttpPost("defaults")]
161+
public async Task<IActionResult> CreateDefaultTagsAsync()
162+
{
163+
List<string> defaultTagNames =
164+
[
165+
"Create design",
166+
"Implement feature",
167+
"Write tests",
168+
"Update documentation",
169+
"Deploy changes"
170+
];
171+
172+
bool hasDefaultTags = await _appDbContext.Tags.AnyAsync(tag => defaultTagNames.Contains(tag.Name));
173+
if (hasDefaultTags)
174+
{
175+
throw new JsonApiException(new ErrorObject(HttpStatusCode.Conflict)
176+
{
177+
Title = "Default tags already exist."
178+
});
179+
}
180+
181+
List<Tag> defaultTags = defaultTagNames.Select(name => new Tag
182+
{
183+
Name = name
184+
}).ToList();
185+
186+
_appDbContext.Tags.AddRange(defaultTags);
187+
await _appDbContext.SaveChangesAsync();
188+
189+
return Ok(defaultTags);
190+
}
191+
}
192+
```

‎docs/usage/openapi.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ provides OpenAPI support for JSON:API by integrating with [Swashbuckle](https://
3737
3838
By default, the OpenAPI document will be available at `http://localhost:<port>/swagger/v1/swagger.json`.
3939
40+
> [!TIP]
41+
> 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).
42+
4043
### Customizing the Route Template
4144
4245
Because Swashbuckle doesn't properly implement the ASP.NET Options pattern, you must *not* use its
43-
[documented way](https://github.com/domaindrivendev/Swashbuckle.AspNetCore?tab=readme-ov-file#change-the-path-for-swagger-json-endpoints)
46+
[documented way](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/docs/configure-and-customize-swagger.md#change-the-path-for-swagger-json-endpoints)
4447
to change the route template:
4548
4649
```c#
@@ -78,6 +81,40 @@ The `NoWarn` line is optional, which suppresses build warnings for undocumented
7881
</PropertyGroup>
7982
```
8083

81-
You can combine this with the documentation that Swagger itself supports, by enabling it as described
82-
[here](https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments).
84+
You can combine this with the documentation that Swashbuckle itself supports, by enabling it as described
85+
[here](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/docs/configure-and-customize-swaggergen.md#include-descriptions-from-xml-comments).
8386
This adds documentation for additional types, such as triple-slash comments on enums used in your resource models.
87+
88+
## Custom JSON:API action methods
89+
90+
To express the metadata of [custom action methods](~/usage/extensibility/controllers.md#custom-action-methods) in OpenAPI,
91+
use the following attributes on your controller action method:
92+
93+
- The `Name` property on `HttpMethodAttribute` to specify the OpenAPI operation ID, for example:
94+
```c#
95+
[HttpGet("active", Name = "get-active-users")]
96+
```
97+
98+
- `EndpointDescriptionAttribute` to specify the OpenAPI endpoint description, for example:
99+
```c#
100+
[EndpointDescription("Provides access to user accounts.")]
101+
```
102+
103+
- `ConsumesAttribute` to specify the resource type of the request body, for example:
104+
```c#
105+
[Consumes(typeof(UserAccount), "application/vnd.api+json")]
106+
```
107+
> [!NOTE]
108+
> The `contentType` parameter is required, but effectively ignored.
109+
110+
- `ProducesResponseTypeAttribute` attribute(s) to specify the response types and status codes, for example:
111+
```c#
112+
[ProducesResponseType<ICollection<UserAccount>>(StatusCodes.Status200OK)]
113+
[ProducesResponseType(StatusCodes.Status204NoContent)]
114+
[ProducesResponseType(StatusCodes.Status404NotFound)]
115+
[ProducesResponseType(StatusCodes.Status409Conflict)]
116+
```
117+
> [!NOTE]
118+
> For non-success response status codes, the type should be omitted.
119+
120+
Custom parameters on action methods can be decorated with the usual attributes, such as `[Required]`, `[Description]`, etc.

‎src/JsonApiDotNetCore.OpenApi.Swashbuckle/ActionDescriptorExtensions.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
77

88
internal static class ActionDescriptorExtensions
99
{
10-
public static MethodInfoGetActionMethod(this ActionDescriptor descriptor)
10+
public static MethodInfo?TryGetActionMethod(this ActionDescriptor descriptor)
1111
{
1212
ArgumentNullException.ThrowIfNull(descriptor);
1313

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

19-
MethodInfo? methodInfo = descriptor.EndpointMetadata.OfType<MethodInfo>().FirstOrDefault();
20-
ConsistencyGuard.ThrowIf(methodInfo == null);
21-
22-
return methodInfo;
19+
return descriptor.EndpointMetadata.OfType<MethodInfo>().FirstOrDefault();
2320
}
2421

2522
public static ControllerParameterDescriptor? GetBodyParameterDescriptor(this ActionDescriptor descriptor)

‎src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,17 +146,17 @@ private static void IncludeDerivedTypes(ResourceType baseType, List<Type> clrTyp
146146

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

151151
switch (actionMethod)
152152
{
153-
case AtomicOperationsActionMethod:
153+
case OperationsActionMethod:
154154
{
155155
return ["operations"];
156156
}
157-
case JsonApiActionMethodjsonApiActionMethod:
157+
case ResourceActionMethodresourceActionMethod:
158158
{
159-
ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(jsonApiActionMethod.ControllerType);
159+
ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(resourceActionMethod.ControllerType);
160160
ConsistencyGuard.ThrowIf(resourceType == null);
161161

162162
return [resourceType.PublicName];

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /