Skip to content

Commit

Permalink
Fix XmlCommentsDocumentFilter tag handling (#2718)
Browse files Browse the repository at this point in the history
Switch to using TagsSelector from SwaggerGeneratorOptions to get the tag for controller comments.
  • Loading branch information
MerickOWA committed Apr 18, 2024
1 parent 8a23f0a commit 572bc32
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ public static void SupportNonNullableReferenceTypes(this SwaggerGenOptions swagg
swaggerGenOptions.SchemaFilter<XmlCommentsSchemaFilter>(xmlDoc);

if (includeControllerXmlComments)
swaggerGenOptions.DocumentFilter<XmlCommentsDocumentFilter>(xmlDoc);
swaggerGenOptions.DocumentFilter<XmlCommentsDocumentFilter>(xmlDoc, swaggerGenOptions.SwaggerGeneratorOptions);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,27 @@ public class XmlCommentsDocumentFilter : IDocumentFilter
private const string SummaryTag = "summary";

private readonly XPathNavigator _xmlNavigator;
private readonly SwaggerGeneratorOptions _options;

public XmlCommentsDocumentFilter(XPathDocument xmlDoc)
: this(xmlDoc, null)
{
}

public XmlCommentsDocumentFilter(XPathDocument xmlDoc, SwaggerGeneratorOptions options)
{
_xmlNavigator = xmlDoc.CreateNavigator();
_options = options;
}

public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Collect (unique) controller names and types in a dictionary
var controllerNamesAndTypes = context.ApiDescriptions
.Select(apiDesc => apiDesc.ActionDescriptor as ControllerActionDescriptor)
.Where(actionDesc => actionDesc != null)
.GroupBy(actionDesc => actionDesc.ControllerName)
.Select(group => new KeyValuePair<string, Type>(group.Key, group.First().ControllerTypeInfo.AsType()));
.Select(apiDesc => new { ApiDesc = apiDesc, ActionDesc = apiDesc.ActionDescriptor as ControllerActionDescriptor })
.Where(x => x.ActionDesc != null)
.GroupBy(x => _options?.TagsSelector(x.ApiDesc).FirstOrDefault() ?? x.ActionDesc.ControllerName)
.Select(group => new KeyValuePair<string, Type>(group.Key, group.First().ActionDesc.ControllerTypeInfo.AsType()));

foreach (var nameAndType in controllerNamesAndTypes)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System.Xml.XPath;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.OpenApi.Models;
using Xunit;
using Swashbuckle.AspNetCore.TestSupport;
Expand Down Expand Up @@ -51,5 +55,114 @@ private static XmlCommentsDocumentFilter Subject()
return new XmlCommentsDocumentFilter(new XPathDocument(xmlComments));
}
}

[Fact]
public void Uses_Proper_Tag_Name()
{
var expectedTagName = "AliasControllerWithXmlComments";
var options = new SwaggerGeneratorOptions();
var document = new OpenApiDocument();
var filterContext = new DocumentFilterContext(
new[]
{
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
RouteValues = new Dictionary<string, string> { { "controller", expectedTagName } }
}
},
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
RouteValues = new Dictionary<string, string> { { "controller", expectedTagName } }
}
}
},
null,
null);

Subject(options).Apply(document, filterContext);

var tag = Assert.Single(document.Tags);
Assert.Equal(expectedTagName, tag.Name);
}

[Fact]
public void Uses_Proper_Tag_Name_With_Custom_TagSelector()
{
var expectedTagName = "AliasControllerWithXmlComments";
var options = new SwaggerGeneratorOptions { TagsSelector = apiDesc => new[] { expectedTagName } };
var document = new OpenApiDocument();
var filterContext = new DocumentFilterContext(
new[]
{
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
}
},
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
}
}
},
null,
null);

Subject(options).Apply(document, filterContext);

var tag = Assert.Single(document.Tags);
Assert.Equal(expectedTagName, tag.Name);
}

private static XmlCommentsDocumentFilter Subject(SwaggerGeneratorOptions options)
{
using (var xmlComments = File.OpenText($"{typeof(FakeControllerWithXmlComments).Assembly.GetName().Name}.xml"))
{
return new XmlCommentsDocumentFilter(new XPathDocument(xmlComments), options);
}
}

[Fact]
public void Ensure_IncludeXmlComments_Adds_Filter_To_Options()
{
var services = new ServiceCollection();
services.AddSingleton<IWebHostEnvironment, DummyHostEnvironment>();
services.AddSwaggerGen(c =>
{
c.IncludeXmlComments(
$"{typeof(FakeControllerWithXmlComments).Assembly.GetName().Name}.xml",
includeControllerXmlComments: true);
});

using var provider = services.BuildServiceProvider();
var options = provider.GetService<Microsoft.Extensions.Options.IOptions<SwaggerGeneratorOptions>>().Value;

Assert.NotNull(options);
Assert.Contains(options.DocumentFilters, x => x is XmlCommentsDocumentFilter);
}

private sealed class DummyHostEnvironment : IWebHostEnvironment
{
public string WebRootPath { get; set; }
public IFileProvider WebRootFileProvider { get; set; }
public string ApplicationName { get; set; }
public IFileProvider ContentRootFileProvider { get; set; }
public string ContentRootPath { get; set; }
public string EnvironmentName { get; set; }
}
}
}

0 comments on commit 572bc32

Please sign in to comment.