Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ public static void IncludeXmlComments(
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; }
}
}
}