Add NuGet package XML documentation to Swagger
Copy XML documentation from NuGet package to project build folder!
At my current client, we are building an API that is put together by re-usable “API parts”, eg. ASP.NET Core Application Parts, which works wonders by the way. We can have multiple parts of the API split into small NuGet packages that can be re-used in other systems, that way we only have to implement system specific code, the rest is reused, including documentation etc.
Talking about documentation, brings us directly to the issue. We use SwashBuckle to generate our Swagger definition and Swagger UI, and Swashbuckle requires XML documentation, to be able to include documentation from our Controllers and models. I thought we could probably just add a checkbox “Add XML documentation from NuGet package, on build”, but… Unfortunately not.
After a lot of research, and looking at NuGets github issues stating this problem I came up with a solution that works both at build time and at publish time (which apparently is handled differently).
The Setup
We have the following projects:
- RestAPI (Root website, that will contain multiple API packages)
- ASP.NET Core website
- Contains Swagger page
- System specific API Service implementations
- BasicAPI (NuGet package)
- ASP.NET Core website
- Contains API endpoints, Models, Service interfaces etc.
- Public XML documentation for its own API and Models
- .. more NuGet packages to come
Include XML documentation, when building NuGet package with .csproj file
Our application is very simple, so we have chosen to pack nuget packages with our .csproj file instead of the more explicit .nuspec file.
Including XML documentation into a NuGet package is thankfully quite easy, and it should be, as it is the main way that documentation follows C# code.
The easiest way is to add the following PropertyGroup
into your nuget package .csproj file:
<PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup>
This will do as it states, GenerateDocumentationFile
. Generate a Documentation file, when you build the project. This file is also automatically added to your NuGet Package when you pack it.
Copy XML docs from NuGet package to your main projects build output
This is where it gets tricky. I was hoping that I could just add another boolean configuration. When looking through the issues for nuget, msbuild and dotnet on github, it did seem like they were working on something, and had implemented a way to add XML documentation from your PROJECT references/dependencies, but not your NuGet dependencies.
Luckily, we can add our own targets to the MSBuild task list and copy the XML docs ourselfs:
First add CopyToOutputDirectory
to your main projects .csproj file, for the PackageReference you want to copy XML doc from. Like so:
<ItemGroup> <PackageReference Include="BasicAPI" Version="0.1.0-develop-6982"> <CopyToOutputDirectory>lib\netcoreapp2.1\*</CopyToOutputDirectory> </PackageReference> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.8" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="2.2.0" /> ... etc <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" /> </ItemGroup>
The relative path added inside the CopyToOutputDirectory
is the relative path from the root of the NuGet package folder, to where the XML docs are located.
In .NET Core, our NuGet packages are located at:
%USERPROFILE%\.nuget\packages\{PackageName}\{PackageVersion}
And the XML documentation file would be located at:
%USERPROFILE%\.nuget\packages\{PackageName}\{PackageVersion}\lib\netcoreapp2.1\{PackageName}.xml
Setup build targets to copy XML docs from NuGet package after Build
To copy the file after build time, we must add a target to MSBuild that does that. Add the following to your main
<Target Name="CopyPackages" AfterTargets="Build"> <ItemGroup> <PackageReferenceFiles Condition="%(PackageReference.CopyToOutputDirectory) != ''" Include="$(NugetPackageRoot)\%(PackageReference.FileName)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" /> </ItemGroup> <Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" /> </Target>
To copy the files from the NuGet package to our build output, we add a Target named “CopyPackages”, this name is for our own use and has no effect. After that, we are stating that we want this Target to run after the Build has finished.
When then create a Variable called PackageReferenceFiles
that finds all PackageReference that has a property called CopyToOutputDirectory
(the one we added in the last section). It then sets that variables value to be a combination of the location of the NuGetPackageRoot and relative path we set earlier.
Finally it asks MSBuild to do a file copy from the variable PackageReferenceFiles
to the Builds Output directory.
Setup build targets to copy XML docs from NuGet package before Publish
For some odd reason, publish is done in multiple steps and it is not just a file copy from the build procedure, at least for web applications. That means that our OutDir
will not be correct, and needs to be changed to PublishDir
, we also need to make sure that this runs before the Target called PrepareForPublish
.
I have tried multiple solutions to make this into one Target, but to finally gave into:
Keep it simple, stupid
And just added another Target with this small change.
So to copy XML docs from NuGet package before Publish, add this to your main projects .csproj file:
<Target Name="CopyPackagess" BeforeTargets="PrepareForPublish"> <ItemGroup> <PackageReferenceFiles Condition="%(PackageReference.CopyToOutputDirectory) != ''" Include="$(NugetPackageRoot)\%(PackageReference.FileName)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" /> </ItemGroup> <Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(PublishDir)" /> </Target>
Adding the NuGet packages XML doc to Swagger (Swashbuckle)
Finally, you just have to add the XML docs to your Swagger definition like so:
public void ConfigureSwagger(IServiceCollection services, object identityServerConfiguration) { // Register the Swagger generator, defining one or more Swagger documents services.AddSwaggerGen(options => { // integrate xml comments options.IncludeXmlComments(XmlCommentsFilePath(GetAssemblyName() + ".xml")); options.IncludeXmlComments(XmlCommentsFilePath("BasicAPI.xml")); // ... } private static string XmlCommentsFilePath(string xmlDocFileName) => Path.Combine(AppContext.BaseDirectory, xmlDocFileName); private static string GetAssemblyName() => typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
I hope this saves you some time, as this took me a small evening to get right.
// André Snede Kock
%(PackageReference.FileName) is error, because it remove extension, if the package has ‘.’ character, the value will be wrong, I use %(PackageReference.Identity) instead.
Please refer to https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata?view=vs-2019 for more information.
Your solution help me a lot.
Is this requiring dotnetcore 2.1?
We are using 2.2 and this method is not working (I did have to change the lib/netcoreapp2.1 to lib/netstandard2.0). It is not giving a build error or anything, but it isn’t transferring the file either, although it is visible in thet .nuget packages folder.
Same for publish, also not working.
Tried several solution found on the web, but they seem to look at 2.1, but something changed it seems in 2.2 making this to fail
There is a small type on this website, being packagesS -> should be packages of course in the publish part.
I think some of the configs changed, take a look at this StackOverflow answer: https://stackoverflow.com/questions/52891727/nuget-package-xml-documentation-not-visible-in-net-core-2-2-app
We have not moved to Dotnet core 2.2 yet, but thanks for the heads up. If I run into the issue then, I’ll post an updated answer here on the blog.
Hi,
Correct, but that link is about getting the XML generated from the package. This is working correctly and the XML file is in my local .nuget folder. However, the build/publish step is not copying it into the output folder of my project when building or publishing. So, I assume the definition are not working in 2.2 as they were in 2.1 either.
I’ll try to set build output to full and see what to find 🙂
Arrh yes, you’re right.
If you figure out the answer to this, I will happily update the blog with the answer.
I am afraid I won’t be able to dive into it myself for quite awhile.
Seems the issue is within the %(PackageReference.FileName)
This value is incorrect in dotnetcore 2.2 at least:
If this would be the tag
Then, the %(PackageReference.FileName) contains “Company.Contracts”, making it search for xml’s in the wrong folder of course.
I cannot find any Microsoft documentation regarding this PackageReference, so I did this fix
lib\netstandard2.0\*.xml
Company.Contracts.MyContract
Then in the target, I use this second ‘variable’, having the Include attribute being Include=”$(NugetPackageRoot)\%(PackageReference.PackageName)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)”
Not the best solution, but without any documentation, it was the ‘quickest’ fix.
Seems xml is removed from my reply where it says ‘if this would be the tag’. Trying again with some html encoded stuff 🙂
<PackageReference Include=”Mensura.Contracts.EmployeeExamination” Version=”0.0.10″>
<CopyToOutputDirectory>lib\netstandard2.0\*.xml</CopyToOutputDirectory>
<PackageName>Mensura.Contracts.EmployeeExamination</PackageName>
</PackageReference>
the publish step is not copying it into the output folder of my project when I publishing into docker/k8s,could you help me ?
It would bedste difficult to help you in a comment.
But try to isolate the problem. Does it work when you publish normally, or is it only when doing so with Docker?
How can include Documentation file from a package that is a dependency of another package? My documentation file is in the inner package and I reference always de parent