如何使用OpenAPI/Swagger将运行在.net5上的函数应用程序中的枚举序列化为字符串?

q9yhzks0  于 2022-11-06  发布在  .NET
关注(0)|答案(4)|浏览(145)

我正在探索在新的隔离模式下运行在.net5上的函数应用程序。我有HTTP触发的函数,我想通过OpenAPI / Swagger发布这些函数。
为此,我使用preview (0.7.2)中的包Microsoft.Azure.WebJobs.Extensions.OpenApi将OpenAPI功能添加到我的Function应用程序中。
我尝试在OpenAPI页面中将enums显示为string,但无法正常工作。
以下是Program.cs文件中的设置:

public static class Program
    {
        private static Task Main(string[] args)
        {
            IHost host = new HostBuilder()
                .ConfigureAppConfiguration(configurationBuilder =>
                {
                    configurationBuilder.AddCommandLine(args);
                })
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder.Services.Configure<JsonSerializerOptions>(options =>
                    {
                        options.Converters.Add(new JsonStringEnumConverter());
                        options.PropertyNameCaseInsensitive = true;
                    });
                })
                .ConfigureServices(services =>
                {
                    //  Registers any services.             
                })
                .Build();

            return host.RunAsync();
        }
    }

下面是枚举:

[JsonConverter(typeof(JsonStringEnumConverter))]
    public enum ApprovalContract
    {
        [EnumMember(Value = "Approved")]
        Approved = 1,

        [EnumMember(Value = "Rejected")]
        Rejected = 2
    }

和使用它的类之一:

public sealed class DeletionResponseContract
    {
        [JsonPropertyName("approval")]
        public ApprovalContract Approval { get; set; }
    }

我将所有对Newtonsoft.Json的引用都替换为System.Text.Json
下面是Swagger页面中的输出:

问题

如何在Swagger页面中将enum序列化为string,而不是int,并在.net5上运行HTTP触发的Azure函数?

更新

我看到JsonStringEnumConverter的构造函数给出了允许整数值的指示:

public JsonStringEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
    {
      this._namingPolicy = namingPolicy;
      this._converterOptions = allowIntegerValues ? EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers : EnumConverterOptions.AllowStrings;
    }

我这样修改了我的配置,但没有任何成功:

builder.Services.Configure<JsonSerializerOptions>(options =>
{
     options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false));
     options.PropertyNameCaseInsensitive = true;
});
mec1mxoz

mec1mxoz1#

您必须实作ISchemaFilter,并在AddSwaggerGen上设定它。它会产生枚举项目的更好描述。

builder.Services.AddSwaggerGen(c =>
{
    c.SchemaFilter<EnumSchemaFilter>();
});

//your implementation
public class EnumSchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            if (context.Type.IsEnum)
            {
                model.Enum.Clear();
                Enum.GetNames(context.Type)
                    .ToList()
                    .ForEach(name => model.Enum.Add(new OpenApiString($"{Convert.ToInt64(Enum.Parse(context.Type, name))} - {name}")));
            }
        }
    }
myzjeezk

myzjeezk2#

穆里洛的回答给了我灵感,但我无法让它发挥作用。所以,这里有一个替代的解决方案:
创建一个文档筛选器,该筛选器将:

  • 在您的swagger架构中查找所有枚举属性
  • 然后使用反射在代码中找到匹配的属性(注意:这并不考虑命名空间,因此如果您有多个同名的类,则可能会失败)
  • 使用c#枚举中的值更新swagger属性
public class EnumDocumentFilter : IDocumentFilter
 {
     public void Apply(IHttpRequestDataObject req, OpenApiDocument document)
     {
         foreach(var schema in document.Components.Schemas)
             foreach(var property in schema.Value.Properties)
                 if (property.Value.Enum.Any())
                 {
                     var schemaType = Assembly.GetExecutingAssembly().GetTypes().Single(t => t.Name == Camel(schema.Key));
                     var propertyType = schemaType.GetProperty(Camel(property.Key)).PropertyType;
                     property.Value.Enum = Enum.GetNames(propertyType)
                         .Select(name => new OpenApiString(name))
                         .Cast<IOpenApiAny>()
                         .ToList();
                     property.Value.Type = "string";
                     property.Value.Default = property.Value.Enum.First();
                     property.Value.Format = null;
                 }
     }

     private static string Camel(string key)
         => $"{char.ToUpperInvariant(key[0])}{key[1..]}";
 }

然后在您的OpenApiConfigurationOptions中注册该过滤器

public class OpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions
    {
...
        public override List<IDocumentFilter> DocumentFilters { get => base.DocumentFilters.Append(new EnumDocumentFilter()).ToList(); }
    }
