Skip to content

Commit c706576

Browse files
Resolve/refactor issue picking up Xml Comments for some types
1 parent a245ffe commit c706576

12 files changed

+291
-175
lines changed

src/Swashbuckle.SwaggerGen/Annotations/TypeExtensions.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Text;
4+
5+
namespace Swashbuckle.SwaggerGen.Annotations
6+
{
7+
public class XmlCommentsIdHelper
8+
{
9+
public static string GetCommentIdForMethod(MethodInfo methodInfo)
10+
{
11+
var builder = new StringBuilder("M:");
12+
AppendFullTypeName(methodInfo.DeclaringType, builder);
13+
builder.Append(".");
14+
AppendMethodName(methodInfo, builder);
15+
16+
return builder.ToString();
17+
}
18+
19+
public static string GetCommentIdForType(Type type)
20+
{
21+
var builder = new StringBuilder("T:");
22+
AppendFullTypeName(type, builder, expandGenericArgs: false);
23+
24+
return builder.ToString();
25+
}
26+
27+
public static string GetCommentIdForProperty(PropertyInfo propertyInfo)
28+
{
29+
var builder = new StringBuilder("P:");
30+
AppendFullTypeName(propertyInfo.DeclaringType, builder);
31+
builder.Append(".");
32+
AppendPropertyName(propertyInfo, builder);
33+
34+
return builder.ToString();
35+
}
36+
37+
private static void AppendFullTypeName(Type type, StringBuilder builder, bool expandGenericArgs = false)
38+
{
39+
builder.Append(type.Namespace);
40+
builder.Append(".");
41+
AppendTypeName(type, builder, expandGenericArgs);
42+
}
43+
44+
private static void AppendTypeName(Type type, StringBuilder builder, bool expandGenericArgs)
45+
{
46+
if (type.IsNested)
47+
{
48+
AppendTypeName(type.DeclaringType, builder, false);
49+
builder.Append(".");
50+
}
51+
52+
builder.Append(type.Name);
53+
54+
if (expandGenericArgs)
55+
ExpandGenericArgsIfAny(type, builder);
56+
}
57+
58+
public static void ExpandGenericArgsIfAny(Type type, StringBuilder builder)
59+
{
60+
if (type.GetTypeInfo().IsGenericType)
61+
{
62+
var genericArgsBuilder = new StringBuilder("{");
63+
64+
var genericArgs = type.GetGenericArguments();
65+
foreach (var argType in genericArgs)
66+
{
67+
AppendFullTypeName(argType, genericArgsBuilder, true);
68+
genericArgsBuilder.Append(",");
69+
}
70+
genericArgsBuilder.Replace(",", "}", genericArgsBuilder.Length - 1, 1);
71+
72+
builder.Replace(string.Format("`{0}", genericArgs.Length), genericArgsBuilder.ToString());
73+
}
74+
else if (type.IsArray)
75+
ExpandGenericArgsIfAny(type.GetElementType(), builder);
76+
}
77+
78+
private static void AppendMethodName(MethodInfo methodInfo, StringBuilder builder)
79+
{
80+
builder.Append(methodInfo.Name);
81+
82+
var parameters = methodInfo.GetParameters();
83+
if (parameters.Length == 0) return;
84+
85+
builder.Append("(");
86+
foreach (var param in parameters)
87+
{
88+
AppendFullTypeName(param.ParameterType, builder, true);
89+
builder.Append(",");
90+
}
91+
builder.Replace(",", ")", builder.Length - 1, 1);
92+
}
93+
94+
private static void AppendPropertyName(PropertyInfo propertyInfo, StringBuilder builder)
95+
{
96+
builder.Append(propertyInfo.Name);
97+
}
98+
}
99+
}

src/Swashbuckle.SwaggerGen/Annotations/XmlCommentsModelFilter.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ namespace Swashbuckle.SwaggerGen.Annotations
66
{
77
public class XmlCommentsModelFilter : IModelFilter
88
{
9-
private const string TypeExpression = "/doc/members/member[@name='T:{0}']";
10-
private const string SummaryExpression = "summary";
11-
private const string PropertyExpression = "/doc/members/member[@name='P:{0}.{1}']";
9+
private const string MemberXPath = "/doc/members/member[@name='{0}']";
10+
private const string SummaryTag = "summary";
1211

1312
private readonly XPathNavigator _xmlNavigator;
1413

@@ -19,12 +18,12 @@ public XmlCommentsModelFilter(XPathDocument xmlDoc)
1918

2019
public void Apply(Schema model, ModelFilterContext context)
2120
{
22-
var typeXPath = string.Format(TypeExpression, context.SystemType.XmlLookupName());
23-
var typeNode = _xmlNavigator.SelectSingleNode(typeXPath);
21+
var commentId = XmlCommentsIdHelper.GetCommentIdForType(context.SystemType);
22+
var typeNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, commentId));
2423

2524
if (typeNode != null)
2625
{
27-
var summaryNode = typeNode.SelectSingleNode(SummaryExpression);
26+
var summaryNode = typeNode.SelectSingleNode(SummaryTag);
2827
if (summaryNode != null)
2928
model.Description = summaryNode.ExtractContent();
3029
}
@@ -38,17 +37,17 @@ public void Apply(Schema model, ModelFilterContext context)
3837
}
3938
}
4039

