Swagger UI Web Api documentation Present enums as

2020-02-07 16:01发布

Is there a way to display all enums as their string value in swagger instead of their int value?

I want to be able to submit POST actions and put enums according to their string value without having to look at the enum every time.

I tried DescribeAllEnumsAsStrings but the server then receives strings instead of the enum value which is not what we're looking for.

Has anyone solved this?

Edit:

public class Letter 
{
    [Required]
    public string Content {get; set;}

    [Required]
    [EnumDataType(typeof(Priority))]
    public Priority Priority {get; set;}
}


public class LettersController : ApiController
{
    [HttpPost]
    public IHttpActionResult SendLetter(Letter letter)
    {
        // Validation not passing when using DescribeEnumsAsStrings
        if (!ModelState.IsValid)
            return BadRequest("Not valid")

        ..
    }

    // In the documentation for this request I want to see the string values of the enum before submitting: Low, Medium, High. Instead of 0, 1, 2
    [HttpGet]
    public IHttpActionResult GetByPriority (Priority priority)
    {

    }
}


public enum Priority
{
    Low, 
    Medium,
    High
}

13条回答
霸刀☆藐视天下
2楼-- · 2020-02-07 16:28

There were a number of shortcomings I found in the other answers for what we were looking for, so I thought I'd supply my own take on this. We're using ASP.NET Core 3.1 with System.Text.Json, but our approach works irrespective of the JSON serializer used.

Our goal was to accept lower-camel-cased enum string values in both the ASP.NET Core API as well as document the same in Swagger. We're currently making use of [DataContract] and [EnumMember], so the approach is to take the lower-camel-cased value from the EnumMember value property and use that across the board.

Our sample enum:

[DataContract]
public class enum Colors
{
  [EnumMember(Value="brightPink")]
  BrightPink,
  [EnumMember(Value="blue")]
  Blue
}

We'll use the EnumMember values in Swashbuckle by using an ISchemaFilter as in the following:

public class DescribeEnumMemberValues : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            schema.Enum.Clear();

            //Retrieve each of the values decorated with an EnumMember attribute
            foreach (var member in context.Type.GetMembers())
            {
                var memberAttr = member.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault();
                if (memberAttr != null)
                {
                    var attr = (EnumMemberAttribute) memberAttr;
                    schema.Enum.Add(new OpenApiString(attr.Value));
                }
            }
        }
    }
}

We're using a third-party NuGet package (GitHub repo) to ensure that this naming scheme is also utilized in ASP.NET Core. Configure it in Startup.cs within ConfigureServices with:

services.AddControllers()
  .AddJsonOptions(opt => opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverterWithAttributeSupport()));

Finally, we need to register our ISchemaFilter in Swashbuckle, so also add the following also in ConfigureServices():

services.AddSwaggerGen(c => {
  c.SchemaFilter<DescribeEnumMemberValues>();
});
查看更多
啃猪蹄的小仙女
3楼-- · 2020-02-07 16:25

I wanted to use rory_za's answer in a .NET Core application, but I had to modify it a bit to make it work. Here is the implementation I came up with for .NET Core.

I also changed it so it doesn't assume the underlying type is int, and use new lines between the values for easier reading.

/// <summary>
/// Add enum value descriptions to Swagger
/// </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)}";
    }
}

Then add this to your ConfigureServices method in Startup.cs:

c.DocumentFilter<EnumDocumentFilter>();
查看更多
戒情不戒烟
4楼-- · 2020-02-07 16:25

I have found nice workaround Here:

@PauloVetor - solved it using ShemaFilter like this:

public class EnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            model.Enum.Clear();
            Enum.GetNames(context.Type)
                .ToList()
                .ForEach(n => model.Enum.Add(new OpenApiString(n)));
            }
        }
    }
}

And in Startup.cs:

