Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jmevel opened this issue Jan 2, 2020 · 6 comments

Comments

@jmevel
Copy link

jmevel commented Jan 2, 2020

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

@cliron1
Copy link

cliron1 commented Jun 11, 2020

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

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

@jmevel
Copy link
Author

jmevel commented Jun 11, 2020

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

@mzhukovs
Copy link

mzhukovs commented Dec 2, 2020

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
            });

@jmevel
Copy link
Author

jmevel commented Dec 13, 2020

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

@mattfrear
Copy link

mattfrear commented Dec 21, 2022

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.

@jmevel
Copy link
Author

jmevel commented Mar 9, 2023

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 jmevel closed this as completed Jun 26, 2024
@jmevel jmevel closed this as not planned Won't fix, can't repro, duplicate, stale Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants