Swashbuckle.WebApi
Swashbuckle.WebApi copied to clipboard
Documentation is not generated for routes with generic parameters or return types.
In our organization, we have generic abstract controllers that we use as base controllers for all our data access APIs. The current algorithm that swagger uses to generate the documentation doesn't know how to handle this scenario because the method documentation signature has generic heuristics.
Here is an example of what the controller looks like with one of the generic methods included.
public abstract class ReadController<TEntity, TKey, TContext, TFilter, TInclude> :
ApiController
where TEntity : class, new()
where TContext : IDbContext
where TFilter : class, new()
where TInclude : struct, IComparable, IConvertible, IFormattable
{
/// <summary>
/// Gets the entity by the given key.
/// </summary>
[Route("{key}")]
[HttpGet]
public virtual async Task<HttpResponseMessage> Get(TKey key, [FromUri] ICollection<TInclude?> includes = null, [FromUri] ICollection<string> fields = null)
{
}
}
The XML that is generated for this method output by MSbuild is as follows:
<member name="M:Jane.Data.WebHost.ReadController`5.Get(`1,System.Collections.Generic.ICollection{System.Nullable{`4}},System.Collections.Generic.ICollection{System.String})">
<summary>
Gets the entity by the given key.
</summary>
</member>
Even though we are including this XML file in the bin folder and ensure it gets added through the swagger call IncludeXmlComments(), no documentation is added to the route in the SwaggerUI.
The issue lies in the ApplyXmlActionComments.Apply() method in Swashbuckle.Swagger; essentially, it just fails to comprehend this method signature in the XML file and therefore cannot match the generic method to its documentation.
Would be nice to have this scenario accounted for so as to make generic routes display documentation as desired.
I have the same issue!
Same issue here. When is 6.0.0 planned for?
Same issue - only occuring when a generic type is used as a parameter. Outputs being generic is just fine.
Same here. Can't believe this is still open. Any workaround?
Can anyone post a link to a sample project that reproduces this issue?
As far as a workaround, one way that might help is to implement an IOperationFilter and fill it in
public class MyOperationFilter : IOperationFilter
{
public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription )
{
if ( string.IsNullOrEmpty( operation.summary ) )
{
// Manually set the documentation summary if swashbuckle couldn't figure it out. Swashbuckle 5 isn't able to figure these out since they have Generic parameters
if ( apiDescription.HttpMethod.Method == "POST" )
{
operation.summary = "POST endpoint. Use this to add a record";
}
else if ( apiDescription.HttpMethod.Method == "PUT" )
{
operation.summary = "PUT endpoint. Use this to update a record";
}
}
}
}
@MikeDPeterson a workaround that I would not recommend ... but yes it is a valid workaround! I would love to troubleshoot this. do you have a simple project that can reproduce this bug?
@heldersepu I can't post a link to the project unfortunately because it is under VPN. But I can describe my situation.
There is a web API project, with an abstract base controller which has CRUD operations.
public class TestController : BaseController<TestType>
Methods of BaseController is like this:
public virtual TEntity Post(TEntity entity)
public virtual TEntity Get(string id)
public virtual TEntity Put(TEntity entity)
public virtual Task Delete(string id)
XML comments for GET and DELETE is visible. But it is not visible for PUT and POST as they have a generic type as a method parameter.
@zibrohimov Can you add the project to GitHub?
@heldersepu I'm afraid I can't because I'm not allowed to do so. But I can give you relevant information about my projects nature if need any details. Here is a screenshot about how does that look. https://www.screencast.com/t/koF4v4HNt
@zibrohimov Just help me out troubleshooting, create a simple project that reproduces the issue and add it to your GitHub
@heldersepu https://github.com/zibrohimov/SwashbuckleDemo
The error is here: https://github.com/domaindrivendev/Swashbuckle/blob/master/Swashbuckle.Core/Swagger/XmlComments/XmlCommentsIdHelper.cs#L63
that if (type.IsGenericType) always return false
If anyone wants to take on the challenge of figure out why IsGenericType does not work as expected here is a small console application reproducing the issue:
using System;
abstract class Parent<T> {
public virtual T Put(T id, char value) {
return id;}}
class Child : Parent<int> {
public virtual string Get(Guid guid) {
return "aei";}}
class Program {
static void Main(string[] args) {
foreach (var methodInfo in typeof(Child).GetMethods()) {
if (methodInfo.IsVirtual && methodInfo.IsSecurityCritical) {
Console.WriteLine(methodInfo.Name);
foreach (var param in methodInfo.GetParameters()) {
Console.Write(" " + param.Name + " IsGenericType=");
Console.WriteLine(param.ParameterType.IsGenericType);}}}
Console.ReadKey();}}
http://rextester.com/DUPC80090
I ran into the same issue and found the cause.
Your controller has a generic type argument TKey that is used as argument in your Get method. In the xml documentation this argument is not expanded. You'll see something like
member name="M:Your.Namespace.ReadController`5.Get(`1, [... more ...])"
In XmlCommentsIdHelper.AppendMethodName I've added a workaround for this (not thoroughly tested, but it works for me)
private static void AppendMethodName(MethodInfo methodInfo, StringBuilder builder)
{
builder.Append(methodInfo.Name);
var parameters = methodInfo.GetParameters();
if (parameters.Length == 0) return;
builder.Append("(");
foreach (var param in parameters)
{
//Change: if the parameter type is a generic type argument replace it with the placeholder instead of the actual type
if (methodInfo.DeclaringType.GenericTypeArguments.Contains(param.ParameterType))
{
builder.Append($"`{Array.IndexOf(methodInfo.DeclaringType.GenericTypeArguments, param.ParameterType)}");
}
else //do it the regular way
{
AppendFullTypeName(param.ParameterType, builder, true);
}
builder.Append(",");
}
builder.Replace(",", ")", builder.Length - 1, 1);
}
Ultimately, I don't think this is related to the use of a generic parameter. SB supports generic parameters and return types and has passing tests to prove it:
https://github.com/domaindrivendev/Swashbuckle/blob/v5.6.0/Swashbuckle.Tests/Swagger/XmlCommentsTests.cs#L192
I think it's actually due to the use of inherited/abstract actions. By design, SB will not pull XML comments from inherited actions. Doing so would add a ton of complexity and have a significant impact on performance. Furthermore, I don't think it makes a ton of sense. Abstract/generic actions can, by their nature, only be accompanied by fairly abstract descriptions and I don't think these are particularly suitable for describing concrete API actions. "Gets the entity by the given key" isn't a particularly useful description.
With that said, I would recommend using a simple OperationFilter instead of XML comments to describe specific implementations of generic actions. Check out the following for an example. It's from the ASP.NET Core-specific SB project, has some minor syntactic differences, but should give you the idea:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v1.0.0/test/WebSites/GenericControllers/Swagger/ApplySummariesOperationFilter.cs
Hi,
I'm hitting a somewhat related issue. I've been able to narrow down the repro to:
- member function being part of a templated abstract class and
- presence of a nullable type in the function's parameters: reference and value type parameters work fine - only nullable value types don't.
code snippets:
/// <summary>Gets a blob</summary>
/// <param name="id">Blob Id</param>
/// <param name="includes">Comma separated list of what to include, for example "ContentInfo,Description". Defaults to None</param>
/// <returns>The blob's metadata</returns>
[Route("blobsfail/{id}")]
[HttpGet]
[ResponseType(typeof(BlobMetadataRetrieve))]
public IHttpActionResult RetrieveBlobMetadataByIdFail(Guid id, BlobMetadataIncludes? includes = null)
{
return RunGet(() => blobRepo.RetrieveById(id, includes.GetValueOrDefault(BlobMetadataIncludes.None)));
}
/// <summary>Gets a blob</summary>
/// <param name="id">Blob Id</param>
/// <param name="includes">Comma separated list of what to include, for example "ContentInfo,Description". Defaults to None</param>
/// <returns>The blob's metadata</returns>
[Route("blobsok/{id}")]
[HttpGet]
[ResponseType(typeof(BlobMetadataRetrieve))]
public IHttpActionResult RetrieveBlobMetadataByIdOk(Guid id, BlobMetadataIncludes includes = BlobMetadataIncludes.None)
{
return RunGet(() => blobRepo.RetrieveById(id, includes));
}
This is the resulting xmldoc:
<member name="M:AssemblyName.Controllers.BlobsController`1.RetrieveBlobMetadataByIdFail(System.Guid,System.Nullable{AssemblyName.DataModel.BlobMetadataIncludes})">
<summary>Gets a blob</summary>
<param name="id">Blob Id</param>
<param name="includes">Comma separated list of what to include, for example "ContentInfo,Description". Defaults to None</param>
<returns>The blob's metadata</returns>
</member>
<member name="M:AssemblyName.Controllers.BlobsController`1.RetrieveBlobMetadataByIdOk(System.Guid,AssemblyName.DataModel.BlobMetadataIncludes)">
<summary>Gets a blob</summary>
<param name="id">Blob Id</param>
<param name="includes">Comma separated list of what to include, for example "ContentInfo,Description". Defaults to None</param>
<returns>The blob's metadata</returns>
</member>
The blobsfail route does not get its documentation populated (route summary and doc for parameters), while the blobsok one gets it. Screenshot:

