Swashbuckle.WebApi
Swashbuckle.WebApi copied to clipboard
Swashbuckle doesn't work properly with inheritdoc
I have following xmldoc generated:
<member name="P:Gate.GateConfig.Connection"> <inheritdoc /> </member>
And this member is inherited from an interface. Visual Studio shows this properly, but output swagger is generated without information from parent.
yeah, nothing changed!
Just ran into this myself - not actually sure if this is possible, as the generated XML doc doesn't seem to contain any information relating to the inheritance chain. I expect Visual Studio needs to deal with this before Swagger can.
is there any plan to enable this or is this something that just doesn't gel with how it all works?
In the meantime this is how you can workaround the problem
-
dotnet tool -g install InheritDoc
- Add this target to your csproj file and also
<Target Name="InheritDoc" AfterTargets="PostBuildEvent" Condition="$(GenerateDocumentationFile)">
<Exec Command="InheritDoc -o" IgnoreExitCode="True" ContinueOnError="true"/>
</Target>
Edit: This is just for the AspNetCore Version
I wrote a ISchemaFilter
to automatically add the summary and example texts to the types and members decorated with an
Add to Swagger:
services.AddSwaggerGen(config => config.SchemaFilter<InheritDocSchemaFilter>(config));
Code:
/// <summary>
/// Adds documentation that is provided by the <inhertidoc /> tag.
/// </summary>
/// <seealso cref="Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter" />
public class InheritDocSchemaFilter : ISchemaFilter
{
private const string SUMMARY_TAG = "summary";
private const string EXAMPLE_TAG = "example";
private readonly List<XPathDocument> _documents;
private readonly Dictionary<string, string> _inheritedDocs;
/// <summary>
/// Initializes a new instance of the <see cref="InheritDocDocumentFilter" /> class.
/// </summary>
/// <param name="options">The options.</param>
public InheritDocDocumentFilter(SwaggerGenOptions options)
{
_documents = options.SchemaFilterDescriptors.Where(x => x.Type == typeof(XmlCommentsSchemaFilter))
.Select(x => x.Arguments.Single())
.Cast<XPathDocument>()
.ToList();
_inheritedDocs = _documents.SelectMany(
doc =>
{
var inheritedElements = new List<(string, string)>();
foreach (XPathNavigator member in doc.CreateNavigator().Select("doc/members/member/inheritdoc"))
{
member.MoveToParent();
inheritedElements.Add((member.GetAttribute("name", ""), member.GetAttribute("cref", "")));
}
return inheritedElements;
})
.ToDictionary(x => x.Item1, x => x.Item2);
}
/// <inheritdoc />
public void Apply(Schema schema, SchemaFilterContext context)
{
if (!(context.JsonContract is JsonObjectContract jsonObjectContract))
return;
// Try to apply a description for inherited types.
var memberName = XmlCommentsMemberNameHelper.GetMemberNameForType(context.SystemType);
if (string.IsNullOrEmpty(schema.Description) && _inheritedDocs.ContainsKey(memberName))
{
var cref = _inheritedDocs[memberName];
var target = GetTargetRecursive(context.SystemType, cref);
var targetXmlNode = GetMemberXmlNode(XmlCommentsMemberNameHelper.GetMemberNameForType(target));
var summaryNode = targetXmlNode?.SelectSingleNode(SUMMARY_TAG);
if (summaryNode != null)
schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}
if (schema.Properties == null)
return;
// Add the summary and examples for the properties.
foreach (var entry in schema.Properties)
{
if (!jsonObjectContract.Properties.Contains(entry.Key))
continue;
var jsonProperty = jsonObjectContract.Properties[entry.Key];
if (TryGetMemberInfo(jsonProperty, out var memberInfo))
ApplyPropertyComments(entry.Value, memberInfo);
}
}
private static bool TryGetMemberInfo(JsonProperty jsonProperty, out MemberInfo memberInfo)
{
if (jsonProperty.UnderlyingName == null)
{
memberInfo = null;
return false;
}
var metadataAttribute = jsonProperty.DeclaringType
.GetCustomAttributes(typeof(ModelMetadataTypeAttribute), true)
.FirstOrDefault();
var typeToReflect = metadataAttribute != null
? ((ModelMetadataTypeAttribute)metadataAttribute).MetadataType
: jsonProperty.DeclaringType;
memberInfo = typeToReflect.GetMember(jsonProperty.UnderlyingName).FirstOrDefault();
return memberInfo != null;
}
private static MemberInfo GetTarget(MemberInfo memberInfo, string cref)
{
var type = memberInfo.DeclaringType ?? memberInfo.ReflectedType;
if (type == null)
return null;
// Find all matching members in all interfaces and the base class.
var targets = type.GetInterfaces()
.Append(type.BaseType)
.SelectMany(
x => x.FindMembers(
memberInfo.MemberType,
BindingFlags.Instance | BindingFlags.Public,
(info, criteria) => info.Name == memberInfo.Name,
null))
.ToList();
// Try to find the target, if one is declared.
if (!string.IsNullOrEmpty(cref))
{
var crefTarget = targets.SingleOrDefault(t => XmlCommentsMemberNameHelper.GetMemberNameForMember(t) == cref);
if (crefTarget != null)
return crefTarget;
}
// We use the last since that will be our base class or the "nearest" implemented interface.
return targets.LastOrDefault();
}
private static Type GetTarget(Type type, string cref)
{
var targets = type.GetInterfaces();
if (type.BaseType != typeof(object))
targets = targets.Append(type.BaseType).ToArray();
// Try to find the target, if one is declared.
if (!string.IsNullOrEmpty(cref))
{
var crefTarget = targets.SingleOrDefault(t => XmlCommentsMemberNameHelper.GetMemberNameForType(t) == cref);
if (crefTarget != null)
return crefTarget;
}
// We use the last since that will be our base class or the "nearest" implemented interface.
return targets.LastOrDefault();
}
private void ApplyPropertyComments(Schema propertySchema, MemberInfo memberInfo)
{
var memberName = XmlCommentsMemberNameHelper.GetMemberNameForMember(memberInfo);
if (!_inheritedDocs.ContainsKey(memberName))
return;
var cref = _inheritedDocs[memberName];
var target = GetTargetRecursive(memberInfo, cref);
var targetXmlNode = GetMemberXmlNode(XmlCommentsMemberNameHelper.GetMemberNameForMember(target));
if (targetXmlNode == null)
return;
var summaryNode = targetXmlNode.SelectSingleNode(SUMMARY_TAG);
if (summaryNode != null)
propertySchema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
var exampleNode = targetXmlNode.SelectSingleNode(EXAMPLE_TAG);
if (exampleNode != null)
propertySchema.Example = XmlCommentsTextHelper.Humanize(exampleNode.InnerXml);
}
private XPathNavigator GetMemberXmlNode(string memberName)
{
var path = $"/doc/members/member[@name='{memberName}']";
foreach (var document in _documents)
{
var node = document.CreateNavigator().SelectSingleNode(path);
if (node != null)
return node;
}
return null;
}
private MemberInfo GetTargetRecursive(MemberInfo memberInfo, string cref)
{
var target = GetTarget(memberInfo, cref);
if (target == null)
return null;
var targetMemberName = XmlCommentsMemberNameHelper.GetMemberNameForMember(target);
if (_inheritedDocs.ContainsKey(targetMemberName))
return GetTarget(target, _inheritedDocs[targetMemberName]);
return target;
}
private Type GetTargetRecursive(Type type, string cref)
{
var target = GetTarget(type, cref);
if (target == null)
return null;
var targetMemberName = XmlCommentsMemberNameHelper.GetMemberNameForType(target);
if (_inheritedDocs.ContainsKey(targetMemberName))
return GetTarget(target, _inheritedDocs[targetMemberName]);
return target;
}
}
@anhaehne great code, sadly completely incompatible with this ( "not Core" ) version of Swashbuckle...
anyone managed to get Swagger to load inheritdoc ?
- dotnet tool -g install InheritDoc
Thanks! I had to run a slightly different command on my end:
dotnet tool install -g InheritDocTool
@anhaehne : The class you provided does not compile for me. For example because the name of the class InheritDocSchemaFilter
does not match the constructor name InheritDocDocumentFilter
Are there are news here? It's been over 4 years this has been opened.
I got into this situation today as well while doing some cleanup on some of our models. We have an interface defining a few properties that many DTOs have and changed the classes to use <inheritdoc />
only to find that the text vanished from the schema.
@julealgon I just ran into this issue myself today. Seems like they haven't implemented it because you can do it yourself pretty easily by pre-processing the XML docs before adding them to Swagger. Here is the code I am using (inspired by this):
void AddXmlDocs() {
// generate paths for the XML doc files in the assembly's directory.
var XmlDocPaths = Directory.GetFiles(
path: AppDomain.CurrentDomain.BaseDirectory,
searchPattern: "*.xml"
);
// load the XML docs for processing.
var XmlDocs = (
from DocPath in XmlDocPaths select XDocument.Load(DocPath)
).ToList();
// need a map for looking up member elements by name.
var TargetMemberElements = new Dictionary<string, XElement>();
// add member elements across all XML docs to the look-up table. We want <member> elements
// that have a 'name' attribute but don't contain an <inheritdoc> child element.
foreach(var doc in XmlDocs) {
var members = doc.XPathSelectElements("/doc/members/member[@name and not(inheritdoc)]");
foreach(var m in members) TargetMemberElements.Add(m.Attribute("name")!.Value, m);
}
// for each <member> element that has an <inheritdoc> child element which references another
// <member> element, replace the <inheritdoc> element with the nodes of the referenced <member>
// element (effectively this 'dereferences the pointer' which is something Swagger doesn't support).
foreach(var doc in XmlDocs) {
var PointerMembers = doc.XPathSelectElements("/doc/members/member[inheritdoc[@cref]]");
foreach(var PointerMember in PointerMembers) {
var PointerElement = PointerMember.Element("inheritdoc");
var TargetMemberName = PointerElement!.Attribute("cref")!.Value;
if(TargetMemberElements.TryGetValue(TargetMemberName, out var TargetMember))
PointerElement.ReplaceWith(TargetMember.Nodes());
}
}
// replace all <see> elements with the unqualified member name that they point to (Swagger uses the
// fully qualified name which makes no sense because the relevant classes and namespaces are not useful
// when calling an API over HTTP).
foreach(var doc in XmlDocs) {
foreach(var SeeElement in doc.XPathSelectElements("//see[@cref]")) {
var TargetMemberName = SeeElement.Attribute("cref")!.Value;
var ShortMemberName = TargetMemberName.Substring(TargetMemberName.LastIndexOf('.') + 1);
if(TargetMemberName.StartsWith("M:")) ShortMemberName += "()";
SeeElement.ReplaceWith(ShortMemberName);
}
}
// add pre-processed XML docs to Swagger.
foreach(var doc in XmlDocs)
ArgOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);
}
The ArgOptions
variable refers to an instance of SwaggerGenOptions
which you use to add the XML files.
pretty easily
People say this to anything even if it's 56 lines of code..
@julealgon I just ran into this issue myself today. Seems like they haven't implemented it because you can do it yourself pretty easily by pre-processing the XML docs before adding them to Swagger. Here is the code I am using (inspired by this):
void AddXmlDocs() { // generate paths for the XML doc files in the assembly's directory. var XmlDocPaths = Directory.GetFiles( path: AppDomain.CurrentDomain.BaseDirectory, searchPattern: "*.xml" ); // load the XML docs for processing. var XmlDocs = ( from DocPath in XmlDocPaths select XDocument.Load(DocPath) ).ToList(); // need a map for looking up member elements by name. var TargetMemberElements = new Dictionary<string, XElement>(); // add member elements across all XML docs to the look-up table. We want <member> elements // that have a 'name' attribute but don't contain an <inheritdoc> child element. foreach(var doc in XmlDocs) { var members = doc.XPathSelectElements("/doc/members/member[@name and not(inheritdoc)]"); foreach(var m in members) TargetMemberElements.Add(m.Attribute("name")!.Value, m); } // for each <member> element that has an <inheritdoc> child element which references another // <member> element, replace the <inheritdoc> element with the nodes of the referenced <member> // element (effectively this 'dereferences the pointer' which is something Swagger doesn't support). foreach(var doc in XmlDocs) { var PointerMembers = doc.XPathSelectElements("/doc/members/member[inheritdoc[@cref]]"); foreach(var PointerMember in PointerMembers) { var PointerElement = PointerMember.Element("inheritdoc"); var TargetMemberName = PointerElement!.Attribute("cref")!.Value; if(TargetMemberElements.TryGetValue(TargetMemberName, out var TargetMember)) PointerElement.ReplaceWith(TargetMember.Nodes()); } } // replace all <see> elements with the unqualified member name that they point to (Swagger uses the // fully qualified name which makes no sense because the relevant classes and namespaces are not useful // when calling an API over HTTP). foreach(var doc in XmlDocs) { foreach(var SeeElement in doc.XPathSelectElements("//see[@cref]")) { var TargetMemberName = SeeElement.Attribute("cref")!.Value; var ShortMemberName = TargetMemberName.Substring(TargetMemberName.LastIndexOf('.') + 1); if(TargetMemberName.StartsWith("M:")) ShortMemberName += "()"; SeeElement.ReplaceWith(ShortMemberName); } } // add pre-processed XML docs to Swagger. foreach(var doc in XmlDocs) ArgOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true); }
The
ArgOptions
variable refers to an instance ofSwaggerGenOptions
which you use to add the XML files.
Unfortunately, this assumes that <inheritdoc cref=""/>
is used rather than a simple <inheritdoc/>
, which the latter implicitly takes the documentation from the child element - be it from an interface, or from a base class. Any workarounds?
@SpiritBob: Unfortunately, this assumes that
is used rather than a simple , which the latter implicitly takes the documentation from the child element - be it from an interface, or from a base class. Any workarounds?
You would have to "dereference the pointer" similar to the example I showed, but with some modifications to locate the <member>
element containing the docs from the base class and copy those XML elements to the <member>
element for the child class. Example: the base class XML docs may look like this:
<member name="M:MyNameSpace.MyBaseClass.MyMethodFoo">
<summary>
Does blah blah.
</summary>
</member>
The child class will then use <inheritdoc>
and look something like this:
<member name="M:MyNameSpace.MyChildClass.MyMethodFoo">
<inheritdoc/>
</member>
The task is then to (1) use XPath to find all <member>
elements that contain an <inheritdoc/>
element, (2) get the value from the name
attribute (which is M:MyNameSpace.MyChildClass.MyMethodFoo
in the above example), (3) map that value to the name in the base class (which is M:MyNameSpace.MyBaseClass.MyMethodFoo
in the above example), (4) use the mapped name and XPath to locate the <member>
element containing docs for the base class, (5) copy XML nodes from the base class docs and replace them into the <member>
element for the child class which originally contained the <inheritdoc/>
element.
The tricky part is mapping from M:MyNameSpace.MyChildClass.MyMethodFoo to M:MyNameSpace.MyBaseClass.MyMethodFoo in order to locate the base class docs. The rest (using XPath, replacing XML nodes, etc) is shown in the code I gave earlier.
Hopefully that points you in the right direction!
@julealgon I just ran into this issue myself today. Seems like they haven't implemented it because you can do it yourself pretty easily by pre-processing the XML docs before adding them to Swagger. Here is the code I am using (inspired by this):
void AddXmlDocs() { // generate paths for the XML doc files in the assembly's directory. var XmlDocPaths = Directory.GetFiles( path: AppDomain.CurrentDomain.BaseDirectory, searchPattern: "*.xml" ); // load the XML docs for processing. var XmlDocs = ( from DocPath in XmlDocPaths select XDocument.Load(DocPath) ).ToList(); // need a map for looking up member elements by name. var TargetMemberElements = new Dictionary<string, XElement>(); // add member elements across all XML docs to the look-up table. We want <member> elements // that have a 'name' attribute but don't contain an <inheritdoc> child element. foreach(var doc in XmlDocs) { var members = doc.XPathSelectElements("/doc/members/member[@name and not(inheritdoc)]"); foreach(var m in members) TargetMemberElements.Add(m.Attribute("name")!.Value, m); } // for each <member> element that has an <inheritdoc> child element which references another // <member> element, replace the <inheritdoc> element with the nodes of the referenced <member> // element (effectively this 'dereferences the pointer' which is something Swagger doesn't support). foreach(var doc in XmlDocs) { var PointerMembers = doc.XPathSelectElements("/doc/members/member[inheritdoc[@cref]]"); foreach(var PointerMember in PointerMembers) { var PointerElement = PointerMember.Element("inheritdoc"); var TargetMemberName = PointerElement!.Attribute("cref")!.Value; if(TargetMemberElements.TryGetValue(TargetMemberName, out var TargetMember)) PointerElement.ReplaceWith(TargetMember.Nodes()); } } // replace all <see> elements with the unqualified member name that they point to (Swagger uses the // fully qualified name which makes no sense because the relevant classes and namespaces are not useful // when calling an API over HTTP). foreach(var doc in XmlDocs) { foreach(var SeeElement in doc.XPathSelectElements("//see[@cref]")) { var TargetMemberName = SeeElement.Attribute("cref")!.Value; var ShortMemberName = TargetMemberName.Substring(TargetMemberName.LastIndexOf('.') + 1); if(TargetMemberName.StartsWith("M:")) ShortMemberName += "()"; SeeElement.ReplaceWith(ShortMemberName); } } // add pre-processed XML docs to Swagger. foreach(var doc in XmlDocs) ArgOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true); }
The
ArgOptions
variable refers to an instance ofSwaggerGenOptions
which you use to add the XML files.
This does work, but all xmldoc references (see / seealso) get stripped, I presume because they are brackets...
Does anyone know a way to force SwaggerDocGen to render them, at least as pure text, if not resolved as fully qualified references ?
InheritDocSchemaFilter
This solution is probably outdated for the current Swagger UI generator version.
I cannot seem to find the namespaces for XmlCommentsMemberNameHelper
and XmlCommentsTextHelper.Humanize
returns string instead of Microsoft.OpenApi.Any.IOpenApiAny
type
and
In the meantime this is how you can workaround the problem
1. `dotnet tool -g install InheritDoc` 2. Add this target to your csproj file and also
<Target Name="InheritDoc" AfterTargets="PostBuildEvent" Condition="$(GenerateDocumentationFile)"> <Exec Command="InheritDoc -o" IgnoreExitCode="True" ContinueOnError="true"/> </Target>
This seemingly does nothing for me (even when I installed .NET Core 3.1 which it requires to run and has shown a warning in build output).
@julealgon I just ran into this issue myself today. Seems like they haven't implemented it because you can do it yourself pretty easily by pre-processing the XML docs before adding them to Swagger. Here is the code I am using (inspired by this):
void AddXmlDocs() { // generate paths for the XML doc files in the assembly's directory. var XmlDocPaths = Directory.GetFiles( path: AppDomain.CurrentDomain.BaseDirectory, searchPattern: "*.xml" ); // load the XML docs for processing. var XmlDocs = ( from DocPath in XmlDocPaths select XDocument.Load(DocPath) ).ToList(); // need a map for looking up member elements by name. var TargetMemberElements = new Dictionary<string, XElement>(); // add member elements across all XML docs to the look-up table. We want <member> elements // that have a 'name' attribute but don't contain an <inheritdoc> child element. foreach(var doc in XmlDocs) { var members = doc.XPathSelectElements("/doc/members/member[@name and not(inheritdoc)]"); foreach(var m in members) TargetMemberElements.Add(m.Attribute("name")!.Value, m); } // for each <member> element that has an <inheritdoc> child element which references another // <member> element, replace the <inheritdoc> element with the nodes of the referenced <member> // element (effectively this 'dereferences the pointer' which is something Swagger doesn't support). foreach(var doc in XmlDocs) { var PointerMembers = doc.XPathSelectElements("/doc/members/member[inheritdoc[@cref]]"); foreach(var PointerMember in PointerMembers) { var PointerElement = PointerMember.Element("inheritdoc"); var TargetMemberName = PointerElement!.Attribute("cref")!.Value; if(TargetMemberElements.TryGetValue(TargetMemberName, out var TargetMember)) PointerElement.ReplaceWith(TargetMember.Nodes()); } } // replace all <see> elements with the unqualified member name that they point to (Swagger uses the // fully qualified name which makes no sense because the relevant classes and namespaces are not useful // when calling an API over HTTP). foreach(var doc in XmlDocs) { foreach(var SeeElement in doc.XPathSelectElements("//see[@cref]")) { var TargetMemberName = SeeElement.Attribute("cref")!.Value; var ShortMemberName = TargetMemberName.Substring(TargetMemberName.LastIndexOf('.') + 1); if(TargetMemberName.StartsWith("M:")) ShortMemberName += "()"; SeeElement.ReplaceWith(ShortMemberName); } } // add pre-processed XML docs to Swagger. foreach(var doc in XmlDocs) ArgOptions.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true); }
The
ArgOptions
variable refers to an instance ofSwaggerGenOptions
which you use to add the XML files.
This has no effect other than it changes the ordering of the controllers (endpoint groups) displayed in the Swagger UI.
Here's my version of the workaround by @anhaehne, working for me with Swashbuckle.AspNetCore 6.5.0 as of today, ymmv:
public class SwaggerXmlSchemaFilter : ISchemaFilter
{
private const string SUMMARY_TAG = "summary";
private const string EXAMPLE_TAG = "example";
private readonly List<XPathDocument> _documents;
private readonly Dictionary<string, string> _inheritedDocs;
public SwaggerXmlSchemaFilter(SwaggerGenOptions options)
{
_documents = options.SchemaFilterDescriptors.Where(x => x.Type == typeof(XmlCommentsSchemaFilter))
.Select(x => x.Arguments.Single())
.Cast<XPathDocument>()
.ToList();
_inheritedDocs = _documents.SelectMany(
doc =>
{
var inheritedElements = new List<(string, string)>();
foreach (XPathNavigator member in doc.CreateNavigator().Select("doc/members/member/inheritdoc"))
{
member.MoveToParent();
inheritedElements.Add((member.GetAttribute("name", ""), member.GetAttribute("cref", "")));
}
return inheritedElements;
})
.ToDictionary(x => x.Item1, x => x.Item2);
} /
private static string GetMemberNameForType(Type type)
=> $"T:{type.FullName}";
private static string GetMemberNameForMember(MemberInfo member)
=> $"{(member is PropertyInfo ? "P" : "F")}:{(member.DeclaringType ?? member.ReflectedType).FullName}.{member.Name}";
/// <inheritdoc />
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var memberName = GetMemberNameForType(context.Type);
var sources = GetPossibleSources(context.Type);
if (string.IsNullOrEmpty(schema.Description) && _inheritedDocs.TryGetValue(memberName, out var cref))
{
// use explicit source if provided (by <inheritdoc cref="source" />)
if (!string.IsNullOrEmpty(cref))
{
var crefTarget = sources.SingleOrDefault(t => GetMemberNameForType(t) == cref);
if (crefTarget != null)
sources = new List<Type> { crefTarget };
}
foreach (var source in sources)
{
var sourceXmlNode = GetMemberXmlNode(GetMemberNameForType(source));
var summaryNode = sourceXmlNode?.SelectSingleNode(SUMMARY_TAG);
if (summaryNode != null)
{
schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
break;
}
}
}
if (schema.Properties == null)
return;
foreach (var entry in schema.Properties)
{
var propertyName = entry.Key.ToTitleCase();
var property = context.Type.GetProperty(propertyName);
if (property != null)
{
var propertySchema = entry.Value;
var propertyMemberName = GetMemberNameForMember(property);
if (string.IsNullOrEmpty(propertySchema.Description) && _inheritedDocs.TryGetValue(propertyMemberName, out cref))
{
foreach (var source in sources)
{
var sourceProperty = source.GetProperty(propertyName);
if (sourceProperty != null)
{
var sourceXmlNode = GetMemberXmlNode(GetMemberNameForMember(sourceProperty));
var summaryNode = sourceXmlNode?.SelectSingleNode(SUMMARY_TAG);
if (summaryNode != null)
{
propertySchema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
break;
}
}
}
}
}
}
}
private static List<Type> GetPossibleSources(Type type)
{
var targets = type.GetInterfaces().ToList();
var baseType = type.BaseType;
while (baseType != typeof(object) && baseType != null)
{
targets.Add(baseType);
baseType = baseType.BaseType;
}
targets.Reverse();
return targets;
}
private XPathNavigator GetMemberXmlNode(string memberName)
{
var path = $"/doc/members/member[@name='{memberName}']";
foreach (var document in _documents)
{
var node = document.CreateNavigator().SelectSingleNode(path);
if (node != null)
return node;
}
return null;
}
}
I created a working fix, based on the version from @fverhoef: https://gist.github.com/drasive/872fdf9f23fe37471b66fad2ee80bb71 Tested with Swashbuckle.AspNetCore v6.5.0 on .NET 7.0.
I have made the following changes:
- Fix: Example tag is inherited as well
- Fix: Properties can be found regardless of name capitalization
- Fix: It compiles
- Refactoring: Code cleanup
hi you can use SauceControl.InheritDoc
I created a working fix, based on the version from @fverhoef: https://gist.github.com/drasive/872fdf9f23fe37471b66fad2ee80bb71 Tested with Swashbuckle.AspNetCore v6.5.0 on .NET 7.0.
Sadly, this did not work for me with .NET 8.0.
Any update on this ?
@domaindrivendev