41-
private void ApplyPropertyComments(Schema propertySchema, MemberInfo memberInfo)
40+
private void ApplyPropertyComments(Schema propertySchema, PropertyInfo propertyInfo)
4241
{
43-
var propertyXPath =
44-
string.Format(PropertyExpression, memberInfo.DeclaringType.XmlLookupName(), memberInfo.Name);
45-
var propertyNode = _xmlNavigator.SelectSingleNode(propertyXPath);
42+
43+
var commentId = XmlCommentsIdHelper.GetCommentIdForProperty(propertyInfo);
44+
var propertyNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, commentId));
4645
if (propertyNode == null) return;
4746

48-
var propSummaryNode = propertyNode.SelectSingleNode(SummaryExpression);
49-
if (propSummaryNode != null)
47+
var summaryNode = propertyNode.SelectSingleNode(SummaryTag);
48+
if (summaryNode != null)
5049
{
51-
propertySchema.Description = propSummaryNode.ExtractContent();
50+
propertySchema.Description = summaryNode.ExtractContent();
5251
}
5352
}
5453
}

src/Swashbuckle.SwaggerGen/Annotations/XmlCommentsOperationFilter.cs

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Linq;
2-
using System.Reflection;
32
using System.Xml.XPath;
43
using Microsoft.AspNet.Mvc.Controllers;
54
using Swashbuckle.SwaggerGen.Generator;
@@ -8,11 +7,10 @@ namespace Swashbuckle.SwaggerGen.Annotations
87
{
98
public class XmlCommentsOperationFilter : IOperationFilter
109
{
11-
private const string MethodExpression = "/doc/members/member[@name='M:{0}.{1}{2}']";
12-
private const string SummaryExpression = "summary";
13-
private const string RemarksExpression = "remarks";
14-
private const string ParameterExpression = "param";
15-
private const string ResponseExpression = "response";
10+
private const string MemberXPath = "/doc/members/member[@name='{0}']";
11+
private const string SummaryTag = "summary";
12+
private const string RemarksTag = "remarks";
13+
private const string ParameterTag = "param";
1614

1715
private readonly XPathNavigator _xmlNavigator;
1816

@@ -26,42 +24,26 @@ public void Apply(Operation operation, OperationFilterContext context)
2624
var controllerActionDescriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor;
2725
if (controllerActionDescriptor == null) return;
2826

29-
var methodXPath = GetMethodXPath(controllerActionDescriptor.MethodInfo);
30-
var methodNode = _xmlNavigator.SelectSingleNode(methodXPath);
27+
var commentId = XmlCommentsIdHelper.GetCommentIdForMethod(controllerActionDescriptor.MethodInfo);
28+
var methodNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, commentId));
3129
if (methodNode == null) return;
3230

33-
var summaryNode = methodNode.SelectSingleNode(SummaryExpression);
31+
var summaryNode = methodNode.SelectSingleNode(SummaryTag);
3432
if (summaryNode != null)
3533
operation.Summary = summaryNode.ExtractContent();
3634

37-
var remarksNode = methodNode.SelectSingleNode(RemarksExpression);
35+
var remarksNode = methodNode.SelectSingleNode(RemarksTag);
3836
if (remarksNode != null)
3937
operation.Description = remarksNode.ExtractContent();
4038

4139
ApplyParamComments(operation, methodNode);
4240
}
4341

