当前位置: 首页 > 知识库问答 >
问题:

Swager C#枚举生成-底层int值与原始枚举不匹配

云季萌
2023-03-14

我在服务器上创建了一个枚举,手动设置了整数值,而不是默认的从0开始递增

public enum UserType
{
    Anonymous = 0,
    Customer = 10,
    Technician = 21,
    Manager = 25,
    Primary = 30
}

我的服务器正在使用aspNetCore. App 2.2.0运行。它在Startup.cs与swashuckle aspnetcore 4.0.1一起配置,以生成一个swagger json文件来描述每次服务器启动时的api。

然后,我使用NSwag Studio for windows v 13.2.3.0生成一个C sharp api客户端,其中包含swagger JSON文件,以便在Xamarin应用程序中使用。在生成的c sharp api客户端中生成的枚举看起来像这样——底层的整数值与原始的枚举不匹配。

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.5.0 (Newtonsoft.Json v11.0.0.0)")]
public enum UserType
{
    [System.Runtime.Serialization.EnumMember(Value = @"Anonymous")]
    Anonymous = 0,

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

    [System.Runtime.Serialization.EnumMember(Value = @"Technician")]
    Technician = 2,

    [System.Runtime.Serialization.EnumMember(Value = @"Manager")]
    Manager = 3,

    [System.Runtime.Serialization.EnumMember(Value = @"Primary")]
    Primary = 4,

}

这给我的客户端带来了一个问题,因为有些情况下我需要知道整数值。我正在寻找一种解决方案,每次我想知道客户端的整数值时,我都可以避免编写转换器。

选项1:是否有一个选项是我在NSwag Studio或中缺少的。net配置(我的启动。Cs config在下面供参考)在那里我可以强制生成的枚举获得与原始枚举相同的整数值?

选项2:或者,如果没有,我的客户端和服务器都可以通过共享类库访问相同的原始枚举。有没有办法让生成的api客户端使用apiclient中的实际原始枚举。cs而不是生成自己的?

参考:

我在Startup. Cs中的swagger生成代码的枚举部分如下所示

