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 @@ -541,13 +541,15 @@ public static void IncludeXmlComments(
bool includeControllerXmlComments = false)
{
var xmlDoc = xmlDocFactory();
swaggerGenOptions.ParameterFilter<XmlCommentsParameterFilter>(xmlDoc);
swaggerGenOptions.RequestBodyFilter<XmlCommentsRequestBodyFilter>(xmlDoc);
swaggerGenOptions.OperationFilter<XmlCommentsOperationFilter>(xmlDoc);
swaggerGenOptions.SchemaFilter<XmlCommentsSchemaFilter>(xmlDoc);
var xmlDocMembers = XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc);

swaggerGenOptions.AddParameterFilterInstance(new XmlCommentsParameterFilter(xmlDocMembers));
swaggerGenOptions.AddRequestBodyFilterInstance(new XmlCommentsRequestBodyFilter(xmlDocMembers));
swaggerGenOptions.AddOperationFilterInstance(new XmlCommentsOperationFilter(xmlDocMembers));
swaggerGenOptions.AddSchemaFilterInstance(new XmlCommentsSchemaFilter(xmlDocMembers));

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

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Linq;
using System.Xml.XPath;

namespace Swashbuckle.AspNetCore.SwaggerGen;

internal static class XPathNavigatorExtensions
{
private const string EmptyNamespace = "";

internal static XPathNodeIterator SelectChildren(this XPathNavigator navigator, string name)
{
return navigator.SelectChildren(name, EmptyNamespace);
}

internal static string GetAttribute(this XPathNavigator navigator, string name)
{
return navigator.GetAttribute(name, EmptyNamespace);
}

internal static XPathNavigator SelectFirstChild(this XPathNavigator navigator, string name)
{
return navigator.SelectChildren(name, EmptyNamespace)
?.OfType<XPathNavigator>()
.FirstOrDefault();
}

internal static XPathNavigator SelectFirstChildWithAttribute(this XPathNavigator navigator, string childName, string attributeName, string attributeValue)
{
return navigator.SelectChildren(childName, EmptyNamespace)
?.OfType<XPathNavigator>()
.FirstOrDefault(n => n.GetAttribute(attributeName, EmptyNamespace) == attributeValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@ namespace Swashbuckle.AspNetCore.SwaggerGen
{
public class XmlCommentsDocumentFilter : IDocumentFilter
{
private const string MemberXPath = "/doc/members/member[@name='{0}']";
private const string SummaryTag = "summary";

private readonly XPathNavigator _xmlNavigator;
private readonly IReadOnlyDictionary<string, XPathNavigator> _xmlDocMembers;
private readonly SwaggerGeneratorOptions _options;

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

public XmlCommentsDocumentFilter(XPathDocument xmlDoc, SwaggerGeneratorOptions options)
public XmlCommentsDocumentFilter(XPathDocument xmlDoc, SwaggerGeneratorOptions options) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), options)
{
_xmlNavigator = xmlDoc.CreateNavigator();
}

internal XmlCommentsDocumentFilter(IReadOnlyDictionary<string, XPathNavigator> xmlDocMembers, SwaggerGeneratorOptions options)
{
_xmlDocMembers = xmlDocMembers;
_options = options;
}

Expand All @@ -38,22 +41,22 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
foreach (var nameAndType in controllerNamesAndTypes)
{
var memberName = XmlCommentsNodeNameHelper.GetMemberNameForType(nameAndType.Value);
var typeNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, memberName));

if (typeNode != null)
if (!_xmlDocMembers.TryGetValue(memberName, out var typeNode))
{
var summaryNode = typeNode.SelectSingleNode(SummaryTag);
if (summaryNode != null)
continue;
}

var summaryNode = typeNode.SelectFirstChild(SummaryTag);
if (summaryNode != null)
{
swaggerDoc.Tags ??= new List<OpenApiTag>();

swaggerDoc.Tags.Add(new OpenApiTag
{
if (swaggerDoc.Tags == null)
swaggerDoc.Tags = new List<OpenApiTag>();

swaggerDoc.Tags.Add(new OpenApiTag
{
Name = nameAndType.Key,
Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml)
});
}
Name = nameAndType.Key,
Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml)
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.XPath;

namespace Swashbuckle.AspNetCore.SwaggerGen;

internal static class XmlCommentsDocumentHelper
{
internal static Dictionary<string, XPathNavigator> CreateMemberDictionary(XPathDocument xmlDoc)
{
var members = xmlDoc.CreateNavigator()
.SelectFirstChild("doc")
?.SelectFirstChild("members")
?.SelectChildren("member")
?.OfType<XPathNavigator>();

if (members == null)
{
return new Dictionary<string, XPathNavigator>();
}

return members.ToDictionary(memberNode => memberNode.GetAttribute("name"));
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
using System;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.XPath;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen
{
public class XmlCommentsOperationFilter : IOperationFilter
{
private readonly XPathNavigator _xmlNavigator;
private readonly IReadOnlyDictionary<string, XPathNavigator> _xmlDocMembers;

public XmlCommentsOperationFilter(XPathDocument xmlDoc)
public XmlCommentsOperationFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc))
{
_xmlNavigator = xmlDoc.CreateNavigator();
}

internal XmlCommentsOperationFilter(IReadOnlyDictionary<string, XPathNavigator> xmlDocMembers)
{
_xmlDocMembers = xmlDocMembers;
}

public void Apply(OpenApiOperation operation, OperationFilterContext context)
Expand All @@ -32,37 +37,41 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
private void ApplyControllerTags(OpenApiOperation operation, Type controllerType)
{
var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(controllerType);
var responseNodes = _xmlNavigator.Select($"/doc/members/member[@name='{typeMemberName}']/response");

if (!_xmlDocMembers.TryGetValue(typeMemberName, out var methodNode)) return;

var responseNodes = methodNode.SelectChildren("response");
ApplyResponseTags(operation, responseNodes);
}

private void ApplyMethodTags(OpenApiOperation operation, MethodInfo methodInfo)
{
var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo);
var methodNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{methodMemberName}']");

if (methodNode == null) return;
if (!_xmlDocMembers.TryGetValue(methodMemberName, out var methodNode)) return;

var summaryNode = methodNode.SelectSingleNode("summary");
var summaryNode = methodNode.SelectFirstChild("summary");
if (summaryNode != null)
operation.Summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);

var remarksNode = methodNode.SelectSingleNode("remarks");
var remarksNode = methodNode.SelectFirstChild("remarks");
if (remarksNode != null)
operation.Description = XmlCommentsTextHelper.Humanize(remarksNode.InnerXml);

var responseNodes = methodNode.Select("response");
var responseNodes = methodNode.SelectChildren("response");
ApplyResponseTags(operation, responseNodes);
}

private void ApplyResponseTags(OpenApiOperation operation, XPathNodeIterator responseNodes)
{
while (responseNodes.MoveNext())
{
var code = responseNodes.Current.GetAttribute("code", "");
var response = operation.Responses.TryGetValue(code, out var operationResponse)
? operationResponse
: operation.Responses[code] = new OpenApiResponse();
var code = responseNodes.Current.GetAttribute("code");
if (!operation.Responses.TryGetValue(code, out var response))
{
response = new OpenApiResponse();
operation.Responses[code] = response;
}

response.Description = XmlCommentsTextHelper.Humanize(responseNodes.Current.InnerXml);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
using System.Reflection;
using Microsoft.OpenApi.Models;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.XPath;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen
{
public class XmlCommentsParameterFilter : IParameterFilter
{
private XPathNavigator _xmlNavigator;
private readonly IReadOnlyDictionary<string, XPathNavigator> _xmlDocMembers;

public XmlCommentsParameterFilter(XPathDocument xmlDoc)
public XmlCommentsParameterFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc))
{
_xmlNavigator = xmlDoc.CreateNavigator();
}

internal XmlCommentsParameterFilter(IReadOnlyDictionary<string, XPathNavigator> xmlDocMembers)
{
_xmlDocMembers = xmlDocMembers;
}

public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
Expand All @@ -28,18 +33,17 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
private void ApplyPropertyTags(OpenApiParameter parameter, ParameterFilterContext context)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(context.PropertyInfo);
var propertyNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']");

if (propertyNode == null) return;
if (!_xmlDocMembers.TryGetValue(propertyMemberName, out var propertyNode)) return;

var summaryNode = propertyNode.SelectSingleNode("summary");
var summaryNode = propertyNode.SelectFirstChild("summary");
if (summaryNode != null)
{
parameter.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
parameter.Schema.Description = null; // no need to duplicate
}

var exampleNode = propertyNode.SelectSingleNode("example");
var exampleNode = propertyNode.SelectFirstChild("example");
if (exampleNode == null) return;

parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, exampleNode.ToString());
Expand All @@ -57,14 +61,16 @@ private void ApplyParamTags(OpenApiParameter parameter, ParameterFilterContext c
if (targetMethod == null) return;

var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod);
var paramNode = _xmlNavigator.SelectSingleNode(
$"/doc/members/member[@name='{methodMemberName}']/param[@name='{context.ParameterInfo.Name}']");

if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode)) return;

XPathNavigator paramNode = propertyNode.SelectFirstChildWithAttribute("param", "name", context.ParameterInfo.Name);

if (paramNode != null)
{
parameter.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);

var example = paramNode.GetAttribute("example", "");
var example = paramNode.GetAttribute("example");
if (string.IsNullOrEmpty(example)) return;

parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, example);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
using System.Linq;
using Microsoft.OpenApi.Models;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.XPath;
using Microsoft.OpenApi.Models;

