Skip to content

Commit b36b5b3

Browse files
committed
refactor to use an options class and add extra tests for json options override methods
1 parent 72ef198 commit b36b5b3

File tree

7 files changed

+218
-74
lines changed

7 files changed

+218
-74
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#if NET
2+
using Microsoft.AspNetCore.Http.Json;
3+
using Microsoft.Extensions.Options;
4+
5+
namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
6+
7+
internal class ConfigureMinimalApiSwaggerGenJsonOptions(IOptions<JsonOptions> jsonOptions) : IConfigureOptions<SwaggerGenJsonOptions>
8+
{
9+
public void Configure(SwaggerGenJsonOptions options)
10+
{
11+
options.SerializerOptions = jsonOptions.Value.SerializerOptions;
12+
}
13+
}
14+
#endif
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#if NET
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Options;
4+
5+
namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
6+
7+
internal class ConfigureMvcSwaggerGenJsonOptions(IOptions<JsonOptions> jsonOptions) : IConfigureOptions<SwaggerGenJsonOptions>
8+
{
9+
public void Configure(SwaggerGenJsonOptions options)
10+
{
11+
options.SerializerOptions = jsonOptions.Value.JsonSerializerOptions;
12+
}
13+
}
14+
#endif
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Text.Json;
2+
using Microsoft.Extensions.Options;
3+
4+
namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
5+
6+
internal class ConfigureSwaggerGenJsonOptions : IPostConfigureOptions<SwaggerGenJsonOptions>
7+
{
8+
#if NET
9+
private readonly IEnumerable<IConfigureOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>> _minimalApiConfigureOptions;
10+
private readonly IEnumerable<IPostConfigureOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>> _minimalApiPostConfigureOptions;
11+
private readonly Microsoft.AspNetCore.Http.Json.JsonOptions _minimalApiJsonOptions;
12+
private readonly Microsoft.AspNetCore.Mvc.JsonOptions _mvcJsonOptions;
13+
14+
public ConfigureSwaggerGenJsonOptions(
15+
IEnumerable<IConfigureOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>> minimalApiConfigureOptions,
16+
IEnumerable<IPostConfigureOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>> minimalApiPostConfigureOptions,
17+
IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions> minimalApiJsonOptions,
18+
IOptions<Microsoft.AspNetCore.Mvc.JsonOptions> mvcJsonOptions
19+
)
20+
{
21+
_minimalApiConfigureOptions = minimalApiConfigureOptions;
22+
_minimalApiPostConfigureOptions = minimalApiPostConfigureOptions;
23+
_minimalApiJsonOptions = minimalApiJsonOptions.Value;
24+
_mvcJsonOptions = mvcJsonOptions.Value;
25+
}
26+
#endif
27+
28+
public void PostConfigure(string name, SwaggerGenJsonOptions options)
29+
{
30+
if (options.SerializerOptions != null)
31+
{
32+
return;
33+
}
34+
35+
/*
36+
* There is no surefire way to do this.
37+
* However, both JsonOptions are defaulted in the same way.
38+
* If neither is configured it makes no difference which one is chosen.
39+
* If both are configured, then we just need to make a choice.
40+
* As Minimal APIs are newer if someone is configuring them
41+
* it's probably more likely that is what they're using.
42+
*
43+
* If either JsonOptions is null we will try to create a new instance as
44+
* a last resort as this is an expensive operation.
45+
*/
46+
47+
#if NET
48+
var serializerOptions = _mvcJsonOptions.JsonSerializerOptions ?? JsonSerializerOptions.Default;
49+
50+
if (HasConfiguredMinimalApiJsonOptions())
51+
{
52+
serializerOptions = _minimalApiJsonOptions.SerializerOptions ?? serializerOptions;
53+
}
54+
55+
options.SerializerOptions = serializerOptions;
56+
#else
57+
options.SerializerOptions = new JsonSerializerOptions();
58+
#endif
59+
}
60+
61+
#if NET
62+
private bool HasConfiguredMinimalApiJsonOptions()
63+
{
64+
return _minimalApiConfigureOptions.Any() || _minimalApiPostConfigureOptions.Any();
65+
}
66+
#endif
67+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Text.Json;
2+
3+
namespace Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
4+
5+
public class SwaggerGenJsonOptions
6+
{
7+
public JsonSerializerOptions SerializerOptions { get; set; }
8+
}
Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using System.Text.Json;
2-
using Microsoft.Extensions.ApiDescriptions;
1+
using Microsoft.Extensions.ApiDescriptions;
32
using Microsoft.Extensions.DependencyInjection.Extensions;
43
using Microsoft.Extensions.Options;
54
using Swashbuckle.AspNetCore.Swagger;
65
using Swashbuckle.AspNetCore.SwaggerGen;
6+
using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
77

88
namespace Microsoft.Extensions.DependencyInjection;
99

@@ -29,10 +29,10 @@ public static IServiceCollection AddSwaggerGen(
2929
services.TryAddTransient(s => s.GetRequiredService<IOptions<SwaggerGeneratorOptions>>().Value);
3030
services.TryAddTransient<ISchemaGenerator, SchemaGenerator>();
3131
services.TryAddTransient(s => s.GetRequiredService<IOptions<SchemaGeneratorOptions>>().Value);
32-
services.AddSingleton<JsonSerializerOptionsProvider>();
32+
services.ConfigureOptions<ConfigureSwaggerGenJsonOptions>();
3333
services.TryAddSingleton<ISerializerDataContractResolver>(s =>
3434
{
35-
var serializerOptions = s.GetRequiredService<JsonSerializerOptionsProvider>().Options;
35+
var serializerOptions = s.GetRequiredService<IOptions<SwaggerGenJsonOptions>>().Value.SerializerOptions;
3636
return new JsonSerializerDataContractResolver(serializerOptions);
3737
});
3838

@@ -47,24 +47,12 @@ public static IServiceCollection AddSwaggerGen(
4747
#if NET
4848
public static IServiceCollection AddSwaggerGenMinimalApisJsonOptions(this IServiceCollection services)
4949
{
50-
return services.Replace(
51-
ServiceDescriptor.Transient<ISerializerDataContractResolver>((s) =>
52-
{
53-
var options = s.GetRequiredService<IOptionsSnapshot<AspNetCore.Http.Json.JsonOptions>>().Value.SerializerOptions;
54-
55-
return new JsonSerializerDataContractResolver(options);
56-
}));
50+
return services.ConfigureOptions<ConfigureMinimalApiSwaggerGenJsonOptions>();
5751
}
5852

5953
public static IServiceCollection AddSwaggerGenMvcJsonOptions(this IServiceCollection services)
6054
{
61-
return services.Replace(
62-
ServiceDescriptor.Transient<ISerializerDataContractResolver>((s) =>
63-
{
64-
var options = s.GetRequiredService<IOptionsSnapshot<AspNetCore.Mvc.JsonOptions>>().Value.JsonSerializerOptions;
65-
66-
return new JsonSerializerDataContractResolver(options);
67-
}));
55+
return services.ConfigureOptions<ConfigureMvcSwaggerGenJsonOptions>();
6856
}
6957
#endif
7058

@@ -74,60 +62,4 @@ public static void ConfigureSwaggerGen(
7462
{
7563
services.Configure(setupAction);
7664
}
77-
78-
private sealed class JsonSerializerOptionsProvider
79-
{
80-
private JsonSerializerOptions _options;
81-
#if NET
82-
private readonly IServiceProvider _serviceProvider;
83-
84-
public JsonSerializerOptionsProvider(IServiceProvider serviceProvider)
85-
{
86-
_serviceProvider = serviceProvider;
87-
}
88-
#endif
89-
90-
public JsonSerializerOptions Options => _options ??= ResolveOptions();
91-
92-
private JsonSerializerOptions ResolveOptions()
93-
{
94-
JsonSerializerOptions serializerOptions;
95-
96-
/*
97-
* There is no surefire way to do this.
98-
* However, both JsonOptions are defaulted in the same way.
99-
* If neither is configured it makes no difference which one is chosen.
100-
* If both are configured, then we just need to make a choice.
101-
* As Minimal APIs are newer if someone is configuring them
102-
* it's probably more likely that is what they're using.
103-
*
104-
* If either JsonOptions is null we will try to create a new instance as
105-
* a last resort as this is an expensive operation.
106-
*/
107-
#if NET
108-
serializerOptions =
109-
_serviceProvider.GetService<IOptions<AspNetCore.Http.Json.JsonOptions>>()?.Value?.SerializerOptions
110-
?? JsonSerializerOptions.Default;
111-
112-
if (HasConfiguredMinimalApiJsonOptions())
113-
{
114-
serializerOptions ??= _serviceProvider.GetService<IOptions<AspNetCore.Http.Json.JsonOptions>>()?.Value?.SerializerOptions;
115-
}
116-
#else
117-
serializerOptions = new JsonSerializerOptions();
118-
#endif
119-
120-
return serializerOptions;
121-
}
122-
123-
#if NET
124-
private bool HasConfiguredMinimalApiJsonOptions()
125-
{
126-
if (_serviceProvider.GetService<IEnumerable<IConfigureOptions<AspNetCore.Http.Json.JsonOptions>>>().Any())
127-
return true;
128-
129-
return _serviceProvider.GetService<IEnumerable<IPostConfigureOptions<AspNetCore.Http.Json.JsonOptions>>>().Any();
130-
}
131-
#endif
132-
}
13365
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions
2+
Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SerializerOptions.get -> System.Text.Json.JsonSerializerOptions
3+
Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SerializerOptions.set -> void
4+
Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection.SwaggerGenJsonOptions.SwaggerGenJsonOptions() -> void
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.FileProviders;
6+
using Microsoft.Extensions.Options;
7+
using Swashbuckle.AspNetCore.SwaggerGen.DependencyInjection;
8+
9+
namespace Swashbuckle.AspNetCore.SwaggerGen.Test;
10+
11+
public class SwaggerGenJsonOptionsTests
12+
{
13+
[Fact]
14+
public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When_Overridden()
15+
{
16+
var services = new ServiceCollection();
17+
services.AddSingleton<IWebHostEnvironment, DummyHostEnvironment>();
18+
services.AddSwaggerGen();
19+
services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter()));
20+
services.AddSwaggerGenMinimalApisJsonOptions();
21+
22+
using var provider = services.BuildServiceProvider();
23+
24+
var swaggerGenConverters = provider.GetService<IOptions<SwaggerGenJsonOptions>>().Value.SerializerOptions.Converters;
25+
26+
Assert.Empty(swaggerGenConverters);
27+
}
28+
29+
[Fact]
30+
public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Overridden()
31+
{
32+
var expectedDummyConverter = new DummyConverter();
33+
34+
var services = new ServiceCollection();
35+
services.AddSingleton<IWebHostEnvironment, DummyHostEnvironment>();
36+
services.AddSwaggerGen();
37+
services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(new DummyConverter()));
38+
services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter));
39+
services.AddSwaggerGenMvcJsonOptions();
40+
41+
using var provider = services.BuildServiceProvider();
42+
43+
var swaggerGenDummyConverter = provider.GetService<IOptions<SwaggerGenJsonOptions>>().Value.SerializerOptions.Converters.FirstOrDefault();
44+
45+
Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter);
46+
}
47+
48+
[Fact]
49+
public static void Ensure_SwaggerGenJsonOptions_Uses_Mvc_JsonOptions_When_Not_Using_Minimal_Apis()
50+
{
51+
var expectedDummyConverter = new DummyConverter();
52+
53+
var services = new ServiceCollection();
54+
services.AddSingleton<IWebHostEnvironment, DummyHostEnvironment>();
55+
services.AddSwaggerGen();
56+
services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(expectedDummyConverter));
57+
58+
using var provider = services.BuildServiceProvider();
59+
60+
var swaggerGenDummyConverter = provider.GetService<IOptions<SwaggerGenJsonOptions>>().Value.SerializerOptions.Converters.FirstOrDefault();
61+
62+
Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter);
63+
}
64+
65+
[Fact]
66+
public static void Ensure_SwaggerGenJsonOptions_Uses_MinimalApi_JsonOptions_When_Configured()
67+
{
68+
var expectedDummyConverter = new DummyConverter();
69+
70+
var services = new ServiceCollection();
71+
services.AddSingleton<IWebHostEnvironment, DummyHostEnvironment>();
72+
services.AddSwaggerGen();
73+
services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(expectedDummyConverter));
74+
services.AddMvcCore().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new DummyConverter()));
75+
76+
using var provider = services.BuildServiceProvider();
77+
78+
var swaggerGenDummyConverter = provider.GetService<IOptions<SwaggerGenJsonOptions>>().Value.SerializerOptions.Converters.FirstOrDefault();
79+
80+
Assert.Equal(expectedDummyConverter, swaggerGenDummyConverter);
81+
}
82+
83+
private sealed class DummyHostEnvironment : IWebHostEnvironment
84+
{
85+
public string WebRootPath { get; set; }
86+
public IFileProvider WebRootFileProvider { get; set; }
87+
public string ApplicationName { get; set; }
88+
public IFileProvider ContentRootFileProvider { get; set; }
89+
public string ContentRootPath { get; set; }
90+
public string EnvironmentName { get; set; }
91+
}
92+
93+
private sealed class DummyConverter : JsonConverter<object>
94+
{
95+
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
96+
{
97+
throw new NotImplementedException();
98+
}
99+
100+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
101+
{
102+
throw new NotImplementedException();
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)