services.AddJsonOptions(options =>
{
   options.SerializerSettings.Converters.Add(new StringEnumConverter());
....

services.AddSwaggerGen(setup =>
{
   setup.SwaggerDoc("v1", new Info { Title = AppConst.SwaggerTitle, Version = "v1" });

   setup.UseReferencedDefinitionsForEnums();
   ... other stuff...
 }

共有3个答案

沈飞舟
2023-03-14

更新
达伍德在上面发布了一个工作解决方案,它完全符合我的需求。

原答案< br >目前似乎没有办法做到这一点。正如@sellotape在他的评论中提到的,这甚至可能不是一个好主意。因为我控制着服务器,而且这是一个相对较新的项目,所以我将我的enum重构为正常的“从零开始的顺序”样式。

我确实认为它对某些用例很有用,例如支持不容易重构的遗留枚举,或者能够对中间有间隙的枚举进行编号,例如10、20、30。这将允许在以后插入11、12等,同时保留对枚举编码某种“顺序”的能力,并且不会随着项目的增长而打破该顺序。

然而,目前看来这是不可能的,所以我们将继续这样做。

秦权
2023-03-14

这就是我正在使用的两个枚举助手。一个由NSwag(x-enumNames)使用,另一个由Azure AutoRest使用(x-ms-enum

最终找到了对EnumDocumentFilter的引用(https://stackoverflow.com/a/49941775/1910735)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace SwaggerDocsHelpers
{
    /// <summary>
    /// Add enum value descriptions to Swagger
    /// https://stackoverflow.com/a/49941775/1910735
    /// </summary>
    public class EnumDocumentFilter : IDocumentFilter
    {
        /// <inheritdoc />
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            // add enum descriptions to result models
            foreach (var schemaDictionaryItem in swaggerDoc.Definitions)
            {
                var schema = schemaDictionaryItem.Value;
                foreach (var propertyDictionaryItem in schema.Properties)
                {
                    var property = propertyDictionaryItem.Value;
                    var propertyEnums = property.Enum;
                    if (propertyEnums != null && propertyEnums.Count > 0)
                    {
                        property.Description += DescribeEnum(propertyEnums);
                    }
                }
            }

            if (swaggerDoc.Paths.Count <= 0) return;

            // add enum descriptions to input parameters
            foreach (var pathItem in swaggerDoc.Paths.Values)
            {
                DescribeEnumParameters(pathItem.Parameters);

                // head, patch, options, delete left out
                var possibleParameterisedOperations = new List<Operation> { pathItem.Get, pathItem.Post, pathItem.Put };
                possibleParameterisedOperations.FindAll(x => x != null)
                    .ForEach(x => DescribeEnumParameters(x.Parameters));
            }
        }

        private static void DescribeEnumParameters(IList<IParameter> parameters)
        {
            if (parameters == null) return;

            foreach (var param in parameters)
            {
                if (param is NonBodyParameter nbParam && nbParam.Enum?.Any() == true)
                {
                    param.Description += DescribeEnum(nbParam.Enum);
                }
                else if (param.Extensions.ContainsKey("enum") && param.Extensions["enum"] is IList<object> paramEnums &&
                  paramEnums.Count > 0)
                {
                    param.Description += DescribeEnum(paramEnums);
                }
            }
        }

        private static string DescribeEnum(IEnumerable<object> enums)
        {
            var enumDescriptions = new List<string>();
            Type type = null;
            foreach (var enumOption in enums)
            {
                if (type == null) type = enumOption.GetType();
                enumDescriptions.Add($"{Convert.ChangeType(enumOption, type.GetEnumUnderlyingType())} = {Enum.GetName(type, enumOption)}");
            }

            return $"{Environment.NewLine}{string.Join(Environment.NewLine, enumDescriptions)}";
        }
    }

    public class EnumFilter : ISchemaFilter
    {
        public void Apply(Schema model, SchemaFilterContext context)
        {
            if (model == null)
                throw new ArgumentNullException("model");

            if (context == null)
                throw new ArgumentNullException("context");


            if (context.SystemType.IsEnum)
            {

                var enumUnderlyingType = context.SystemType.GetEnumUnderlyingType();
                model.Extensions.Add("x-ms-enum", new
                {
                    name = context.SystemType.Name,
                    modelAsString = false,
                    values = context.SystemType
                    .GetEnumValues()
                    .Cast<object>()
                    .Distinct()
                    .Select(value =>
                    {
                        //var t = context.SystemType;
                        //var convereted = Convert.ChangeType(value, enumUnderlyingType);
                        //return new { value = convereted, name = value.ToString() };
                        return new { value = value, name = value.ToString() };
                    })
                    .ToArray()
                });
            }
        }
    }


    /// <summary>
    /// Adds extra schema details for an enum in the swagger.json i.e. x-enumNames (used by NSwag to generate Enums for C# client)
    /// https://github.com/RicoSuter/NSwag/issues/1234
    /// </summary>
    public class NSwagEnumExtensionSchemaFilter : ISchemaFilter
    {
        public void Apply(Schema model, SchemaFilterContext context)
        {
            if (model == null)
                throw new ArgumentNullException("model");

            if (context == null)
                throw new ArgumentNullException("context");


            if (context.SystemType.IsEnum)
            {
                var names = Enum.GetNames(context.SystemType);
                model.Extensions.Add("x-enumNames", names);
            }
        }
    }
}

然后在startup.cs中配置它们

        services.AddSwaggerGen(c =>
        {
            ... the rest of your configuration

            // REMOVE THIS to use Integers for Enums
            // c.DescribeAllEnumsAsStrings();

            // add enum generators based on whichever code generators you decide
            c.SchemaFilter<NSwagEnumExtensionSchemaFilter>();
            c.SchemaFilter<EnumFilter>();
        });

这将在Swagger.json文件中生成枚举

        sensorType: {
          format: "int32",
          enum: [
            0,
            1,
            2,
            3
          ],
          type: "integer",
          x-enumNames: [
            "NotSpecified",
            "Temperature",
            "Fuel",
            "Axle"
          ],
          x-ms-enum: {
            name: "SensorTypesEnum",
            modelAsString: false,
            values: [{
                value: 0,
                name: "NotSpecified"
              },
              {
                value: 1,
                name: "Temperature"
              },
              {
                value: 2,
                name: "Fuel"
              },
              {
                value: 3,
                name: "Axle"
              }
            ]
          }
        },

但是这个解决方案有一个问题(我还没有时间研究),就是Enum名称是用我的DTO名称在NSwag中生成的-如果你确实找到了解决方案,请告诉我:-)

例如,使用NSwag生成以下枚举:

赫连冠玉
2023-03-14

@Dawood答案是杰作,
但它仅适用于旧版本的Swashbuckle(我不确定是哪个版本)
如果您有Swashbuckle 6.x,则该代码将无法编译。
这是相同的解决方案,但适用于虚张声势 6.x

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

/// <summary>
/// Add enum value descriptions to Swagger
/// https://stackoverflow.com/a/49941775/1910735
/// </summary>
public class EnumDocumentFilter : IDocumentFilter
{
    /// <inheritdoc />
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        foreach (KeyValuePair<string, OpenApiPathItem> schemaDictionaryItem in swaggerDoc.Paths)
        {
            OpenApiPathItem schema = schemaDictionaryItem.Value;
            foreach (OpenApiParameter property in schema.Parameters)
            {
                IList<IOpenApiAny> propertyEnums = property.Schema.Enum;
                if (propertyEnums.Count > 0)
                    property.Description += DescribeEnum(propertyEnums);
            }
        }

        if (swaggerDoc.Paths.Count == 0)
            return;

        // add enum descriptions to input parameters
        foreach (OpenApiPathItem pathItem in swaggerDoc.Paths.Values)
        {
            DescribeEnumParameters(pathItem.Parameters);

            foreach (KeyValuePair<OperationType, OpenApiOperation> operation in pathItem.Operations)
                DescribeEnumParameters(operation.Value.Parameters);
        }
    }

    private static void DescribeEnumParameters(IList<OpenApiParameter> parameters)
    {
        if (parameters == null)
            return;

        foreach (OpenApiParameter param in parameters)
        {
            if (param.Schema.Enum?.Any() == true)
            {
                param.Description += DescribeEnum(param.Schema.Enum);
            }
            else if (param.Extensions.ContainsKey("enum") && 
                     param.Extensions["enum"] is IList<object> paramEnums &&
                     paramEnums.Count > 0)
            {
                param.Description += DescribeEnum(paramEnums);
            }
        }
    }

    private static string DescribeEnum(IEnumerable<object> enums)
    {
        List<string> enumDescriptions = new();
        Type? type = null;
        foreach (object enumOption in enums)
        {
            if (type == null)
                type = enumOption.GetType();

            enumDescriptions.Add($"{Convert.ChangeType(enumOption, type.GetEnumUnderlyingType())} = {Enum.GetName(type, enumOption)}");
        }

        return Environment.NewLine + string.Join(Environment.NewLine, enumDescriptions);
    }
}
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

//https://stackoverflow.com/a/60276722/4390133
public class EnumFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (schema is null)
            throw new ArgumentNullException(nameof(schema));

        if (context is null)
            throw new ArgumentNullException(nameof(context));

        if (context.Type.IsEnum is false)
            return;

        schema.Extensions.Add("x-ms-enum", new EnumFilterOpenApiExtension(context));
    }
}
using System.Text.Json;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;
using Swashbuckle.AspNetCore.SwaggerGen;