sczxawaw

sczxawaw3#

感谢Alan欣顿的回答,我能够使用反射使自定义文档过滤器正常工作。

**问题:**枚举是自动生成的,我无法在每次刷新代码时都添加StringEnumCovertor属性。自动生成的代码:

public enum Status
{
    [System.Runtime.Serialization.EnumMember(Value = @"new")]
    New = 0,

    [System.Runtime.Serialization.EnumMember(Value = @"confirmed")]
    Confirmed = 1,

    [System.Runtime.Serialization.EnumMember(Value = @"processing")]
    Processing = 2
}

public partial class Order 
{
    /// <summary>Status</summary>
    [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
    [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
    public Status Status { get; set; }
}

解决方案:

public OpenApiConfigurationOptions()
{
    DocumentFilters.Add(new OpenApiEnumAsStringsDocumentFilter());
}

public class OpenApiEnumAsStringsDocumentFilter : IDocumentFilter
{
    private const string YourNamespace = "your.namespace";
    private const string EnumDefaultMemberValue = "value__";
    private const string StringSchemaType = "string";

    public void Apply(IHttpRequestDataObject request, OpenApiDocument document)
    {
        var assemblyTypes = Assembly
            .GetExecutingAssembly()
            .GetTypes()
            .Where(x => !string.IsNullOrEmpty(x.FullName) && x.FullName.StartsWith(YourNamespace, StringComparison.InvariantCulture));

        // Loop all DTO classes
        foreach (var schema in document.Components.Schemas)
        {
            foreach (var property in schema.Value.Properties)
            {
                if (property.Value.Enum.Any())
                {
                    var schemaType = assemblyTypes.SingleOrDefault(t => t.Name.Equals(schema.Key, StringComparison.InvariantCultureIgnoreCase));
                    if (schemaType == null)
                        continue;

                    var enumType = schemaType.GetProperty(string.Concat(property.Key[0].ToString().ToUpper(), property.Key.AsSpan(1))).PropertyType;

                    UpdateEnumValuesAsString(property.Value, enumType);
                }
            }
        }

        // Loop all request parameters
        foreach (var path in document.Paths)
        {
            foreach (var operation in path.Value.Operations)
            {
                foreach (var parameter in operation.Value.Parameters)
                {
                    if (parameter.Schema.Enum.Any())
                    {
                        var enumType = assemblyTypes.SingleOrDefault(t => t.Name.Equals(parameter.Name, StringComparison.InvariantCultureIgnoreCase));
                        if (enumType == null)
                            continue;

                        UpdateEnumValuesAsString(parameter.Schema, enumType);
                    }
                }
            }
        }
    }

    private static void UpdateEnumValuesAsString(OpenApiSchema schema, Type enumType)
    {
        schema.Enum.Clear();
        enumType
            .GetTypeInfo()
            .DeclaredMembers
            .Where(m => !m.Name.Equals(EnumDefaultMemberValue, StringComparison.InvariantCulture))
            .ToList()
            .ForEach(m =>
            {
                var attribute = m.GetCustomAttribute<EnumMemberAttribute>(false);
                schema.Enum.Add(new OpenApiString(attribute.Value));
            });
        schema.Type = StringSchemaType;
        schema.Default = schema.Enum.FirstOrDefault();
        schema.Format = null;
    }
}
1zmg4dgp

1zmg4dgp4#

根据Microsoft.Azure.WebJobs.Extensions.OpenApi.Core文档,您应该能够在枚举上设置[JsonConverter(typeof(StringEnumConverter))](使用Newtonsoft包)属性,以触发Swagger中字符串的使用。
但是,我遇到了OpenAPI文档仍然没有将枚举显示为字符串的问题,我认为该问题与Newtonsoft版本13.0.1(这是我的项目的依赖项)和Azure功能核心工具(AFCT)v. 3.41。无论如何,当将Newtonsoft降级到12.0时,它都得到了解决。3或更低版本,或者升级项目以使用Azure Functions V4以及Azure Function Core Tools v. 4.x.x。
我怀疑Azure Function Core Tools是原因,而不是与Azure Function版本相关的其他东西,原因是AFCT在您启动它时加载Newtonsoft程序集12.0.0.0,如果您在项目中使用Newtonsoft 12.0.3,则Microsoft.Azure.WebJobs.Extensions.OpenApi.Core可能会使用相同的程序集。但如果项目使用13.0.1,则它引用程序集版本 www.example.com 由Microsoft.Azure.WebJobs.Extensions.OpenApi.Core与12.0.0.0程序集一起加载。版本中的这种不匹配可能是该属性不能按预期工作的原因。

相关问题