From b8f3dda59efb1a16ad940bf3d0a1b17d47293003 Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Sun, 20 Apr 2025 23:29:51 +0100 Subject: [PATCH 1/6] fix minimal api json options not being respected --- .../SwaggerGenServiceCollectionExtensions.cs | 51 ++++++++++++++++--- .../PublicAPI/net8.0/PublicAPI.Shipped.txt | 4 +- .../PublicAPI/net9.0/PublicAPI.Shipped.txt | 4 +- ...waggerRequestUri=v1.DotNet8_0.verified.txt | 9 ++-- ...waggerRequestUri=v1.DotNet9_0.verified.txt | 9 ++-- ...reRenderedCorrectly.DotNet8_0.verified.txt | 9 ++-- ...reRenderedCorrectly.DotNet9_0.verified.txt | 9 ++-- 7 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs index bcbfc82a7e..c31230ffa4 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs @@ -44,6 +44,28 @@ public static IServiceCollection AddSwaggerGen( return services; } + public static IServiceCollection AddSwaggerGenMinimalApisJsonOptions(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient((s) => + { + var options = s.GetRequiredService>().Value.SerializerOptions; + + return new JsonSerializerDataContractResolver(options); + })); + } + + public static IServiceCollection AddSwaggerGenMvcJsonOptions(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient((s) => + { + var options = s.GetRequiredService>().Value.JsonSerializerOptions; + + return new JsonSerializerDataContractResolver(options); + })); + } + public static void ConfigureSwaggerGen( this IServiceCollection services, Action setupAction) @@ -68,17 +90,34 @@ private JsonSerializerOptions ResolveOptions() JsonSerializerOptions serializerOptions; /* - * First try to get the options configured for MVC, - * then try to get the options configured for Minimal APIs if available, - * then try the default JsonSerializerOptions if available, - * otherwise create a new instance as a last resort as this is an expensive operation. + * There is no surefire way to do this. + * However, both JsonOptions are defaulted in the same way. + * If neither is configured it makes no difference which one is chosen. + * If both are configured, then we just need to make a choice. + * As Minimal APIs are newer if someone is configuring them + * it's probably more likely that is what they're using. + * + * If either JsonOptions is null we will try to create a new instance as + * a last resort as this is an expensive operation. */ serializerOptions = - _serviceProvider.GetService>()?.Value?.JsonSerializerOptions - ?? _serviceProvider.GetService>()?.Value?.SerializerOptions + _serviceProvider.GetService>()?.Value?.SerializerOptions ?? JsonSerializerOptions.Default; + if (HasConfiguredMinimalApiJsonOptions()) + { + serializerOptions ??= _serviceProvider.GetService>()?.Value?.SerializerOptions; + } + return serializerOptions; } + + private bool HasConfiguredMinimalApiJsonOptions() + { + if (_serviceProvider.GetService>>().Any()) + return true; + + return _serviceProvider.GetService>>().Any(); + } } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt index 02932c3763..349b7333f1 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -1,4 +1,6 @@ -static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json, System.Text.Json.JsonSerializerOptions options) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiSchemaExtensions.ResolveType(this Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository schemaRepository) -> string Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt index 02932c3763..349b7333f1 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt @@ -1,4 +1,6 @@ -static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json, System.Text.Json.JsonSerializerOptions options) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiSchemaExtensions.ResolveType(this Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository schemaRepository) -> string Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet8_0.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet8_0.verified.txt index d437eea1cf..176304b23b 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet8_0.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet8_0.verified.txt @@ -988,12 +988,11 @@ }, "DateTimeKind": { "enum": [ - 0, - 1, - 2 + "Unspecified", + "Utc", + "Local" ], - "type": "integer", - "format": "int32" + "type": "string" }, "Fruit": { "type": "object", diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet9_0.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet9_0.verified.txt index d437eea1cf..176304b23b 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet9_0.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=WebApi.Program_swaggerRequestUri=v1.DotNet9_0.verified.txt @@ -988,12 +988,11 @@ }, "DateTimeKind": { "enum": [ - 0, - 1, - 2 + "Unspecified", + "Utc", + "Local" ], - "type": "integer", - "format": "int32" + "type": "string" }, "Fruit": { "type": "object", diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet8_0.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet8_0.verified.txt index d437eea1cf..176304b23b 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet8_0.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet8_0.verified.txt @@ -988,12 +988,11 @@ }, "DateTimeKind": { "enum": [ - 0, - 1, - 2 + "Unspecified", + "Utc", + "Local" ], - "type": "integer", - "format": "int32" + "type": "string" }, "Fruit": { "type": "object", diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet9_0.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet9_0.verified.txt index d437eea1cf..176304b23b 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet9_0.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/VerifyTests.TypesAreRenderedCorrectly.DotNet9_0.verified.txt @@ -988,12 +988,11 @@ }, "DateTimeKind": { "enum": [ - 0, - 1, - 2 + "Unspecified", + "Utc", + "Local" ], - "type": "integer", - "format": "int32" + "type": "string" }, "Fruit": { "type": "object", From 010792eafab376bd04599c2eecf722fc739150f2 Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Mon, 21 Apr 2025 20:42:41 +0100 Subject: [PATCH 2/6] move changes from shipped to unshipped --- .../PublicAPI/net8.0/PublicAPI.Shipped.txt | 4 +--- .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 3 ++- .../PublicAPI/net9.0/PublicAPI.Shipped.txt | 4 +--- .../PublicAPI/net9.0/PublicAPI.Unshipped.txt | 3 ++- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt index 349b7333f1..02932c3763 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -1,6 +1,4 @@ -static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection -static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection -static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny +static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json, System.Text.Json.JsonSerializerOptions options) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiSchemaExtensions.ResolveType(this Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository schemaRepository) -> string Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 5f282702bb..fc2380cbac 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt index 349b7333f1..02932c3763 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Shipped.txt @@ -1,6 +1,4 @@ -static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection -static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection -static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny +static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory.CreateFromJson(string json, System.Text.Json.JsonSerializerOptions options) -> Microsoft.OpenApi.Any.IOpenApiAny static Swashbuckle.AspNetCore.SwaggerGen.OpenApiSchemaExtensions.ResolveType(this Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository schemaRepository) -> string Swashbuckle.AspNetCore.SwaggerGen.OpenApiAnyFactory diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Unshipped.txt index 5f282702bb..fc2380cbac 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ - \ No newline at end of file +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMinimalApisJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection +static Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.AddSwaggerGenMvcJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection From a7f843abef1522ff776ea2bfbe64311857f45584 Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Tue, 29 Apr 2025 23:58:17 +0100 Subject: [PATCH 3/6] refactor to use an options class and add extra tests for json options override methods --- ...onfigureMinimalApiSwaggerGenJsonOptions.cs | 12 ++ .../ConfigureMvcSwaggerGenJsonOptions.cs | 12 ++ .../ConfigureSwaggerGenJsonOptions.cs | 59 ++++++++++ .../SwaggerGenJsonOptions.cs | 8 ++ .../SwaggerGenServiceCollectionExtensions.cs | 72 +----------- .../PublicAPI/PublicAPI.Unshipped.txt | 4 + .../SwaggerGenJsonOptionsTests.cs | 105 ++++++++++++++++++ 7 files changed, 206 insertions(+), 66 deletions(-) create mode 100644 src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs create mode 100644 src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs create mode 100644 src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs create mode 100644 src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs new file mode 100644 index 0000000000..78fb216fe0 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Http.Json; +using Microsoft.Extensions.Options; + +namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; + +internal class ConfigureMinimalApiSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions +{ + public void Configure(SwaggerGenJsonOptions options) + { + options.SerializerOptions = jsonOptions.Value.SerializerOptions; + } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs new file mode 100644 index 0000000000..c9937ccfba --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; + +internal class ConfigureMvcSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions +{ + public void Configure(SwaggerGenJsonOptions options) + { + options.SerializerOptions = jsonOptions.Value.JsonSerializerOptions; + } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs new file mode 100644 index 0000000000..f151b9e591 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using Microsoft.Extensions.Options; + +namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; + +internal class ConfigureSwaggerGenJsonOptions : IPostConfigureOptions +{ + private readonly IEnumerable> _minimalApiConfigureOptions; + private readonly IEnumerable> _minimalApiPostConfigureOptions; + private readonly Microsoft.AspNetCore.Http.Json.JsonOptions _minimalApiJsonOptions; + private readonly Microsoft.AspNetCore.Mvc.JsonOptions _mvcJsonOptions; + + public ConfigureSwaggerGenJsonOptions( + IEnumerable> minimalApiConfigureOptions, + IEnumerable> minimalApiPostConfigureOptions, + IOptions minimalApiJsonOptions, + IOptions mvcJsonOptions + ) + { + _minimalApiConfigureOptions = minimalApiConfigureOptions; + _minimalApiPostConfigureOptions = minimalApiPostConfigureOptions; + _minimalApiJsonOptions = minimalApiJsonOptions.Value; + _mvcJsonOptions = mvcJsonOptions.Value; + } + + public void PostConfigure(string name, SwaggerGenJsonOptions options) + { + if (options.SerializerOptions != null) + { + return; + } + + /* + * There is no surefire way to do this. + * However, both JsonOptions are defaulted in the same way. + * If neither is configured it makes no difference which one is chosen. + * If both are configured, then we just need to make a choice. + * As Minimal APIs are newer if someone is configuring them + * it's probably more likely that is what they're using. + * + * If either JsonOptions is null we will try to create a new instance as + * a last resort as this is an expensive operation. + */ + + var serializerOptions = _mvcJsonOptions.JsonSerializerOptions ?? JsonSerializerOptions.Default; + + if (HasConfiguredMinimalApiJsonOptions()) + { + serializerOptions = _minimalApiJsonOptions.SerializerOptions ?? serializerOptions; + } + + options.SerializerOptions = serializerOptions; + } + + private bool HasConfiguredMinimalApiJsonOptions() + { + return _minimalApiConfigureOptions.Any() || _minimalApiPostConfigureOptions.Any(); + } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs new file mode 100644 index 0000000000..b5c2075887 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs @@ -0,0 +1,8 @@ +using System.Text.Json; + +namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; + +public class SwaggerGenJsonOptions +{ + public JsonSerializerOptions SerializerOptions { get; set; } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs index c31230ffa4..83556de385 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenServiceCollectionExtensions.cs @@ -1,9 +1,9 @@ -using System.Text.Json; -using Microsoft.Extensions.ApiDescriptions; +using Microsoft.Extensions.ApiDescriptions; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection; @@ -29,10 +29,10 @@ public static IServiceCollection AddSwaggerGen( services.TryAddTransient(s => s.GetRequiredService>().Value); services.TryAddTransient(); services.TryAddTransient(s => s.GetRequiredService>().Value); - services.AddSingleton(); + services.ConfigureOptions(); services.TryAddSingleton(s => { - var serializerOptions = s.GetRequiredService().Options; + var serializerOptions = s.GetRequiredService>().Value.SerializerOptions; return new JsonSerializerDataContractResolver(serializerOptions); }); @@ -46,24 +46,12 @@ public static IServiceCollection AddSwaggerGen( public static IServiceCollection AddSwaggerGenMinimalApisJsonOptions(this IServiceCollection services) { - return services.Replace( - ServiceDescriptor.Transient((s) => - { - var options = s.GetRequiredService>().Value.SerializerOptions; - - return new JsonSerializerDataContractResolver(options); - })); + return services.ConfigureOptions(); } public static IServiceCollection AddSwaggerGenMvcJsonOptions(this IServiceCollection services) { - return services.Replace( - ServiceDescriptor.Transient((s) => - { - var options = s.GetRequiredService>().Value.JsonSerializerOptions; - - return new JsonSerializerDataContractResolver(options); - })); + return services.ConfigureOptions(); } public static void ConfigureSwaggerGen( @@ -72,52 +60,4 @@ public static void ConfigureSwaggerGen( { services.Configure(setupAction); } - - private sealed class JsonSerializerOptionsProvider - { - private JsonSerializerOptions _options; - private readonly IServiceProvider _serviceProvider; - - public JsonSerializerOptionsProvider(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public JsonSerializerOptions Options => _options ??= ResolveOptions(); - - private JsonSerializerOptions ResolveOptions() - { - JsonSerializerOptions serializerOptions; - - /* - * There is no surefire way to do this. - * However, both JsonOptions are defaulted in the same way. - * If neither is configured it makes no difference which one is chosen. - * If both are configured, then we just need to make a choice. - * As Minimal APIs are newer if someone is configuring them - * it's probably more likely that is what they're using. - * - * If either JsonOptions is null we will try to create a new instance as - * a last resort as this is an expensive operation. - */ - serializerOptions = - _serviceProvider.GetService>()?.Value?.SerializerOptions - ?? JsonSerializerOptions.Default; - - if (HasConfiguredMinimalApiJsonOptions()) - { - serializerOptions ??= _serviceProvider.GetService>()?.Value?.SerializerOptions; - } - - return serializerOptions; - } - - private bool HasConfiguredMinimalApiJsonOptions() - { - if (_serviceProvider.GetService>>().Any()) - return true; - - return _serviceProvider.GetService>>().Any(); - } - } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt index e69de29bb2..17ed50dfe2 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions +Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SerializerOptions.get -> System.Text.Json.JsonSerializerOptions +Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SerializerOptions.set -> void +Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SwaggerGenJsonOptions() -> void diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs new file mode 100644 index 0000000000..bbc0de3367 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs @@ -0,0 +1,105 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; + +namespace Swashbuckle.AspNetCore.SwaggerGen.Test; + +public class SwaggerGenJsonOptionsTests +{ + [Fact] + public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When_Overridden() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSwaggerGen(); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter())); + services.AddSwaggerGenMinimalApisJsonOptions(); + + using var provider = services.BuildServiceProvider(); + + var swaggerGenConverters = provider.GetService>().Value.SerializerOptions.Converters; + + Assert.Empty(swaggerGenConverters); + } + + [Fact] + public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Overridden() + { + var expectedDummyConverter = new DummyConverter(); + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSwaggerGen(); + services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(new DummyConverter())); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter)); + services.AddSwaggerGenMvcJsonOptions(); + + using var provider = services.BuildServiceProvider(); + + var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); + + Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); + } + + [Fact] + public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Not_Using_Minimal_Apis() + { + var expectedDummyConverter = new DummyConverter(); + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSwaggerGen(); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter)); + + using var provider = services.BuildServiceProvider(); + + var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); + + Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); + } + + [Fact] + public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When_Configured() + { + var expectedDummyConverter = new DummyConverter(); + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSwaggerGen(); + services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(expectedDummyConverter)); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter())); + + using var provider = services.BuildServiceProvider(); + + var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); + + Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); + } + + private sealed class DummyHostEnvironment : IWebHostEnvironment + { + public string WebRootPath { get; set; } + public IFileProvider WebRootFileProvider { get; set; } + public string ApplicationName { get; set; } + public IFileProvider ContentRootFileProvider { get; set; } + public string ContentRootPath { get; set; } + public string EnvironmentName { get; set; } + } + + private sealed class DummyConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} From c48911f7cd40b0c0b556922ef63f967407b91fbb Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Sun, 11 May 2025 16:06:56 +0100 Subject: [PATCH 4/6] apply suggestions --- README.md | 13 +++++++++++++ .../ConfigureMinimalApiSwaggerGenJsonOptions.cs | 2 +- .../ConfigureMvcSwaggerGenJsonOptions.cs | 2 +- .../ConfigureSwaggerGenJsonOptions.cs | 12 +++--------- .../DependencyInjection/SwaggerGenJsonOptions.cs | 6 ++++++ .../Fixtures/DummyHostEnvironment.cs | 14 ++++++++++++++ .../SwaggerGenJsonOptionsTests.cs | 12 +----------- .../XmlComments/XmlCommentsDocumentFilterTests.cs | 12 +----------- 8 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/DummyHostEnvironment.cs diff --git a/README.md b/README.md index 9a86d8edac..6731bbf409 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,19 @@ In versions of Swashbuckle.AspNetCore prior to `5.0.0`, Swashbuckle would genera on the behavior of the [Newtonsoft.Json serializer][newtonsoft-json]. This made sense because that was the serializer that shipped with ASP.NET Core at the time. However, since ASP.NET Core 3.0, ASP.NET Core introduces a new serializer, [System.Text.Json (STJ)][system-text-json] out-of-the-box. +If you find that the *STJ* options/attributes are not being honored, this may be because you are using a combination of minimal apis and mvc which have separate json options. +To force the swagger generation to use either set of json options you can use one of the following methods. + +```csharp +services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); +}); +services.AddSwaggerGenMinimalApisJsonOptions(); +// or ... +services.AddSwaggerGenMvcJsonOptions(); +``` + If you want to use Newtonsoft.Json instead, you need to install a separate package and explicitly opt-in. By default Swashbuckle.AspNetCore will assume you're using the System.Text.Json serializer and generate Schemas based on its behavior. If you're using Newtonsoft.Json then you'll need to install a separate Swashbuckle package, [Swashbuckle.AspNetCore.Newtonsoft][swashbuckle-aspnetcore-newtonsoft] to explicitly opt-in. diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs index 78fb216fe0..0e6e3fa67e 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMinimalApiSwaggerGenJsonOptions.cs @@ -3,7 +3,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; -internal class ConfigureMinimalApiSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions +internal sealed class ConfigureMinimalApiSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions { public void Configure(SwaggerGenJsonOptions options) { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs index c9937ccfba..1d9d3ea0d2 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureMvcSwaggerGenJsonOptions.cs @@ -3,7 +3,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; -internal class ConfigureMvcSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions +internal sealed class ConfigureMvcSwaggerGenJsonOptions(IOptions jsonOptions) : IConfigureOptions { public void Configure(SwaggerGenJsonOptions options) { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs index f151b9e591..c924430036 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs @@ -3,7 +3,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; -internal class ConfigureSwaggerGenJsonOptions : IPostConfigureOptions +internal sealed class ConfigureSwaggerGenJsonOptions : IPostConfigureOptions { private readonly IEnumerable> _minimalApiConfigureOptions; private readonly IEnumerable> _minimalApiPostConfigureOptions; @@ -14,8 +14,7 @@ public ConfigureSwaggerGenJsonOptions( IEnumerable> minimalApiConfigureOptions, IEnumerable> minimalApiPostConfigureOptions, IOptions minimalApiJsonOptions, - IOptions mvcJsonOptions - ) + IOptions mvcJsonOptions) { _minimalApiConfigureOptions = minimalApiConfigureOptions; _minimalApiPostConfigureOptions = minimalApiPostConfigureOptions; @@ -44,16 +43,11 @@ public void PostConfigure(string name, SwaggerGenJsonOptions options) var serializerOptions = _mvcJsonOptions.JsonSerializerOptions ?? JsonSerializerOptions.Default; - if (HasConfiguredMinimalApiJsonOptions()) + if (_minimalApiConfigureOptions.Any() || _minimalApiPostConfigureOptions.Any()) { serializerOptions = _minimalApiJsonOptions.SerializerOptions ?? serializerOptions; } options.SerializerOptions = serializerOptions; } - - private bool HasConfiguredMinimalApiJsonOptions() - { - return _minimalApiConfigureOptions.Any() || _minimalApiPostConfigureOptions.Any(); - } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs index b5c2075887..4b46263c85 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs @@ -2,7 +2,13 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; +/// +/// Configures the to be used by . +/// public class SwaggerGenJsonOptions { + /// + /// Gets or Sets the json serializer options used by . + /// public JsonSerializerOptions SerializerOptions { get; set; } } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/DummyHostEnvironment.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/DummyHostEnvironment.cs new file mode 100644 index 0000000000..40b2ae70b4 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/DummyHostEnvironment.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.FileProviders; + +namespace Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures; + +internal sealed class DummyHostEnvironment : IWebHostEnvironment +{ + public string WebRootPath { get; set; } + public IFileProvider WebRootFileProvider { get; set; } + public string ApplicationName { get; set; } + public IFileProvider ContentRootFileProvider { get; set; } + public string ContentRootPath { get; set; } + public string EnvironmentName { get; set; } +} diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs index bbc0de3367..a129f0b8e7 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs @@ -2,9 +2,9 @@ using System.Text.Json.Serialization; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; +using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures; namespace Swashbuckle.AspNetCore.SwaggerGen.Test; @@ -80,16 +80,6 @@ public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); } - private sealed class DummyHostEnvironment : IWebHostEnvironment - { - public string WebRootPath { get; set; } - public IFileProvider WebRootFileProvider { get; set; } - public string ApplicationName { get; set; } - public IFileProvider ContentRootFileProvider { get; set; } - public string ContentRootPath { get; set; } - public string EnvironmentName { get; set; } - } - private sealed class DummyConverter : JsonConverter { public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsDocumentFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsDocumentFilterTests.cs index 54602b782f..bc16a39135 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsDocumentFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsDocumentFilterTests.cs @@ -4,8 +4,8 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures; namespace Swashbuckle.AspNetCore.SwaggerGen.Test; @@ -179,14 +179,4 @@ public void Ensure_IncludeXmlComments_Adds_Filter_To_Options() Assert.NotNull(options); Assert.Contains(options.DocumentFilters, x => x is XmlCommentsDocumentFilter); } - - private sealed class DummyHostEnvironment : IWebHostEnvironment - { - public string WebRootPath { get; set; } - public IFileProvider WebRootFileProvider { get; set; } - public string ApplicationName { get; set; } - public IFileProvider ContentRootFileProvider { get; set; } - public string ContentRootPath { get; set; } - public string EnvironmentName { get; set; } - } } From 0281a56cb3d1521eadf27e9d714541e149d00977 Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Sun, 1 Jun 2025 22:20:16 +0100 Subject: [PATCH 5/6] apply pr suggestions --- README.md | 5 +- .../SwaggerGenJsonOptions.cs | 2 +- .../SwaggerGenJsonOptionsTests.cs | 57 +++++++------------ test/WebSites/Basic/Startup.cs | 2 + .../MinimalAppWithHostedServices/Program.cs | 2 + 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 6731bbf409..210341285e 100644 --- a/README.md +++ b/README.md @@ -115,14 +115,15 @@ In versions of Swashbuckle.AspNetCore prior to `5.0.0`, Swashbuckle would genera on the behavior of the [Newtonsoft.Json serializer][newtonsoft-json]. This made sense because that was the serializer that shipped with ASP.NET Core at the time. However, since ASP.NET Core 3.0, ASP.NET Core introduces a new serializer, [System.Text.Json (STJ)][system-text-json] out-of-the-box. -If you find that the *STJ* options/attributes are not being honored, this may be because you are using a combination of minimal apis and mvc which have separate json options. -To force the swagger generation to use either set of json options you can use one of the following methods. +If you find that the *STJ* options/attributes are not being honored, this may be because you are using a combination of Minimal APIs and MVC, which have separate JSON options. +To force the OpenAPI document generation to use either set of JSON options you can use one of the following methods: ```csharp services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); }); +// Then either: services.AddSwaggerGenMinimalApisJsonOptions(); // or ... services.AddSwaggerGenMvcJsonOptions(); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs index 4b46263c85..f98352bacc 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenJsonOptions.cs @@ -8,7 +8,7 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; public class SwaggerGenJsonOptions { /// - /// Gets or Sets the json serializer options used by . + /// Gets or sets the JSON serializer options to use. /// public JsonSerializerOptions SerializerOptions { get; set; } } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs index a129f0b8e7..cb4c76ec81 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenJsonOptionsTests.cs @@ -6,6 +6,9 @@ using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection; using Swashbuckle.AspNetCore.SwaggerGen.Test.Fixtures; +using MinimalApiJsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; +using MvcJsonOptions = Microsoft.AspNetCore.Mvc.JsonOptions; + namespace Swashbuckle.AspNetCore.SwaggerGen.Test; public class SwaggerGenJsonOptionsTests @@ -16,80 +19,60 @@ public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When var services = new ServiceCollection(); services.AddSingleton(); services.AddSwaggerGen(); - services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter())); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.AllowTrailingCommas = true); services.AddSwaggerGenMinimalApisJsonOptions(); using var provider = services.BuildServiceProvider(); - var swaggerGenConverters = provider.GetService>().Value.SerializerOptions.Converters; - - Assert.Empty(swaggerGenConverters); + var minimalApiJsonOptions = provider.GetService>().Value.SerializerOptions; + var swaggerGenSerializerOptions = provider.GetService>().Value.SerializerOptions; + Assert.Equal(minimalApiJsonOptions, swaggerGenSerializerOptions); } [Fact] public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Overridden() { - var expectedDummyConverter = new DummyConverter(); - var services = new ServiceCollection(); services.AddSingleton(); services.AddSwaggerGen(); - services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(new DummyConverter())); - services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter)); + services.ConfigureHttpJsonOptions(o => o.SerializerOptions.AllowTrailingCommas = true); services.AddSwaggerGenMvcJsonOptions(); using var provider = services.BuildServiceProvider(); - var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); - - Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); + var mvcJsonOptions = provider.GetService>().Value.JsonSerializerOptions; + var swaggerGenSerializerOptions = provider.GetService>().Value.SerializerOptions; + Assert.Equal(mvcJsonOptions, swaggerGenSerializerOptions); } [Fact] public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Not_Using_Minimal_Apis() { - var expectedDummyConverter = new DummyConverter(); - var services = new ServiceCollection(); services.AddSingleton(); services.AddSwaggerGen(); - services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter)); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.AllowTrailingCommas = true); using var provider = services.BuildServiceProvider(); - var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); - - Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); + var mvcJsonOptions = provider.GetService>().Value.JsonSerializerOptions; + var swaggerGenSerializerOptions = provider.GetService>().Value.SerializerOptions; + Assert.Equal(mvcJsonOptions, swaggerGenSerializerOptions); } [Fact] public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When_Configured() { - var expectedDummyConverter = new DummyConverter(); - var services = new ServiceCollection(); services.AddSingleton(); services.AddSwaggerGen(); - services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(expectedDummyConverter)); - services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter())); + services.ConfigureHttpJsonOptions(o => o.SerializerOptions.AllowTrailingCommas = true); + services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.AllowTrailingCommas = true); using var provider = services.BuildServiceProvider(); - var swaggerGenDummyConverter = provider.GetService>().Value.SerializerOptions.Converters.FirstOrDefault(); - - Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter); - } - - private sealed class DummyConverter : JsonConverter - { - public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } + var minimalApiJsonOptions = provider.GetService>().Value.SerializerOptions; + var swaggerGenSerializerOptions = provider.GetService>().Value.SerializerOptions; + Assert.Equal(minimalApiJsonOptions, swaggerGenSerializerOptions); } } diff --git a/test/WebSites/Basic/Startup.cs b/test/WebSites/Basic/Startup.cs index ba8ce11df0..2dad12c0b0 100644 --- a/test/WebSites/Basic/Startup.cs +++ b/test/WebSites/Basic/Startup.cs @@ -42,6 +42,8 @@ public void ConfigureServices(IServiceCollection services) c.EnableAnnotations(); }); + + services.AddSwaggerGenMinimalApisJsonOptions(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/test/WebSites/MinimalAppWithHostedServices/Program.cs b/test/WebSites/MinimalAppWithHostedServices/Program.cs index c0195e5b72..69d490769f 100644 --- a/test/WebSites/MinimalAppWithHostedServices/Program.cs +++ b/test/WebSites/MinimalAppWithHostedServices/Program.cs @@ -6,6 +6,8 @@ c.SwaggerDoc("v1", new() { Title = "MinimalApp", Version = "v1" }); }); +builder.Services.AddSwaggerGenMinimalApisJsonOptions(); + builder.Services.AddHostedService(); var app = builder.Build(); From b0baf6d3f77729ebf55aca7a81962371e4b72805 Mon Sep 17 00:00:00 2001 From: ItsVeryWindy Date: Tue, 10 Jun 2025 07:50:22 +0100 Subject: [PATCH 6/6] remove ?? operator --- .../DependencyInjection/ConfigureSwaggerGenJsonOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs index c924430036..5a84186f74 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGenJsonOptions.cs @@ -41,11 +41,11 @@ public void PostConfigure(string name, SwaggerGenJsonOptions options) * a last resort as this is an expensive operation. */ - var serializerOptions = _mvcJsonOptions.JsonSerializerOptions ?? JsonSerializerOptions.Default; + var serializerOptions = _mvcJsonOptions.JsonSerializerOptions; if (_minimalApiConfigureOptions.Any() || _minimalApiPostConfigureOptions.Any()) { - serializerOptions = _minimalApiJsonOptions.SerializerOptions ?? serializerOptions; + serializerOptions = _minimalApiJsonOptions.SerializerOptions; } options.SerializerOptions = serializerOptions;