public class EnumFilterOpenApiExtension : IOpenApiExtension
{
    private readonly SchemaFilterContext _context;
    public EnumFilterOpenApiExtension(SchemaFilterContext context)
    {
        _context = context;
    }

    public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
    {
        JsonSerializerOptions options = new() { WriteIndented = true };

        var obj = new {
            name = _context.Type.Name,
            modelAsString = false,
            values = _context.Type
                            .GetEnumValues()
                            .Cast<object>()
                            .Distinct()
                            .Select(value => new { value, name = value.ToString() })
                            .ToArray()
        };
        writer.WriteRaw(JsonSerializer.Serialize(obj, options));
    }
}
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

/// <summary>
/// Adds extra schema details for an enum in the swagger.json i.e. x-enumNames (used by NSwag to generate Enums for C# client)
/// https://github.com/RicoSuter/NSwag/issues/1234
/// </summary>
public class NSwagEnumExtensionSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (schema is null)
            throw new ArgumentNullException(nameof(schema));

        if (context is null)
            throw new ArgumentNullException(nameof(context));

        if (context.Type.IsEnum)
            schema.Extensions.Add("x-enumNames", new NSwagEnumOpenApiExtension(context));
    }
}
using System.Text.Json;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;
using Swashbuckle.AspNetCore.SwaggerGen;

