Skip to content

Commit e3cea82

Browse files
Add OpenApi support, ensuring that it properly configures the content-types
1 parent c70b331 commit e3cea82

3 files changed

Lines changed: 135 additions & 97 deletions

File tree

JsonApiToolkit/Controllers/JsonApiController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace JsonApiToolkit.Controllers;
2323
[Produces("application/vnd.api+json")]
2424
[Consumes("application/vnd.api+json")]
2525
[ServiceFilter(typeof(JsonApiExceptionFilter))]
26+
[ApiExplorerSettings(GroupName = "JsonApi")]
2627
public abstract class JsonApiController : ControllerBase
2728
{
2829
/// <summary>

JsonApiToolkit/Extensions/ServiceCollectionExtensions.cs

Lines changed: 133 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,115 +4,151 @@
44
using Microsoft.AspNetCore.Mvc;
55
using Microsoft.AspNetCore.Mvc.Formatters;
66
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.OpenApi.Models;
78

8-
namespace JsonApiToolkit.Extensions;
9-
10-
/// <summary>
11-
/// Provides extension methods for integrating JsonApiToolkit into the ASP.NET Core dependency injection system.
12-
/// </summary>
13-
/// <remarks>
14-
/// Contains the core setup method for registering and configuring all JsonApiToolkit components
15-
/// in an ASP.NET Core application.
16-
/// </remarks>
17-
public static class ServiceCollectionExtensions
9+
namespace JsonApiToolkit.Extensions
1810
{
1911
/// <summary>
20-
/// Configures all necessary services and options for JsonApiToolkit in an ASP.NET Core application.
12+
/// Provides extension methods for integrating JsonApiToolkit into the ASP.NET Core dependency injection system.
2113
/// </summary>
22-
/// <param name="services">The service collection to add JsonApiToolkit services to</param>
23-
/// <returns>The service collection for method chaining</returns>
24-
/// <remarks>
25-
/// This method performs the following configuration steps:
26-
/// <list type="number">
27-
/// <item>
28-
/// <description>Configures JSON serialization options:
29-
/// <list type="bullet">
30-
/// <item>
31-
/// <description>Sets camelCase property naming to comply with JSON:API naming conventions</description>
32-
/// </item>
33-
/// <item>
34-
/// <description>Ignores null values to reduce response size</description>
35-
/// </item>
36-
/// <item>
37-
/// <description>Configures reference handling to prevent circular references</description>
38-
/// </item>
39-
/// </list>
40-
/// </description>
41-
/// </item>
42-
/// <item>
43-
/// <description>Adds support for the JSON:API media type:
44-
/// <list type="bullet">
45-
/// <item>
46-
/// <description>Registers "application/vnd.api+json" as a supported media type for JSON formatters</description>
47-
/// </item>
48-
/// <item>
49-
/// <description>Ensures proper content negotiation for JSON:API responses</description>
50-
/// </item>
51-
/// </list>
52-
/// </description>
53-
/// </item>
54-
/// <item>
55-
/// <description>Registers the JSON:API exception filter:
56-
/// <list type="bullet">
57-
/// <item>
58-
/// <description>Provides standardized error handling for unhandled exceptions</description>
59-
/// </item>
60-
/// <item>
61-
/// <description>Formats errors according to the JSON:API specification</description>
62-
/// </item>
63-
/// </list>
64-
/// </description>
65-
/// </item>
66-
/// </list>
67-
/// Call this method in your Startup.ConfigureServices or Program.cs to fully configure JsonApiToolkit.
68-
/// <para>
69-
/// Example:
70-
/// <code>
71-
/// builder.Services.AddJsonApiToolkit();
72-
/// </code>
73-
/// </para>
74-
/// </remarks>
75-
public static IServiceCollection AddJsonApiToolkit(this IServiceCollection services)
14+
public static class ServiceCollectionExtensions
7615
{
77-
services.Configure<JsonOptions>(options =>
78-
{
79-
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
80-
options.JsonSerializerOptions.DefaultIgnoreCondition =
81-
JsonIgnoreCondition.WhenWritingNull;
82-
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
83-
});
84-
85-
services.Configure<MvcOptions>(options =>
16+
/// <summary>
17+
/// Configures all necessary services and options for JsonApiToolkit in an ASP.NET Core application.
18+
/// Also configures OpenAPI/Swagger to use the correct JSON:API content types for controllers inheriting from JsonApiController.
19+
/// </summary>
20+
/// <param name="services">The service collection to add JsonApiToolkit services to.</param>
21+
/// <returns>The service collection for method chaining.</returns>
22+
/// <remarks>
23+
/// This method:
24+
/// <list type="number">
25+
/// <item>
26+
/// <description>Configures JSON serialization options for JSON:API.</description>
27+
/// </item>
28+
/// <item>
29+
/// <description>Adds support for the JSON:API media type to input/output formatters.</description>
30+
/// </item>
31+
/// <item>
32+
/// <description>Registers JSON:API exception and content-type filters.</description>
33+
/// </item>
34+
/// <item>
35+
/// <description>Configures OpenAPI/Swagger to use "application/vnd.api+json" for all endpoints inheriting from JsonApiController.</description>
36+
/// </item>
37+
/// </list>
38+
/// </remarks>
39+
public static IServiceCollection AddJsonApiToolkit(this IServiceCollection services)
8640
{
87-
SystemTextJsonOutputFormatter? jsonOutputFormatter = options
88-
.OutputFormatters.OfType<SystemTextJsonOutputFormatter>()
89-
.FirstOrDefault();
41+
// Configure JSON serialization options
42+
services.Configure<JsonOptions>(options =>
43+
{
44+
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
45+
options.JsonSerializerOptions.DefaultIgnoreCondition =
46+
JsonIgnoreCondition.WhenWritingNull;
47+
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
48+
});
9049

91-
if (
92-
jsonOutputFormatter?.SupportedMediaTypes.Contains("application/vnd.api+json")
93-
== false
94-
)
50+
// Configure MVC formatters and filters
51+
services.Configure<MvcOptions>(options =>
9552
{
96-
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.api+json");
97-
}
53+
SystemTextJsonOutputFormatter? jsonOutputFormatter = options
54+
.OutputFormatters.OfType<SystemTextJsonOutputFormatter>()
55+
.FirstOrDefault();
56+
57+
if (
58+
jsonOutputFormatter?.SupportedMediaTypes.Contains("application/vnd.api+json")
59+
== false
60+
)
61+
{
62+
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.api+json");
63+
}
64+
65+
SystemTextJsonInputFormatter? jsonInputFormatter = options
66+
.InputFormatters.OfType<SystemTextJsonInputFormatter>()
67+
.FirstOrDefault();
68+
69+
if (
70+
jsonInputFormatter?.SupportedMediaTypes.Contains("application/vnd.api+json")
71+
== false
72+
)
73+
{
74+
jsonInputFormatter.SupportedMediaTypes.Add("application/vnd.api+json");
75+
}
76+
77+
options.Filters.AddService<JsonApiContentTypeFilter>();
78+
});
9879

99-
SystemTextJsonInputFormatter? jsonInputFormatter = options
100-
.InputFormatters.OfType<SystemTextJsonInputFormatter>()
101-
.FirstOrDefault();
80+
// Register filters
81+
services.AddScoped<JsonApiExceptionFilter>();
82+
services.AddScoped<JsonApiContentTypeFilter>();
10283

103-
if (
104-
jsonInputFormatter?.SupportedMediaTypes.Contains("application/vnd.api+json")
105-
== false
106-
)
84+
// Register OpenAPI document transformer for JSON:API content types
85+
services.AddOpenApi(o =>
10786
{
108-
jsonInputFormatter.SupportedMediaTypes.Add("application/vnd.api+json");
109-
}
110-
options.Filters.AddService<JsonApiContentTypeFilter>();
111-
});
87+
// OpenAPI document transformer that rewrites request and response content types
88+
// to "application/vnd.api+json" for all endpoints tagged as "JsonApi".
89+
o.AddDocumentTransformer(
90+
(document, _, _) =>
91+
{
92+
foreach (var path in document.Paths.Values)
93+
{
94+
foreach (var operation in path.Operations.Values)
95+
{
96+
// Only apply to operations tagged as "JsonApi"
97+
if (operation.Tags.Any(t => t.Name == "JsonApi"))
98+
{
99+
// Rewrite request body content types
100+
if (operation.RequestBody != null)
101+
{
102+
if (
103+
operation.RequestBody.Content.TryGetValue(
104+
"application/json",
105+
out var jsonContent
106+
)
107+
)
108+
{
109+
operation.RequestBody.Content[
110+
"application/vnd.api+json"
111+
] = jsonContent;
112+
operation.RequestBody.Content.Remove(
113+
"application/json"
114+
);
115+
}
116+
else if (
117+
!operation.RequestBody.Content.ContainsKey(
118+
"application/vnd.api+json"
119+
)
120+
)
121+
{
122+
operation.RequestBody.Content[
123+
"application/vnd.api+json"
124+
] = new OpenApiMediaType();
125+
}
126+
}
112127

113-
services.AddScoped<JsonApiExceptionFilter>();
114-
services.AddScoped<JsonApiContentTypeFilter>();
128+
// Rewrite response content types
129+
foreach (var response in operation.Responses.Values)
130+
{
131+
if (
132+
response.Content.TryGetValue(
133+
"application/json",
134+
out var jsonContent
135+
)
136+
)
137+
{
138+
response.Content["application/vnd.api+json"] =
139+
jsonContent;
140+
response.Content.Remove("application/json");
141+
}
142+
}
143+
}
144+
}
145+
}
146+
return Task.CompletedTask;
147+
}
148+
);
149+
});
115150

116-
return services;
151+
return services;
152+
}
117153
}
118154
}

JsonApiToolkit/JsonApiToolkit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.3.0" />
2626
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
2727
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
28+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
2829
</ItemGroup>
2930
<ItemGroup>
3031
<None Include="README.md" Pack="true" PackagePath="\" />

0 commit comments

Comments
 (0)