Open Closed

Configure ABP & Swashbuckle to represent enums as strings Part II #6718


User avatar
0
alex@livemobility.com created
  • ABP Framework version: v8
  • UI Type: Angular /
  • Database System: EF Core (SQL Server,)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

Hello ABP team

As a followup on ticket: https://support.abp.io/QA/Questions/2360/Configure-ABP--Swashbuckle-to-represent-enums-as-strings

I just realized that the problem is actually now solved by introducing that package. Basically we want to configure ASP.Core to interpret the enums by their String values, and not integers. This is usually solved by adding:

services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); });

And than the result is this:

How can we achieve this with ABP?

Just to be clear, using Unchase.Swashbuckle.AspNetCore.Extensions is not the solution, since this will just add explanations to the integer values.


16 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    How can we achieve this with ABP?

    services.AddControllers().AddJsonOptions(options =>
    {
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    })
    

    In abp project.

    services.Configure<JsonOptions>(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });
    
    
  • User Avatar
    0
    alex@livemobility.com created

    Hi,

    Thanks for the initial input

    I added this in

            context.Services.AddOptions&lt;JsonOptions&gt;()
          .Configure&lt;IServiceProvider&gt;((options, rootServiceProvider) =>
          {
              options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
          });
    

    in ConfigureServices, at the end. Still, this didn't transform the enums into strings

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share a test template app?

    liming.ma@volosoft.com

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I can't build and run your project. Can you check it?

    your zip file has error.

  • User Avatar
    0
    alex@livemobility.com created

    Hi, sorry about posting the project to the wrong ticket.

    I checked the build and it looks like you guys didn't fix the base template ( I reported a bug regarding this a long time ago )

    Please change in Acme.BookStore.HttpApi.Host.csproj

    <ItemGroup> <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX" Version="3.0.-" /> </ItemGroup>

    to <ItemGroup> <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX" Version="3.0.0" /> </ItemGroup>

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    There is no Program file in Acme.BookStore.HttpApi.Host

  • User Avatar
    0
    alex@livemobility.com created

    Hi, yes there is

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    It seems there is no such file.

  • User Avatar
    0
    alex@livemobility.com created

    I created a new zip:

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok, I will check it asap.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    options.UserFriendlyEnums();

    private static void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddAbpSwaggerGenWithOAuth(
            configuration["AuthServer:Authority"]!,
            new Dictionary<string, string>
            {
                    {"BookStore", "BookStore API"}
            },
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
                options.UserFriendlyEnums();
            });
    }
    

  • User Avatar
    0
    alex@livemobility.com created

    Hi,

    Thanks for your analysis. This solution only fixes the swagger UI, which now shows Enums as strings, but the serialization is still done as int. An example below, that property should have been returned as a string.

    In a vanilla asp core + swagger project, this is fixed by using
    services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); });

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I will enhance this in the abp framework.

    context.Services.Configure<JsonOptions>(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.Converters.RemoveAll(x => x is AbpStringToEnumFactory);
        options.JsonSerializerOptions.Converters.Add(new MyAbpStringToEnumFactory());
    });
    
    using System;
    using System.Reflection;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    using Volo.Abp.Json.SystemTextJson.JsonConverters;
    
    namespace Acme.BookStore;
    
    public class MyAbpStringToEnumFactory : JsonConverterFactory
    {
        private readonly JsonNamingPolicy? _namingPolicy;
        private readonly bool _allowIntegerValues;
    
        public MyAbpStringToEnumFactory()
            : this(namingPolicy: null, allowIntegerValues: true)
        {
    
        }
    
        public MyAbpStringToEnumFactory(JsonNamingPolicy? namingPolicy, bool allowIntegerValues)
        {
            _namingPolicy = namingPolicy;
            _allowIntegerValues = allowIntegerValues;
        }
    
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsEnum;
        }
    
        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            return (JsonConverter)Activator.CreateInstance(
                typeof(MyAbpStringToEnumConverter<>).MakeGenericType(typeToConvert),
                BindingFlags.Instance | BindingFlags.Public,
                binder: null,
                new object?[] { _namingPolicy, _allowIntegerValues },
                culture: null)!;
        }
    }
    
    public class MyAbpStringToEnumConverter<T> : JsonConverter<T>
        where T : struct, Enum
    {
        private readonly JsonStringEnumConverter _innerJsonStringEnumConverter;
    
        private JsonSerializerOptions? _readJsonSerializerOptions;
        private JsonSerializerOptions? _writeJsonSerializerOptions;
    
        public MyAbpStringToEnumConverter()
            : this(namingPolicy: null, allowIntegerValues: true)
        {
    
        }
    
        public MyAbpStringToEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
        {
            _innerJsonStringEnumConverter = new JsonStringEnumConverter(namingPolicy, allowIntegerValues);
        }
    
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsEnum;
        }
    
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            _readJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, x =>
                    x == this ||
                    x.GetType() == typeof(AbpStringToEnumFactory),
                _innerJsonStringEnumConverter.CreateConverter(typeToConvert, options));
    
            return JsonSerializer.Deserialize<T>(ref reader, _readJsonSerializerOptions);
        }
    
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, x =>
                    x == this ||
                    x.GetType() == typeof(MyAbpStringToEnumFactory),
                _innerJsonStringEnumConverter.CreateConverter(typeof(T), options));
    
            JsonSerializer.Serialize(writer, value,_writeJsonSerializerOptions);
        }
    
        public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return (T)Enum.Parse(typeToConvert, reader.GetString()!);
        }
    
        public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            writer.WritePropertyName(Enum.GetName(typeof(T), value)!);
        }
    }
    
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    https://github.com/abpframework/abp/pull/19132

  • User Avatar
    0
    alex@livemobility.com created

    Hi, Thanks for the support. In the meantime I should use your provided solution with MyAbpStringToEnumConverter ?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Yes, You should use MyAbpStringToEnumConverter until the new version is released.

Made with ❤️ on ABP v9.0.0-preview. Updated on October 07, 2024, 08:52