services.AddSwaggerGen(options =>
{
    options.SchemaFilter<EnumSchemaFilter>();
}
查看更多
Anthone
5楼-- · 2020-02-07 16:26

So I think I have a similar problem. I'm looking for swagger to generate enums along with the int -> string mapping. The API must accept the int. The swagger-ui matters less, what I really want is code generation with a "real" enum on the other side (android apps using retrofit in this case).

So from my research this ultimately seems to be a limit of the OpenAPI specification which Swagger uses. It's not possible to specify names and numbers for enums.

The best issue I've found to follow is https://github.com/OAI/OpenAPI-Specification/issues/681 which looks like a "maybe soon" but then Swagger would have to be updated, and in my case Swashbuckle as well.

For now my workaround has been to implement a document filter that looks for enums and populates the relevant description with the contents of the enum.

        GlobalConfiguration.Configuration
            .EnableSwagger(c =>
                {
                    c.DocumentFilter<SwaggerAddEnumDescriptions>();

                    //disable this
                    //c.DescribeAllEnumsAsStrings()

SwaggerAddEnumDescriptions.cs:

using System;
using System.Web.Http.Description;
using Swashbuckle.Swagger;
using System.Collections.Generic;

public class SwaggerAddEnumDescriptions : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
    {
        // add enum descriptions to result models
        foreach (KeyValuePair<string, Schema> schemaDictionaryItem in swaggerDoc.definitions)
        {
            Schema schema = schemaDictionaryItem.Value;
            foreach (KeyValuePair<string, Schema> propertyDictionaryItem in schema.properties)
            {
                Schema property = propertyDictionaryItem.Value;
                IList<object> propertyEnums = property.@enum;
                if (propertyEnums != null && propertyEnums.Count > 0)
                {
                    property.description += DescribeEnum(propertyEnums);
                }
            }
        }

        // add enum descriptions to input parameters
        if (swaggerDoc.paths.Count > 0)
        {
            foreach (PathItem pathItem in swaggerDoc.paths.Values)
            {
                DescribeEnumParameters(pathItem.parameters);

                // head, patch, options, delete left out
                List<Operation> possibleParameterisedOperations = new List<Operation> { pathItem.get, pathItem.post, pathItem.put };
                possibleParameterisedOperations.FindAll(x => x != null).ForEach(x => DescribeEnumParameters(x.parameters));
            }
        }
    }

    private void DescribeEnumParameters(IList<Parameter> parameters)
    {
        if (parameters != null)
        {
            foreach (Parameter param in parameters)
            {
                IList<object> paramEnums = param.@enum;
                if (paramEnums != null && paramEnums.Count > 0)
                {
                    param.description += DescribeEnum(paramEnums);
                }
            }
        }
    }

    private string DescribeEnum(IList<object> enums)
    {
        List<string> enumDescriptions = new List<string>();
        foreach (object enumOption in enums)
        {
            enumDescriptions.Add(string.Format("{0} = {1}", (int)enumOption, Enum.GetName(enumOption.GetType(), enumOption)));
        }
        return string.Join(", ", enumDescriptions.ToArray());
    }

}

This results in something like the following on your swagger-ui so at least you can "see what you're doing": enter image description here

查看更多
Evening l夕情丶
6楼-- · 2020-02-07 16:29

For ASP.Net Core 3 with Newtonsoft JSON library

In Startup.cs/ConfigureServices():

services
    .AddControllersWithViews(...)
    .AddNewtonsoftJson(options => 
        options.SerializerSettings.Converters.Add(new StringEnumConverter()))

This does not require a call to options.DescribeAllEnumsAsStrings() in AddSwaggerGen().

For ASP.Net Core 3 with Microsoft JSON library

See @Bashir's answer, basically:

services
    .AddControllersWithViews(...)
    .AddJsonOptions(options => 
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));

And as he describes, it currently does require options.DescribeAllEnumsAsStrings().

For ASP.Net Core 2

In Startup.cs/ConfigureServices():

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

This does not require a call to options.DescribeAllEnumsAsStrings() in AddSwaggerGen().

Pre ASP.Net Core

Use the DescribeAllEnumsAsStrings() approach from the accepted answer.

查看更多
做自己的国王
7楼-- · 2020-02-07 16:29

ASP NET SOLUTION

In my api docs one enum was still shown as int despite the property being marked with StringEnumConverter. We couldn't afford using the global setting for all enums mentioned above. Adding this line in SwaggerConfig solved the issue:

c.MapType<ContactInfoType>(() => new Schema { type = "string", @enum = Enum.GetNames(typeof(ContactInfoType))});
查看更多
登录 后发表回答