Mohammad Dayyan
Mohammad Dayyan

Reputation: 22409

SwaggerUI not display enum summary description, C# .net core?

I used https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-2.1&tabs=visual-studio#xml-comments to show my classes summaries description in SwaggerUI, it's OK but not show enum summary description !
My startup.cs

services.AddSwaggerGen(c =>
{   
    c.SwaggerDoc("v1", new Info
    {
        Version = "v1",
        Title = "My App-Service",
        Description = "My Description",
    });
    c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));  
    c.DescribeAllEnumsAsStrings();
});

My enum:

public enum GenderEnum
{
    /// <summary>
    /// Man Description
    /// </summary>
    Man = 1,

    /// <summary>
    /// Woman Description
    /// </summary>
    Woman = 2
}

It shows something like following:
Swagger UI enum

I want to show Man Description and Woman Description in SwaggerUI
like this:

Man = 1, Man Description
Woman = 2,  Woman Description


I'm using Swashbuckle.AspNetCore v4.0.1 package

Upvotes: 21

Views: 19958

Answers (4)

Paul Hatcher
Paul Hatcher

Reputation: 8156

This solution allows for

  • Show underlying value as well as name/description
  • Handle multiple xml documentation files, but only process docs once.
  • Customization of the layout without code change

Here's the class...

using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;

using Microsoft.OpenApi.Models;

using Swashbuckle.AspNetCore.SwaggerGen;

namespace YourNamespace
{
    /// <summary>
    /// Swagger schema filter to modify description of enum types so they
    /// show the XML docs attached to each member of the enum.
    /// </summary>
    public class DescribeEnumMembers : ISchemaFilter
    {
        private readonly XDocument xmlComments;
        private readonly string assemblyName;

        /// <summary>
        /// Initialize schema filter.
        /// </summary>
        /// <param name="xmlComments">Document containing XML docs for enum members.</param>
        public DescribeEnumMembers(XDocument xmlComments)
        {
            this.xmlComments = xmlComments;
            this.assemblyName = DetermineAssembly(xmlComments);
        }

        /// <summary>
        /// Pre-amble to use before the enum items
        /// </summary>
        public static string Prefix { get; set; } = "<p>Possible values:</p>";

        /// <summary>
        /// Format to use, 0 : value, 1: Name, 2: Description
        /// </summary>
        public static string Format { get; set; } = "<b>{0} - {1}</b>: {2}";

        /// <summary>
        /// Apply this schema filter.
        /// </summary>
        /// <param name="schema">Target schema object.</param>
        /// <param name="context">Schema filter context.</param>
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            var type = context.Type;

            // Only process enums and...
            if (!type.IsEnum)
            {
                return;
            }

            // ...only the comments defined in their origin assembly
            if (type.Assembly.GetName().Name != assemblyName)
            {
                return;
            }
            var sb = new StringBuilder(schema.Description);

            if (!string.IsNullOrEmpty(Prefix))
            {
                sb.AppendLine(Prefix);
            }

            sb.AppendLine("<ul>");

            // TODO: Handle flags better e.g. Hex formatting
            foreach (var name in Enum.GetValues(type))
            {
                // Allows for large enums
                var value = Convert.ToInt64(name);
                var fullName = $"F:{type.FullName}.{name}";

                var description = xmlComments.XPathEvaluate(
                    $"normalize-space(//member[@name = '{fullName}']/summary/text())"
                ) as string;

                sb.AppendLine(string.Format("<li>" + Format + "</li>", value, name, description));
            }

            sb.AppendLine("</ul>");

            schema.Description = sb.ToString();
        }

        private string DetermineAssembly(XDocument doc)
        {
            var name = ((IEnumerable)doc.XPathEvaluate("/doc/assembly")).Cast<XElement>().ToList().FirstOrDefault();
            
            return name?.Value;
        }
    }
}

and utilization...

services.AddSwaggerGen(c =>
{
    ...
    
    // See https://github.com/domaindrivendev/Swashbuckle/issues/86
    var dir = new DirectoryInfo(AppContext.BaseDirectory);
    
    foreach (var fi in dir.EnumerateFiles("*.xml"))
    {
        var doc = XDocument.Load(fi.FullName);
        c.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);
        c.SchemaFilter<DescribeEnumMembers>(doc);
    }
});

This then reports as

enter image description here

Upvotes: 11

Eric Mutta
Eric Mutta

Reputation: 1437

As of June/2021 OpenApi now supports this and you can extend Swagger to show the details. Here is my code for C# on .NET 5.0.

First define the schema filter in a file (call it DescribeEnumMembers.cs and be sure to change YourNamespace to the name of your namespace):

