Swagger is a great thing! It allows us to easily see the API of our service, generate a client for it in different languages and even work with the service through the UI. In ASP.NET Core, we have NuGet package Swashbuckle.AspNetCore for the support of Swagger. But there is one thing I don't like about this implementation. Swashbuckle can show me descriptions of methods, parameters, and classes based on XML comments in the .NET code. But it does not show the descriptions of the enum members. Here, I'll show how to add them.
Swagger is a great thing! It allows us to easily see the API of our service, generate a client for it in different languages and even work with the service through the UI. In ASP.NET Core, we have NuGet package Swashbuckle.AspNetCore for the support of Swagger.
But there is one thing I don't like about this implementation. Swashbuckle can show me descriptions of methods, parameters, and classes based on XML comments in the .NET code. But it does not show the descriptions of the enum
members.
Let me show you what I mean.
Service Creation
I created a simple Web service:
[Route("api/data")]
[ApiController]
public class EnumsController : ControllerBase
{
[HttpGet]
public Task<Result> ExecuteOperation(int id, OperationType type)
{
return Task.FromResult(Result.Success);
}
[HttpPost]
public Task<IActionResult> Change(DataChange change)
{
return Task.FromResult<IActionResult>(Ok());
}
}
This controller makes extensive use of enum
s. It uses them as argument types, as method results, and as parts of more complex objects:
public enum OperationType
{
Do,
Undo
}
public enum Result
{
Success,
Failure
}
public class DataChange
{
public int Id { get; set; }
public Sources Source { get; set; }
public OperationType Operation { get; set; }
}
public enum Sources
{
Memory,
Database
}
I installed Swashbuckle.AspNetCore
NuGet package to support Swagger. Now I must configure it. It can be done in the Startup
file:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c => {
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI();
app.UseRouting();
...
}
}
Now we can start our service. And at the address http://localhost:5000/swagger/index.html, we'll find a description of it:
But now, all our enumerations are represented by mere numbers:
I'd prefer to provide string
values for enumerations. They at least make some sense to users, unlike these numbers.
To do this, we need to make some changes to the Swashbuckle configuration. I installed another NuGet package Swashbuckle.AspNetCore.Newtonsoft
. And here are my changes. I changed:
services.AddControllers();
to:
services.AddControllers().AddNewtonsoftJson(o =>
{
o.SerializerSettings.Converters.Add(new StringEnumConverter
{
CamelCaseText = true
});
});
Now our enumerations are represented as string
s:
But even now, I see one drawback. Swagger UI does not show me XML comments assigned to the members of enumerations.
Description of Enumeration Types
Let's see how we can get them. I did a bit of searching on the internet but found almost nothing. Although there is one very interesting piece of code. Unfortunately, it matches the old version of Swashbuckle. Nevertheless, it is a good starting point.
Swashbuckle allows us to interfere with the documentation generation process. For example, there is an interface ISchemaFilter
, which allows you to change the schema description of individual classes. The following code shows how to change the descriptions of enumerations:
public class EnumTypesSchemaFilter : ISchemaFilter
{
private readonly XDocument _xmlComments;
public EnumTypesSchemaFilter(string xmlPath)
{
if(File.Exists(xmlPath))
{
_xmlComments = XDocument.Load(xmlPath);
}
}
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (_xmlComments == null) return;
if(schema.Enum != null && schema.Enum.Count > 0 &&
context.Type != null && context.Type.IsEnum)
{
schema.Description += "<p>Members:</p><ul>";
var fullTypeName = context.Type.FullName;
foreach (var enumMemberName in schema.Enum.OfType<OpenApiString>().
Select(v => v.Value))
{
var fullEnumMemberName = $"F:{fullTypeName}.{enumMemberName}";
var enumMemberComments = _xmlComments.Descendants("member")
.FirstOrDefault(m => m.Attribute("name").Value.Equals
(fullEnumMemberName, StringComparison.OrdinalIgnoreCase));
if (enumMemberComments == null) continue;
var summary = enumMemberComments.Descendants("summary").FirstOrDefault();
if (summary == null) continue;
schema.Description += $"<li><i>{enumMemberName}</i> -
{summary.Value.Trim()}</li>";
}
schema.Description += "</ul>";
}
}
}
The constructor of this class accepts path to the file with XML comments. I read its contents into the XDocument
object. Then in Apply
method, we check if the current type is enumeration. For such types, we add an HTML list with descriptions of all the members of this enumeration to the type description.
Now we must plug the class of our filter into Swashbuckle:
services.AddSwaggerGen(c => {
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath);
});
It can be done using the SchemaFilter
method in the configuration section for Swagger. I pass the path to the file with XML comments to this method. This value will be passed to the constructor of the EnumTypesSchemaFilter
class.
Now the Swagger UI shows the enum
descriptions as follows:
Description of Enumeration Parameters
It looks better. But not good enough. Our controller has a method that takes an enum
as a parameter:
public Task<Result> ExecuteOperation(int id, OperationType type)
Let's see how the Swagger UI shows this:
As you can see, there is no description of the enum
members here. The reason is that we see here a description of the parameter, not a description of the parameter type. So this is an XML comment for the parameter, not for the parameter type.
But we can solve this problem too. To do this, we will use another Swashbuckle interface - IDocumentFilter
. Here is our implementation:
public class EnumTypesDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var path in swaggerDoc.Paths.Values)
{
foreach(var operation in path.Operations.Values)
{
foreach(var parameter in operation.Parameters)
{
var schemaReferenceId = parameter.Schema.Reference?.Id;
if (string.IsNullOrEmpty(schemaReferenceId)) continue;
var schema = context.SchemaRepository.Schemas[schemaReferenceId];
if (schema.Enum == null || schema.Enum.Count == 0) continue;
parameter.Description += "<p>Variants:</p>";
int cutStart = schema.Description.IndexOf("<ul>");
int cutEnd = schema.Description.IndexOf("</ul>") + 5;
parameter.Description += schema.Description
.Substring(cutStart, cutEnd - cutStart);
}
}
}
}
}
Here, in the Apply
method, we iterate through all the parameters of all the methods of all the controllers. Unfortunately, in this interface, we do not have access to the parameter type, only to the schema of this type (at least I think so). That's why I just cut the description of the enum
members from the string
with the parameter type description.
Our class must be registered in the same way using the DocumentFilter
method:
services.AddSwaggerGen(c => {
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath);
c.DocumentFilter<EnumTypesDocumentFilter>();
});
Here's what the parameter description in the Swagger UI looks like now:
Conclusion
The code presented in this article is more of a sketch than a final version. But I hope it can be useful and allow you to add a description of the enum
members to your Swagger UI. Thank you!
You can read more of my articles on my blog.
P.S. You can find the whole code of the project on GitHub.