Swagger C# Генерация перечисления - базовые значения int не совпадают с исходным перечислением - PullRequest
3 голосов
/ 14 февраля 2020

Я создал enum на своем сервере с целочисленными значениями, заданными вручную, а не с шагом по умолчанию, увеличенным с 0

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

Мой сервер работает с использованием AspNetCore.App 2.2.0. Он настроен в Startup.cs с помощью swashbuckle aspnetcore 4.0.1 для генерации файла swagger json для описания API при каждом запуске сервера.

Затем я использую NSwag Studio для 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 конфигурация (моя конфигурация Startup.Cs приведена ниже для справки), где я могу заставить сгенерированные перечисления получить те же целочисленные значения, что и исходное перечисление?

Вариант 2: в противном случае, если нет, и мой клиент, и мой сервер имеют доступ к одному и тому же исходному перечислению через общую библиотеку классов. Есть ли способ заставить сгенерированный клиент API использовать фактические исходные перечисления в apiclient.cs вместо создания своих собственных?

Ссылка:

Перечисляемая часть моего кода генерации чванства в Startup.Cs выглядит так

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...
                  }

Ответы [ 2 ]

1 голос
/ 18 февраля 2020

Итак, это два помощника Enum, которые я использую. Один используется NSwag (x-enumNames), а другой - Azure AutoRest (x-ms-enums)

Наконец-то найдена ссылка для EnumDocumentFilter ({ ссылка })

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 file

        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. Если вы найдете решение для этого, дайте мне знать: )

Например, следующий Enum был сгенерирован с использованием NSwag:

enter image description here

0 голосов
/ 16 февраля 2020

* ОБНОВЛЕНИЕ *

Дауд опубликовал рабочее решение в ответе ниже, который делает именно то, что я хочу.

* ОРИГИНАЛЬНЫЙ ОТВЕТ * Кажется, в настоящее время нет возможности сделать это. Как отметил @sellotape в своем комментарии, это может даже не быть хорошей идеей. Так как я контролирую сервер, и это относительно новый проект, я реорганизовал мой enum в нормальный стиль «последовательный с нуля».

Я действительно думаю, что это было бы полезно для некоторых вариантов использования - например, поддержка устаревшего перечисления, которое нельзя легко реорганизовать, или возможность нумерации перечислений с пробелами в середине, например, 10,20,30 , Это позволило бы вставить 11,12 и c позднее, сохранив при этом возможность кодировать некий «порядок» в вашем перечислении и не нарушать этот порядок по мере роста проекта.

На данный момент это не представляется возможным, поэтому мы будем go с этим.

...