Swashbuckle.WebApi: 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.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 47
  • Comments: 20

Most upvoted comments

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.

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>

@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…

  1. dotnet tool -g install InheritDoc

Thanks! I had to run a slightly different command on my end:

dotnet tool install -g InheritDocTool

@SpiritBob: 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?

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!

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.

yeah, nothing changed!