44-
private static string GetMethodXPath(MethodInfo methodInfo)
45-
{
46-
var typeLookupName = methodInfo.DeclaringType.XmlLookupName();
47-
var actionName = methodInfo.Name;
48-
49-
var paramLookupNames = methodInfo.GetParameters()
50-
.Select(paramInfo => paramInfo.ParameterType.XmlLookupNameWithTypeParameters())
51-
.ToArray();
52-
53-
var parameters = (paramLookupNames.Any())
54-
? string.Format("({0})", string.Join(",", paramLookupNames))
55-
: string.Empty;
56-
57-
return string.Format(MethodExpression, typeLookupName, actionName, parameters);
58-
}
59-
6042
private static void ApplyParamComments(Operation operation, XPathNavigator methodNode)
6143
{
6244
if (operation.Parameters == null) return;
6345

64-
var paramNodes = methodNode.Select(ParameterExpression);
46+
var paramNodes = methodNode.Select(ParameterTag);
6547
while (paramNodes.MoveNext())
6648
{
6749
var paramNode = paramNodes.Current;

test/Swashbuckle.SwaggerGen.Test/Annotations/SwaggerAttributesOperationFilterTests.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using Newtonsoft.Json;
44
using Xunit;
55
using Swashbuckle.SwaggerGen.Generator;
6-
using Swashbuckle.SwaggerGen.TestFixtures.ApiDescriptions;
6+
using Swashbuckle.SwaggerGen.TestFixtures;
77

88
namespace Swashbuckle.SwaggerGen.Annotations
99
{
@@ -16,7 +16,7 @@ public void Apply_AssignsProperties_FromActionAttribute()
1616
{
1717
OperationId = "foobar"
1818
};
19-
var filterContext = FilterContextFor(nameof(ActionFixtures.AnnotatedWithSwaggerOperation));
19+
var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerOperation));
2020

2121
Subject().Apply(operation, filterContext);
2222

@@ -33,8 +33,8 @@ public void Apply_DelegatesToSpecifiedFilter_IfControllerAnnotatedWithFilterAttr
3333
OperationId = "foobar"
3434
};
3535
var filterContext = FilterContextFor(
36-
nameof(ActionFixtures.ReturnsActionResult),
37-
nameof(ControllerFixtures.AnnotatedWithSwaggerOperationFilter)
36+
nameof(FakeActions.ReturnsActionResult),
37+
nameof(FakeControllers.AnnotatedWithSwaggerOperationFilter)
3838
);
3939

4040
Subject().Apply(operation, filterContext);
@@ -49,7 +49,7 @@ public void Apply_DelegatesToSpecifiedFilter_IfActionAnnotatedWithFilterAttribut
4949
{
5050
OperationId = "foobar"
5151
};
52-
var filterContext = FilterContextFor(nameof(ActionFixtures.AnnotatedWithSwaggerOperationFilter));
52+
var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerOperationFilter));
5353

5454
Subject().Apply(operation, filterContext);
5555

@@ -67,8 +67,8 @@ public void Apply_RemovesExistingResponses_IfControllerAnnotatedWithRemoveDefaul
6767
}
6868
};
6969
var filterContext = FilterContextFor(
70-
nameof(ActionFixtures.ReturnsActionResult),
71-
nameof(ControllerFixtures.AnnotatedWithSwaggerResponseRemoveDefaults)
70+
nameof(FakeActions.ReturnsActionResult),
71+
nameof(FakeControllers.AnnotatedWithSwaggerResponseRemoveDefaults)
7272
);
7373

7474
Subject().Apply(operation, filterContext);
@@ -86,7 +86,7 @@ public void Apply_RemovesExistingResponses_IfActionAnnotatedWithRemoveDefaultsAt
8686
{ "200", new Response() }
8787
}
8888
};
89-
var filterContext = FilterContextFor(nameof(ActionFixtures.AnnotatedWithSwaggerResponseRemoveDefaults));
89+
var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerResponseRemoveDefaults));
9090

9191
Subject().Apply(operation, filterContext);
9292

@@ -104,8 +104,8 @@ public void Apply_AddsResponses_FromControllerAttributes()
104104
}
105105
};
106106
var filterContext = FilterContextFor(
107-
nameof(ActionFixtures.ReturnsActionResult),
108-
nameof(ControllerFixtures.AnnotatedWithSwaggerResponses)
107+
nameof(FakeActions.ReturnsActionResult),
108+
nameof(FakeControllers.AnnotatedWithSwaggerResponses)
109109
);
110110

111111
Subject().Apply(operation, filterContext);
@@ -125,7 +125,7 @@ public void Apply_AddsResponses_FromActionAttributes()
125125
{ "200", new Response() }
126126
}
127127
};
128-
var filterContext = FilterContextFor(nameof(ActionFixtures.AnnotatedWithSwaggerResponses));
128+
var filterContext = FilterContextFor(nameof(FakeActions.AnnotatedWithSwaggerResponses));
129129

130130
Subject().Apply(operation, filterContext);
131131

@@ -145,8 +145,8 @@ public void Apply_FavorsActionAttributes_IfControllerAndActionBothAnnotatedWithS
145145
}
146146
};
147147
var filterContext = FilterContextFor(
148-
nameof(ActionFixtures.AnnotatedWithSwaggerResponses),
149-
nameof(ControllerFixtures.AnnotatedWithSwaggerResponses)
148+
nameof(FakeActions.AnnotatedWithSwaggerResponses),
149+
nameof(FakeControllers.AnnotatedWithSwaggerResponses)
150150
);
151151

152152
Subject().Apply(operation, filterContext);

0 commit comments

Comments
 (0)