public class NSwagEnumOpenApiExtension : IOpenApiExtension
{
    private readonly SchemaFilterContext _context;
    public NSwagEnumOpenApiExtension(SchemaFilterContext context)
    {
        _context = context;
    }

    public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
    {
        string[] enums = Enum.GetNames(_context.Type);
        JsonSerializerOptions options = new() { WriteIndented = true };
        string value = JsonSerializer.Serialize(enums, options);
        writer.WriteRaw(value);
    }
}

最后,过滤器的注册

services.AddSwaggerGen(c =>
{
    ... the rest of your configuration

    // REMOVE THIS to use Integers for Enums
    // c.DescribeAllEnumsAsStrings();

    // add enum generators based on whichever code generators you decide
    c.SchemaFilter<NSwagEnumExtensionSchemaFilter>();
    c.SchemaFilter<EnumFilter>();
});

笔记

    我正在使用 C# 10 功能
  1. (隐式用法和其他功能)如果您不使用 C# 10,则必须添加一些 USING 语句并恢复为旧的命名空间样式,并为了 C# 而进行一些其他小修改
  2. 我测试了这段代码,结果与原始答案相同
 类似资料:
  • 让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 枚举 出所有可能的值,这也正是此枚举名字的由来。 任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的,而且不能

  • 枚举具有名为'hash value'的属性,该属性是枚举内的索引。

  • 问题内容: 假设我有一个格式为基本XML的文件,如下所示: 我想在运行时变成这样的东西: …,然后将新创建的枚举传递给我的应用程序。我将如何实现这样的目标?可以吗 问题答案: 您尝试做的事情没有任何意义。枚举实际上仅是为了编译时的利益,因为它们表示一组固定的常量。在运行时,动态生成的枚举的含义是什么- 与普通对象有什么不同?例如: 您的XML可以解析为新实例化的对象,这些对象可以存储在某些程序中,

  • 问题内容: 我正在从Android的本机代码接收long或int形式的返回值,我想将其转换或与enum匹配以用于处理目的。可能吗 ?怎么样? 问题答案: 如果您完全控制值和枚举,并且它们是顺序的,则可以使用枚举序号值:

  • 问题内容: 我在声明枚举时遇到麻烦。我要创建的是一个“ DownloadType”的枚举,其中有3种下载类型(AUDIO,VIDEO,AUDIO_AND_VIDEO)。 我已经实现了如下代码: 如果我再像这样使用它,则效果很好: 但是,我希望这样,所以我不必要求“值”。我可能会弄错,但这是Java中几个类(例如Font)工作的方式,例如,设置字体样式,您可以使用: 它返回一个int值,我们不使用:

  • 问题内容: 以前,我将LegNo枚举定义为: 通过调用,我可以获得与每个枚举关联的值。 但是现在我决定让枚举是int -1而不是0,所以我决定使用私有构造函数进行初始化并设置其int值 现在唯一的事情就是因为我这样做,所以该方法不适用于枚举。我如何获得与int相关联的枚举?除了使用case switch语句或if-elseif-elseif之外,还有其他有效的方法吗? 我可以看到很多与从枚举获取i