using System;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;

using Microsoft.OpenApi.Models;

using Swashbuckle.AspNetCore.SwaggerGen;

namespace YourNamespace
{
  /// <summary>
  /// Swagger schema filter to modify description of enum types so they
  /// show the XML docs attached to each member of the enum.
  /// </summary>
  public class DescribeEnumMembers: ISchemaFilter
  {
    private readonly XDocument mXmlComments;

    /// <summary>
    /// Initialize schema filter.
    /// </summary>
    /// <param name="argXmlComments">Document containing XML docs for enum members.</param>
    public DescribeEnumMembers(XDocument argXmlComments)
      => mXmlComments = argXmlComments;

    /// <summary>
    /// Apply this schema filter.
    /// </summary>
    /// <param name="argSchema">Target schema object.</param>
    /// <param name="argContext">Schema filter context.</param>
    public void Apply(OpenApiSchema argSchema, SchemaFilterContext argContext) {
      var EnumType = argContext.Type;

      if(!EnumType.IsEnum) return;

      var sb = new StringBuilder(argSchema.Description);

      sb.AppendLine("<p>Possible values:</p>");
      sb.AppendLine("<ul>");

      foreach(var EnumMemberName in Enum.GetNames(EnumType)) {
        var FullEnumMemberName = $"F:{EnumType.FullName}.{EnumMemberName}";

        var EnumMemberDescription = mXmlComments.XPathEvaluate(
          $"normalize-space(//member[@name = '{FullEnumMemberName}']/summary/text())"
        ) as string;

        if(string.IsNullOrEmpty(EnumMemberDescription)) continue;

        sb.AppendLine($"<li><b>{EnumMemberName}</b>: {EnumMemberDescription}</li>");
      }

      sb.AppendLine("</ul>");

      argSchema.Description = sb.ToString();
    }
  }
}

Then enable it in your ASP.NET ConfigureServices() method. Here is my code after snipping out the parts that don't matter for this exercise:

public void ConfigureServices(IServiceCollection argServices) {
  // ...<snip other code>

  argServices.AddSwaggerGen(SetSwaggerGenOptions);

  // ...<snip other code>

  return;

  // ...<snip other code>

  void SetSwaggerGenOptions(SwaggerGenOptions argOptions) {
    // ...<snip other code>

    AddXmlDocs();
    return;

    void AddXmlDocs() {
      // generate paths for the XML doc files in the assembly's directory.
      var XmlDocPaths = Directory.GetFiles(
        path: AppDomain.CurrentDomain.BaseDirectory,
        searchPattern: "YourAssemblyNameHere*.xml"
      );

      // load the XML docs for processing.
      var XmlDocs = (
        from DocPath in XmlDocPaths select XDocument.Load(DocPath)
      ).ToList();

      // ...<snip other code>

      // add pre-processed XML docs to Swagger.
      foreach(var doc in XmlDocs) {
        argOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);

        // apply schema filter to add description of enum members.
        argOptions.SchemaFilter<DescribeEnumMembers>(doc);
      }
    }
  }
}

Remember to change "YourAssemblyNameHere*.xml" to match your assembly name. The important line that enables the schema filter is:

argOptions.SchemaFilter<DescribeEnumMembers>(doc);

...which MUST be called AFTER the following line:

argOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);

Using the above code, if you have an enum type defined like this for example:

  /// <summary>
  /// Setting to control when a no-match report is returned when searching.
  /// </summary>
  public enum NoMatchReportSetting
  {
    /// <summary>
    /// Return no-match report only if the search query has no match.
    /// </summary>
    IfNoMatch = 0,
    /// <summary>
    /// Always return no-match report even if the search query has a match.
    /// </summary>
    Always = 1,
    /// <summary>
    /// Never return no-match report even if search query has no match.
    /// </summary>
    No = 99
  }

The Swagger documentation will end up showing a description of each enum member as part of the description of the enum type itself:

swagger doc screenshot

Upvotes: 8

Tobias
Tobias

Reputation: 2139

Unfortunately this does not seem to be supported in the OpenAPI specification. There is an open Github issue for this.

There is also an open Github issue for Swashbuckle. However, in this issue there is a workaround to create a schema filter, which at least shows the enum value comments in the enum type description.

Upvotes: 1

Tarik Tutuncu
Tarik Tutuncu

Reputation: 818

I solved this using a description attribute. Here is an example usage:

public enum GenderEnum
{
    [Description("Man Description")]
    Man = 1,

    [Description("Woman Description")]
    Woman = 2
}

Upvotes: -6

Related Questions