Swashbuckle.WebApi icon indicating copy to clipboard operation
Swashbuckle.WebApi copied to clipboard

Swashbuckle.AspNetCore.Filters throws an exception when trying to use System.Text.Json.Serialization.JsonStringEnumConverter

Open jmevel opened this issue 5 years ago • 6 comments

VERSION:

Swashbuckle.AspNetCore Version="5.0.0-rc5" Swashbuckle.AspNetCore.Filters Version="5.0.0-rc9" Swashbuckle.AspNetCore.Swagger Version="5.0.0-rc5" Swashbuckle.AspNetCore.SwaggerGen Version="5.0.0-rc5" Swashbuckle.AspNetCore.SwaggerUi Version="5.0.0-rc5"

STEPS TO REPRODUCE:

I'm sorry I wanted to create a minimal sample repository so you guys can just clone and check but at my company for security reasons I can't push anything on Github.

Create an ASP.NET 3.1 project.

Here is the Startup.cs

public class Startup
{
	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}

	public IConfiguration Configuration { get; }

	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers();

		services.AddVersionedSwagger();
	}

	public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
	{
		app.UseRouting();

		app.UseVersionedSwagger(provider);

		app.UseEndpoints(endpoints =>
		{
			endpoints.MapControllers();
		});
	}
}

A VersionedSwaggerExtensions.cs file declaring some extension methods

public static class VersionedSwaggerExtensions
    {
        public static IServiceCollection AddVersionedSwagger(this IServiceCollection services)
        {
            services.AddApiVersioning(o =>
            {
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(1, 0);
            });

            services.AddVersionedApiExplorer(o => o.GroupNameFormat = "'V'VVV");

            var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

            services.AddSwaggerGen(options =>
            {
                var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

                foreach (var apiVersion in provider.ApiVersionDescriptions)
                {
                    ConfigureVersionedDescription(options, apiVersion);
                }
            });

            services.AddSwaggerExamplesFromAssemblyOf<FooRequestExampleProvider>();

            return services;
        }

        private static void ConfigureVersionedDescription(SwaggerGenOptions options, ApiVersionDescription apiVersion)
        {
            var descriptions = new Dictionary<string, string>
                {
                    { "1.0", "Version 1.0 of my test API" }
                };

            var apiVersionName = apiVersion.ApiVersion.ToString();
            options.SwaggerDoc(apiVersion.GroupName,
                new OpenApiInfo()
                {
                    Title = "Just a test API",
                    Contact = new OpenApiContact()
                    {
                        Name = "Jérôme MEVEL"
                    },
                    Version = apiVersionName,
                    Description = descriptions[apiVersionName]
                });
        }

        public static IApplicationBuilder UseVersionedSwagger(this IApplicationBuilder app, IApiVersionDescriptionProvider provider)
        {
            app.UseSwagger();

            app.UseSwaggerUI(options =>
            {
                foreach (var description in provider.ApiVersionDescriptions)
                {
                    options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
                }
            });

            return app;
        }

And finally a FooController

[ApiController]
[Route("[controller]")]
public class FooController : ControllerBase
{
	/// <summary>
	/// Just a test Method
	/// </summary>
	/// <param name="fooParams">My Params</param>
	/// <returns></returns>
	[HttpGet("Foo")]
	[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FooResult))]
	[SwaggerRequestExample(requestType: typeof(FooQuery), examplesProviderType: typeof(FooRequestExampleProvider), jsonConverter: typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
	[SwaggerResponseExample(statusCode: 200, examplesProviderType: typeof(FooResponseExampleProvider), jsonConverter: typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
	public FooResult Foo([FromQuery]FooQuery fooParams)
	{
		return new FooResult()
		{
			Result = "Success"
		};
	}
}

EXPECTED RESULT:

Swagger generates the API documentation along with query examples

ACTUAL RESULT:

options.SwaggerEndpoint throws the following exception (didn't include the full stacktrace)

System.InvalidCastException HResult=0x80004002 Message=Unable to cast object of type 'System.Text.Json.Serialization.JsonStringEnumConverter' to type 'Newtonsoft.Json.JsonConverter'. Source=Swashbuckle.AspNetCore.Filters StackTrace: at Swashbuckle.AspNetCore.Filters.SwaggerRequestExampleAttribute..ctor(Type requestType, Type examplesProviderType, Type contractResolver, Type jsonConverter)

ADDITIONAL DETAILS

Here is the StackOverflow question I created

jmevel avatar Jan 02 '20 15:01 jmevel

see 'NewtonSoft.JSON Support' here: https://dotnetcoretutorials.com/2020/01/31/using-swagger-in-net-core-3/

Install-Package Swashbuckle.AspNetCore.Newtonsoft ... services.AddSwaggerGenNewtonsoftSupport();

cliron1 avatar Jun 11 '20 07:06 cliron1

see 'NewtonSoft.JSON Support' here: https://dotnetcoretutorials.com/2020/01/31/using-swagger-in-net-core-3/

Install-Package Swashbuckle.AspNetCore.Newtonsoft ... services.AddSwaggerGenNewtonsoftSupport();

I think you misunderstood my issue, I don't want to use NewtonSoft, I'm trying to use Swashbuckle along with the new System.Text.Json package.

The problem is that it seems Swashbuckle is tightly using Newtonsoft.Json.JsonConverter and there's no way to use System.Text.Json.Serialization.JsonStringEnumConverter instead.

Internally Swashbuckle is trying to convert the JsonStringEnumConverter type as a Newtonsoft.Json.JsonConverter and obviously fails

jmevel avatar Jun 11 '20 09:06 jmevel

Yes I am seeing the same thing, i.e. that the attribute for System.Text.Json (not Newtonsoft) with [JsonConverter(typeof(JsonStringEnumConverter))] is not taken into account

Update: added this to Startup.cs and it is working as expected, fyi @jmevel :

        services.AddControllersWithViews() 
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); // this was the key
            });

mzhukovs avatar Dec 02 '20 14:12 mzhukovs

Yes I am seeing the same thing, i.e. that the attribute for System.Text.Json (not Newtonsoft) with [JsonConverter(typeof(JsonStringEnumConverter))] is not taken into account

Update: added this to Startup.cs and it is working as expected, fyi @jmevel :

        services.AddControllersWithViews() 
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); // this was the key
            });

Thanks for your answer! I think at the time I wrote this question I didn't know yet how to set a default JSON converter. However I think it's weird we have to do that. Isn't System.Text.Json already the default JSON library since .NET Core 3.0?

Swashbuckle is itself shifting to this new library so it should be the default one in any Swashbuckle Nuget package

jmevel avatar Dec 13 '20 11:12 jmevel

Hi, I'm the author of https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters

  1. Is this still an issue with the latest version of the above NuGet?
  2. If so - then it should be logged at the above GitHub project and not here. This issue here can be closed.

mattfrear avatar Dec 21 '22 20:12 mattfrear

I'm closing this issue as @mattfrear suggested because I created this ticket on the wrong repository. Sorry but I have no idea if this is still an issue as I'm not working with Swashbuckle anymore (not in the company anymore).

jmevel avatar Mar 09 '23 13:03 jmevel