From c1af13947bfc4cfdd164608bf1d39171bc4f8338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20Mikkel=C3=A4?= Date: Tue, 3 Dec 2024 11:53:26 +0100 Subject: [PATCH] Removed newtonsoft and made serialization agnostic via interface --- src/Demo/Demo.csproj | 2 + src/Demo/NewtonsoftSerializationProvider.cs | 32 +++++ src/Demo/Program.cs | 9 +- src/DemoOld/DemoOld.csproj | 2 + .../NewtonsoftSerializationProvider.cs | 33 +++++ src/DemoOld/Startup.cs | 110 +++++++-------- .../MultipartFromDataServicesExtension.cs | 45 +++--- .../FormDataJsonBinderProvider.cs | 25 +--- .../IMultipartJsonSerializationProvider.cs | 12 ++ .../Integrations/JsonModelBinder.cs | 129 +++++++----------- .../JsonMultipartFormDataOptions.cs | 5 - .../Integrations/JsonSerializerChoice.cs | 6 - .../MultiPartJsonOperationFilter.cs | 25 +--- ...etCore.JsonMultipartFormDataSupport.csproj | 1 - .../FormDataJsonBinderProviderTests.cs | 12 +- src/tests/UnitTests/JsonModelBinderTests.cs | 8 +- .../UnitTests/JsonSerializationProvider.cs | 32 +++++ .../NewtonsoftSerializationProvider.cs | 32 +++++ src/tests/UnitTests/UnitTests.csproj | 1 + 19 files changed, 298 insertions(+), 223 deletions(-) create mode 100644 src/Demo/NewtonsoftSerializationProvider.cs create mode 100644 src/DemoOld/NewtonsoftSerializationProvider.cs create mode 100644 src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/IMultipartJsonSerializationProvider.cs delete mode 100644 src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonMultipartFormDataOptions.cs delete mode 100644 src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonSerializerChoice.cs create mode 100644 src/tests/UnitTests/JsonSerializationProvider.cs create mode 100644 src/tests/UnitTests/NewtonsoftSerializationProvider.cs diff --git a/src/Demo/Demo.csproj b/src/Demo/Demo.csproj index 50e22bb..378c942 100644 --- a/src/Demo/Demo.csproj +++ b/src/Demo/Demo.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Demo/NewtonsoftSerializationProvider.cs b/src/Demo/NewtonsoftSerializationProvider.cs new file mode 100644 index 0000000..5aea5ca --- /dev/null +++ b/src/Demo/NewtonsoftSerializationProvider.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +namespace Demo; + +public class NewtonsoftSerializationProvider : IMultipartJsonSerializationProvider +{ + private readonly JsonSerializerSettings _serializerSettings; + + public NewtonsoftSerializationProvider(IOptions options) + { + _serializerSettings = options.Value.SerializerSettings; + } + + public NewtonsoftSerializationProvider() + { + _serializerSettings = new MvcNewtonsoftJsonOptions().SerializerSettings; + } + + public string Serialize(object value) + { + return JsonConvert.SerializeObject(value, _serializerSettings); + } + + public object Deserialize(ModelBindingContext bindingContext, string valueAsString) + { + return JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType, _serializerSettings)!; + } +} \ No newline at end of file diff --git a/src/Demo/Program.cs b/src/Demo/Program.cs index 1c26c79..ae29189 100644 --- a/src/Demo/Program.cs +++ b/src/Demo/Program.cs @@ -1,13 +1,14 @@ -using System.Xml; +using Demo; using Demo.Models.Products; using FluentValidation; using FluentValidation.AspNetCore; using MicroElements.Swashbuckle.FluentValidation.AspNetCore; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Extensions; -using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; -using Formatting=Newtonsoft.Json.Formatting; var builder = WebApplication.CreateBuilder(args); @@ -30,7 +31,7 @@ builder.Services.AddFluentValidationClientsideAdapters(); builder.Services.AddValidatorsFromAssemblyContaining(); ValidatorOptions.Global.LanguageManager.Enabled = false; -builder.Services.AddJsonMultipartFormDataSupport(JsonSerializerChoice.Newtonsoft); +builder.Services.AddJsonMultipartFormDataSupport(); builder.Services.AddSwaggerExamplesFromAssemblyOf(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/src/DemoOld/DemoOld.csproj b/src/DemoOld/DemoOld.csproj index a96dbad..65326de 100644 --- a/src/DemoOld/DemoOld.csproj +++ b/src/DemoOld/DemoOld.csproj @@ -12,6 +12,8 @@ + + diff --git a/src/DemoOld/NewtonsoftSerializationProvider.cs b/src/DemoOld/NewtonsoftSerializationProvider.cs new file mode 100644 index 0000000..3c09b44 --- /dev/null +++ b/src/DemoOld/NewtonsoftSerializationProvider.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +namespace DemoOld; + +public class NewtonsoftSerializationProvider : IMultipartJsonSerializationProvider +{ + private readonly JsonSerializerSettings _serializerSettings; + + public NewtonsoftSerializationProvider(IOptions options) + { + _serializerSettings = options.Value.SerializerSettings; + } + + public NewtonsoftSerializationProvider() + { + _serializerSettings = new MvcNewtonsoftJsonOptions().SerializerSettings; + } + + public string Serialize(object value) + { + return JsonConvert.SerializeObject(value, _serializerSettings); + } + + public object Deserialize(ModelBindingContext bindingContext, string valueAsString) + { + return JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType, _serializerSettings)!; + } +} \ No newline at end of file diff --git a/src/DemoOld/Startup.cs b/src/DemoOld/Startup.cs index 0586afb..7d63948 100644 --- a/src/DemoOld/Startup.cs +++ b/src/DemoOld/Startup.cs @@ -11,72 +11,72 @@ using Newtonsoft.Json.Converters; using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Extensions; -using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; -namespace DemoOld { - public class Startup { - public Startup(IConfiguration configuration) { - Configuration = configuration; - } +namespace DemoOld; + +public class Startup { + public Startup(IConfiguration configuration) { + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) { - // ===== System.Text.Json ===== - // services.AddControllers() - // .AddJsonOptions(options => { - // options.JsonSerializerOptions.WriteIndented = true; - // options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - // }); + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) { + // ===== System.Text.Json ===== + // services.AddControllers() + // .AddJsonOptions(options => { + // options.JsonSerializerOptions.WriteIndented = true; + // options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + // }); - // ===== JSON.Net- ===== - services.AddControllers() - .AddNewtonsoftJson(options => { - options.SerializerSettings.Converters.Add(new StringEnumConverter()); - options.SerializerSettings.Formatting = Formatting.Indented; - }) - .AddFluentValidation(f => { - f.RegisterValidatorsFromAssemblyContaining(); - // Important! Without this it won't work automatically - // vvv - f.ImplicitlyValidateChildProperties = true; + // ===== JSON.Net- ===== + services.AddControllers() + .AddNewtonsoftJson(options => { + options.SerializerSettings.Converters.Add(new StringEnumConverter()); + options.SerializerSettings.Formatting = Formatting.Indented; + }) + .AddFluentValidation(f => { + f.RegisterValidatorsFromAssemblyContaining(); + // Important! Without this it won't work automatically + // vvv + f.ImplicitlyValidateChildProperties = true; - f.LocalizationEnabled = false; - }); + f.LocalizationEnabled = false; + }); - services.AddJsonMultipartFormDataSupport(JsonSerializerChoice.Newtonsoft); - services.AddSwaggerExamplesFromAssemblyOf(); - services.AddSwaggerGen(o => { - o.SwaggerDoc("v1", new OpenApiInfo { - Title = "DemoOld", - Version = "v1" - }); + // services.AddSingleton>(sp => new(new(sp.GetRequiredService>()))); + services.AddJsonMultipartFormDataSupport(); + services.AddSwaggerExamplesFromAssemblyOf(); + services.AddSwaggerGen(o => { + o.SwaggerDoc("v1", new OpenApiInfo { + Title = "DemoOld", + Version = "v1" }); - services.AddFluentValidationRulesToSwagger(); - } + }); + services.AddFluentValidationRulesToSwagger(); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) { - app.UseDeveloperExceptionPage(); - } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + if (env.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + } - app.UseSwagger(); - app.UseSwaggerUI(o => { - o.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - o.RoutePrefix = string.Empty; - }); + app.UseSwagger(); + app.UseSwaggerUI(o => { + o.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + o.RoutePrefix = string.Empty; + }); - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthorization(); + app.UseAuthorization(); - app.UseEndpoints(endpoints => { - endpoints.MapControllers(); - }); - } + app.UseEndpoints(endpoints => { + endpoints.MapControllers(); + }); } -} +} \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Extensions/MultipartFromDataServicesExtension.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Extensions/MultipartFromDataServicesExtension.cs index d16cf34..7b68e7e 100644 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Extensions/MultipartFromDataServicesExtension.cs +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Extensions/MultipartFromDataServicesExtension.cs @@ -1,7 +1,4 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Extensions { @@ -12,27 +9,27 @@ public static class MultipartFromDataServicesExtension { /// /// Adds support for json in multipart/form-data requests /// - public static IServiceCollection AddJsonMultipartFormDataSupport(this IServiceCollection services, JsonSerializerChoice jsonSerializerChoice) { - JsonMultipartFormDataOptions.JsonSerializerChoice = jsonSerializerChoice; - - switch (jsonSerializerChoice) { - case JsonSerializerChoice.SystemText: - services.AddMvc(options => { - var jsonOptions = services.BuildServiceProvider().GetRequiredService>(); - options.ModelBinderProviders.Insert(0, new FormDataJsonBinderProvider(jsonOptions)); - }); - break; - case JsonSerializerChoice.Newtonsoft: - services.AddMvc(options => { - var jsonOptions = services.BuildServiceProvider().GetRequiredService>(); - options.ModelBinderProviders.Insert(0, new FormDataJsonBinderProvider(jsonOptions)); - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(jsonSerializerChoice), jsonSerializerChoice, null); - } - + public static IServiceCollection AddJsonMultipartFormDataSupport(this IServiceCollection services) + where TMultipartJsonSerializationProvider : class, IMultipartJsonSerializationProvider + { + services.AddSingleton(); + return AddJsonMultipartFormDataSupport(services); + } + + /// + /// Adds support for json in multipart/form-data requests + /// + public static IServiceCollection AddJsonMultipartFormDataSupport(this IServiceCollection services) + { + var serviceProvider = services.BuildServiceProvider(); + + var serializationProvider = serviceProvider.GetRequiredService(); + + services.AddMvc(options => + { + options.ModelBinderProviders.Insert(0, new FormDataJsonBinderProvider(new(serializationProvider))); + }); services.AddSwaggerGen(options => { options.OperationFilter(); diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/FormDataJsonBinderProvider.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/FormDataJsonBinderProvider.cs index 59e89b9..a8f49ea 100644 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/FormDataJsonBinderProvider.cs +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/FormDataJsonBinderProvider.cs @@ -1,9 +1,7 @@ using System; using System.Reflection; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Attributes; namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { @@ -11,18 +9,13 @@ namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { /// Looks for field with and use for binder. /// public class FormDataJsonBinderProvider : IModelBinderProvider { - private readonly IOptions _jsonOptions; - private readonly IOptions _newtonSoftJsonOptions; + readonly JsonModelBinder _jsonModelBinder; - public FormDataJsonBinderProvider(IOptions jsonOptions) { - _jsonOptions = jsonOptions; + public FormDataJsonBinderProvider(JsonModelBinder jsonModelBinder) + { + _jsonModelBinder = jsonModelBinder; } - - - public FormDataJsonBinderProvider(IOptions newtonSoftJsonOptions) { - _newtonSoftJsonOptions = newtonSoftJsonOptions; - } - + /// public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); @@ -42,13 +35,7 @@ public IModelBinder GetBinder(ModelBinderProviderContext context) { // Do not use this provider if this property does not have the From attribute if (propInfo.GetCustomAttribute() == null) return null; - // All criteria met; use the FormDataJsonBinder - if (_jsonOptions != null) - return new JsonModelBinder(_jsonOptions); - else if (_newtonSoftJsonOptions != null) - return new JsonModelBinder(_newtonSoftJsonOptions); - else - return new JsonModelBinder(); + return _jsonModelBinder; } } } \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/IMultipartJsonSerializationProvider.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/IMultipartJsonSerializationProvider.cs new file mode 100644 index 0000000..a76c185 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/IMultipartJsonSerializationProvider.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +/// +/// Serialization provider for and . +/// +public interface IMultipartJsonSerializationProvider +{ + public object Deserialize(ModelBindingContext bindingContext, string valueAsString); + public string Serialize(object value); +} \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonModelBinder.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonModelBinder.cs index 24a0c77..68fca28 100644 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonModelBinder.cs +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonModelBinder.cs @@ -1,100 +1,69 @@ using System; using System.IO; -using System.Text.Json; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using JsonSerializer = System.Text.Json.JsonSerializer; -namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { - /// - /// Binds field from JSON string. - /// - public class JsonModelBinder : IModelBinder { - private readonly IOptions _jsonOptions; - private readonly IOptions _newtonsoftJsonOptions; - - public JsonModelBinder() { } - - public JsonModelBinder(IOptions jsonOptions) { - _jsonOptions = jsonOptions; +namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +/// +/// Binds field from JSON string. +/// +public class JsonModelBinder : IModelBinder { + readonly IMultipartJsonSerializationProvider _serializationProvider; + + public JsonModelBinder(IMultipartJsonSerializationProvider serializationProvider) { + _serializationProvider = serializationProvider; + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) { + if (bindingContext == null) { + throw new ArgumentNullException(nameof(bindingContext)); } - public JsonModelBinder(IOptions newtonsoftJsonOptions) { - _newtonsoftJsonOptions = newtonsoftJsonOptions; + string modelBindingKey; + if (bindingContext.IsTopLevelObject) { + modelBindingKey = bindingContext.BinderModelName; + } + else { + modelBindingKey = bindingContext.ModelName; } - /// - public async Task BindModelAsync(ModelBindingContext bindingContext) { - if (bindingContext == null) { - throw new ArgumentNullException(nameof(bindingContext)); - } - - string modelBindingKey; - if (bindingContext.IsTopLevelObject) { - modelBindingKey = bindingContext.BinderModelName; - } - else { - modelBindingKey = bindingContext.ModelName; - } - - // Check the value sent in - var valueProviderResult = await this.GetValueProvidedResult(bindingContext); - if (valueProviderResult != ValueProviderResult.None) { - bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + // Check the value sent in + var valueProviderResult = await this.GetValueProvidedResult(bindingContext); + if (valueProviderResult != ValueProviderResult.None) { + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); - // Attempt to convert the input value - var valueAsString = valueProviderResult.FirstValue; + // Attempt to convert the input value + var valueAsString = valueProviderResult.FirstValue; - try { - object result; - if (_jsonOptions != null) { - result = DeserializeUsingSystemSerializer(bindingContext, valueAsString); - } - else if (_newtonsoftJsonOptions != null) { - result = DeserializeUsingJsonNet(bindingContext, valueAsString); - } - else { - result = DeserializeUsingSystemSerializer(bindingContext, valueAsString); - } + try { + var result = _serializationProvider.Deserialize(bindingContext, valueAsString); - bindingContext.Result = ModelBindingResult.Success(result); - } - catch (Exception e) { - bindingContext.ModelState.AddModelError(modelBindingKey ?? string.Empty, e.Message); - } + bindingContext.Result = ModelBindingResult.Success(result); + } + catch (Exception e) { + bindingContext.ModelState.AddModelError(modelBindingKey ?? string.Empty, e.Message); } - } - - private object DeserializeUsingSystemSerializer(ModelBindingContext bindingContext, string valueAsString) { - return JsonSerializer.Deserialize(valueAsString, bindingContext.ModelType, - _jsonOptions?.Value?.JsonSerializerOptions ?? new JsonSerializerOptions()); } + } - private object DeserializeUsingJsonNet(ModelBindingContext bindingContext, string valueAsString) { - return JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType, - _newtonsoftJsonOptions.Value.SerializerSettings); + private async Task GetValueProvidedResult(ModelBindingContext bindingContext) { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult != ValueProviderResult.None) { + return valueProviderResult; } - private async Task GetValueProvidedResult(ModelBindingContext bindingContext) { - var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); - if (valueProviderResult != ValueProviderResult.None) { - return valueProviderResult; - } - - var file = bindingContext.HttpContext.Request.Form.Files.GetFile(bindingContext.ModelName); - if (file is null) { - return valueProviderResult; - } + var file = bindingContext.HttpContext.Request.Form.Files.GetFile(bindingContext.ModelName); + if (file is null) { + return valueProviderResult; + } - await using var stream = file.OpenReadStream(); - using var reader = new StreamReader(stream); - var json = await reader.ReadToEndAsync(); - valueProviderResult = new ValueProviderResult(json); + await using var stream = file.OpenReadStream(); + using var reader = new StreamReader(stream); + var json = await reader.ReadToEndAsync(); + valueProviderResult = new ValueProviderResult(json); - return valueProviderResult; - } - } + return valueProviderResult; + } } \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonMultipartFormDataOptions.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonMultipartFormDataOptions.cs deleted file mode 100644 index c2798c3..0000000 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonMultipartFormDataOptions.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { - internal static class JsonMultipartFormDataOptions { - internal static JsonSerializerChoice JsonSerializerChoice = JsonSerializerChoice.SystemText; - } -} \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonSerializerChoice.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonSerializerChoice.cs deleted file mode 100644 index 0c26dbd..0000000 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/JsonSerializerChoice.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { - public enum JsonSerializerChoice { - SystemText, - Newtonsoft - } -} \ No newline at end of file diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/MultiPartJsonOperationFilter.cs b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/MultiPartJsonOperationFilter.cs index a590681..b818ca5 100644 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/MultiPartJsonOperationFilter.cs +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Integrations/MultiPartJsonOperationFilter.cs @@ -2,17 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Newtonsoft.Json; using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Attributes; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Extensions; using Swashbuckle.AspNetCore.SwaggerGen; -using JsonSerializer = System.Text.Json.JsonSerializer; namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations { @@ -22,21 +20,17 @@ namespace Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations public class MultiPartJsonOperationFilter : IOperationFilter { private readonly IServiceProvider _serviceProvider; - private readonly IOptions _jsonOptions; - private readonly IOptions _newtonsoftJsonOption; private readonly IOptions _generatorOptions; + private readonly IMultipartJsonSerializationProvider _serializationResolver; /// /// Creates /// - public MultiPartJsonOperationFilter(IServiceProvider serviceProvider, IOptions jsonOptions, - IOptions newtonsoftJsonOption, - IOptions generatorOptions) + public MultiPartJsonOperationFilter(IServiceProvider serviceProvider, IOptions generatorOptions) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _jsonOptions = jsonOptions; - _newtonsoftJsonOption = newtonsoftJsonOption; _generatorOptions = generatorOptions; + _serializationResolver = serviceProvider.GetRequiredService(); } /// @@ -146,20 +140,13 @@ private static void AddEncoding(OpenApiMediaType mediaType, PropertyInfo propert Explode = false }); } - + private void AddExample(PropertyInfo propertyInfo, OpenApiSchema openApiSchema) { var example = GetExampleFor(propertyInfo.PropertyType); // Example do not exist. Use default. if (example == null) return; - string json; - - if (JsonMultipartFormDataOptions.JsonSerializerChoice == JsonSerializerChoice.SystemText) - json = JsonSerializer.Serialize(example, _jsonOptions.Value.JsonSerializerOptions); - else if (JsonMultipartFormDataOptions.JsonSerializerChoice == JsonSerializerChoice.Newtonsoft) - json = JsonConvert.SerializeObject(example, _newtonsoftJsonOption.Value.SerializerSettings); - else - json = JsonSerializer.Serialize(example); + var json = _serializationResolver.Serialize(example); openApiSchema.Example = new OpenApiString(json); } diff --git a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.csproj b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.csproj index 913493c..5b0da42 100644 --- a/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.csproj +++ b/src/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport/Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.csproj @@ -18,7 +18,6 @@ - diff --git a/src/tests/UnitTests/FormDataJsonBinderProviderTests.cs b/src/tests/UnitTests/FormDataJsonBinderProviderTests.cs index 737f79f..eeb81ed 100644 --- a/src/tests/UnitTests/FormDataJsonBinderProviderTests.cs +++ b/src/tests/UnitTests/FormDataJsonBinderProviderTests.cs @@ -12,7 +12,7 @@ public class FormDataJsonBinderProviderTests { public void GetBinder_ContextIsNull_ShouldThrowException() { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); // Act var action = () => sut.GetBinder(null!); // Assert @@ -26,7 +26,7 @@ public void GetBinder_ContextIsNull_ShouldThrowException() { public void GetBinder_SimpleType_ShouldReturnNull(Type type) { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); var context = new TestModelBinderProviderContext(new TestModelMetadata(ModelMetadataIdentity.ForType(type))); // Act var result = sut.GetBinder(context); @@ -38,7 +38,7 @@ public void GetBinder_SimpleType_ShouldReturnNull(Type type) { public void GetBinder_NotProperty_ShouldReturnNull() { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); var context = new TestModelBinderProviderContext( new TestModelMetadata(ModelMetadataIdentity.ForType(typeof(TestTypeNoProperty)))); @@ -52,7 +52,7 @@ public void GetBinder_NotProperty_ShouldReturnNull() { public void GetBinder_IFormFileProperty_ShouldReturnNull() { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); var context = TestModelBinderProviderContext.ForProperty(typeof(TestTypePropertyIFromFile), nameof(TestTypePropertyIFromFile.Test)); // Act @@ -65,7 +65,7 @@ public void GetBinder_IFormFileProperty_ShouldReturnNull() { public void GetBinder_PropertyWithoutFromJsonAttribute_ShouldReturnNull() { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); var context = TestModelBinderProviderContext.ForProperty(typeof(TestTypeNoAttribute), nameof(TestTypeNoAttribute.Test)); // Act @@ -78,7 +78,7 @@ public void GetBinder_PropertyWithoutFromJsonAttribute_ShouldReturnNull() { public void GetBinder_ShouldReturnJsonBinder() { // Arrange var options = Substitute.For>(); - var sut = new FormDataJsonBinderProvider(options); + var sut = new FormDataJsonBinderProvider(new(new JsonSerializationProvider(options))); var context = TestModelBinderProviderContext.ForProperty(typeof(TestTypeContainer), nameof(TestTypeContainer.Test)); // Act var result = sut.GetBinder(context); diff --git a/src/tests/UnitTests/JsonModelBinderTests.cs b/src/tests/UnitTests/JsonModelBinderTests.cs index ba94a3d..eb633ef 100644 --- a/src/tests/UnitTests/JsonModelBinderTests.cs +++ b/src/tests/UnitTests/JsonModelBinderTests.cs @@ -1,8 +1,8 @@ -using System.Text.Json; -using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Primitives; using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; using UnitTests.TestData.Types; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace UnitTests; @@ -10,7 +10,7 @@ public class JsonModelBinderTests { [Test] public void BindModelAsync_NullContext_ShouldReturnNull() { // Arrange - var sut = new JsonModelBinder(); + var sut = new JsonModelBinder(new NewtonsoftSerializationProvider()); // Act var action = async () => await sut.BindModelAsync(null!); // Assert @@ -20,7 +20,7 @@ public void BindModelAsync_NullContext_ShouldReturnNull() { [Test] public async Task BindModelAsync_ShouldBindData() { // Arrange - var sut = new JsonModelBinder(); + var sut = new JsonModelBinder(new NewtonsoftSerializationProvider()); var testType = new TestType { Id = 1, Text = Guid.NewGuid().ToString() diff --git a/src/tests/UnitTests/JsonSerializationProvider.cs b/src/tests/UnitTests/JsonSerializationProvider.cs new file mode 100644 index 0000000..2de7287 --- /dev/null +++ b/src/tests/UnitTests/JsonSerializationProvider.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +namespace UnitTests; + +public class JsonSerializationProvider : IMultipartJsonSerializationProvider +{ + private readonly JsonSerializerOptions _serializerOptions; + + public JsonSerializationProvider(IOptions options) + { + _serializerOptions = options.Value?.JsonSerializerOptions ?? new JsonOptions().JsonSerializerOptions; + } + + public JsonSerializationProvider() + { + _serializerOptions = new JsonOptions().JsonSerializerOptions; + } + + public string Serialize(object value) + { + return JsonSerializer.Serialize(value, _serializerOptions); + } + + public object Deserialize(ModelBindingContext bindingContext, string valueAsString) + { + return JsonSerializer.Deserialize(valueAsString, bindingContext.ModelType, _serializerOptions)!; + } +} \ No newline at end of file diff --git a/src/tests/UnitTests/NewtonsoftSerializationProvider.cs b/src/tests/UnitTests/NewtonsoftSerializationProvider.cs new file mode 100644 index 0000000..7e3278d --- /dev/null +++ b/src/tests/UnitTests/NewtonsoftSerializationProvider.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.JsonMultipartFormDataSupport.Integrations; + +namespace UnitTests; + +public class NewtonsoftSerializationProvider : IMultipartJsonSerializationProvider +{ + private readonly JsonSerializerSettings _serializerSettings; + + public NewtonsoftSerializationProvider(IOptions options) + { + _serializerSettings = options.Value.SerializerSettings; + } + + public NewtonsoftSerializationProvider() + { + _serializerSettings = new MvcNewtonsoftJsonOptions().SerializerSettings; + } + + public string Serialize(object value) + { + return JsonConvert.SerializeObject(value, _serializerSettings); + } + + public object Deserialize(ModelBindingContext bindingContext, string valueAsString) + { + return JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType, _serializerSettings)!; + } +} \ No newline at end of file diff --git a/src/tests/UnitTests/UnitTests.csproj b/src/tests/UnitTests/UnitTests.csproj index 6e4b5a3..80f40db 100644 --- a/src/tests/UnitTests/UnitTests.csproj +++ b/src/tests/UnitTests/UnitTests.csproj @@ -9,6 +9,7 @@ +