Upgrading to the latest version on NuGet.org (5.6.0) did not help.
Any ideas or workarounds? Routing does not like value types with defaults, which is why we have to use nullable. Nullable works fine as long as they are not parameters of functions in templated classes.
Thanks,
Greg
@vdbg I tried to reproduce with your comments... since you didn't share the code for the "templated abstract class" I replace it with an int ...and it works fine for me: http://swashbuckletest.azurewebsites.net/swagger/ui/index?filter=Blob Code behind that is here: https://github.com/heldersepu/SwashbuckleTest/commit/7817595a15c3e88999cc0e525ee69a9ac2b2ea10
@heldersepu : updated the repro to add the "member function being part of a templated abstract class" part (both this and nullable param are required)
https://github.com/vdbg/SwashbuckleTest/commit/7d4f48f41f01904115022699f72c3ab7b1dafdf9

@vdbg Thanks that gets me a lot closer, I'm trying to get a minimal Controller reproducing this issue... So far it looks like that inheritance is not the problem https://github.com/heldersepu/SwashbuckleTest/commit/a356655fee6c3575e50f367b4a674754d54334d6
@vdbg Here is my minimal case to reproduce your scenario:
using System.Web.Http;
namespace Swagger_Test.Controllers
{
public abstract class Blob<T> : ApiController
{
/// <summary> Get a Bad Blob </summary>
public string GetBad(int? x) { return "Bad"; }
/// <summary> Get an Ok Blob </summary>
public string PostOk(int x) { return "Ok"; }
}
public class Foo { }
public class BlobController : Blob<Foo> { }
}
The problem is the combination of generics and the nullable param, If I remove any of those it work for me... Now the real troubleshooting can start!
For any one interested on scoring an easy PR the bug is here: https://github.com/domaindrivendev/Swashbuckle/blob/master/Swashbuckle.Core/Swagger/XmlComments/XmlCommentsIdHelper.cs#L75
That Replace must be done only to the args not the whole builder
Thanks for looking into it ! What are the odds / timelines of having a fix pushed on NuGet.org ?
@vdbg I made the fix on my repo, now it loads correctly: http://swashbuckletest.azurewebsites.net/swagger/ui/index?filter=Blob
My package has a different name in nuget: https://www.nuget.org/packages/Swagger-Net/
@heldersepu : what's the difference between Swagger-Net and Swashbuckle ?
Swagger-Net was a fork of Swashbuckle. ...but I decided to detach it and rename it, that way I can fix bugs, add features and do releases at a much faster pace.
If you have time try both post back the differences you see?
Hello, so I tried the latest nuget package and I'm still hitting the same issue when it comes to enum type parameters (both nullable and non-nullable).

I created a minimal repro of the same here...
@yojagad you are bringing something new to the equation. It is not related to generics or the nullable param, I thing is because the nested enum...
@yojagad the minimal repro is much appreciated! I should have a solution shortly...
@yojagad your issue has been fixed.