namespace Swashbuckle.AspNetCore.SwaggerGen
{
public class XmlCommentsRequestBodyFilter : IRequestBodyFilter
{
private readonly XPathNavigator _xmlNavigator;
private readonly IReadOnlyDictionary<string, XPathNavigator> _xmlDocMembers;

public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc))
{
}

public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc)
internal XmlCommentsRequestBodyFilter(IReadOnlyDictionary<string, XPathNavigator> xmlDocMembers)
{
_xmlNavigator = xmlDoc.CreateNavigator();
_xmlDocMembers = xmlDocMembers;
}

public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
Expand Down Expand Up @@ -42,20 +47,16 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte
private void ApplyPropertyTags(OpenApiRequestBody requestBody, RequestBodyFilterContext context, PropertyInfo propertyInfo)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(propertyInfo);
var propertyNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']");

if (propertyNode is null)
{
return;
}
if (!_xmlDocMembers.TryGetValue(propertyMemberName, out var propertyNode)) return;

var summaryNode = propertyNode.SelectSingleNode("summary");
var summaryNode = propertyNode.SelectFirstChild("summary");
if (summaryNode is not null)
{
requestBody.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}

var exampleNode = propertyNode.SelectSingleNode("example");
var exampleNode = propertyNode.SelectFirstChild("example");
if (exampleNode is null || requestBody.Content?.Count is 0)
{
return;
Expand Down Expand Up @@ -87,14 +88,16 @@ private void ApplyParamTags(OpenApiRequestBody requestBody, RequestBodyFilterCon
}

var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod);
var paramNode = _xmlNavigator.SelectSingleNode(
$"/doc/members/member[@name='{methodMemberName}']/param[@name='{parameterInfo.Name}']");

if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode)) return;

var paramNode = propertyNode.SelectFirstChildWithAttribute("param", "name", parameterInfo.Name);

if (paramNode is not null)
{
requestBody.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);

var example = paramNode.GetAttribute("example", "");
var example = paramNode.GetAttribute("example");
if (!string.IsNullOrEmpty(example))
{
foreach (var mediaType in requestBody.Content.Values)
Expand Down
Loading