I have a product with multiple repositories, each distributed as a NuGet package. I would like to establish a separate repository for DocFx, which generates the documentation from the NuGet packages. That way, it can consolidate the documentation for multiple { repositories, projects, packages } into one location, while also allowing conceptual documentation to be updated independent of the package release cycle.
TL;DR: I am able to get DocFx to correctly generate the reference documentation based on the assemblies in the NuGet package, but it doesn't include any of the XML documentation (i.e., from ///
metadata). How do I solve this? Details below.
To pursue this, I first ensured that the csproj
files for the source projects were configured to generate the XML documentation as part of the build output:
<Project>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
And, as a result, the nupkg
file correctly includes the XML documentation at e.g.:
/lib/net6.0/{AssemblyName}.xml
This documentation is then picked up by e.g. Visual Studio IntelliSense when consuming these packages.
I then added metadata
for these assembles via the docfx.json
configuration file:
{
"metadata": [
{
"src": [
{
"files": "bin/Release/net6.0/{AssemblyName}.dll"
}
],
"dest": "api/{AssemblyName}"
}
]
…
}
With this in place, DocFx correctly identifies the namespaces, types, and members, and generates reference documentation for each. So far, so good!
The problem is that DocFx doesn't include any of the XML Doc (///
) information for these assemblies, despite them being included with the package. How do I mitigate this?
If you specify an assembly in the metadata
configuration, DocFx will look for the XML documentation in the same directory (source). The problem, however, is that when you include a NuGet reference, that XML documentation isn't included in your project's bin
folder, even if it's included in the NuGet package. As a result, while DocFx is able to identify the structure, it is not able to identify the documentation.
TL;DR: To resolve this, you can configure msbuild
to copy the XML documentation to the bin
folder, ensuring DocFx has access to it (example). Details below.
When you reference a NuGet package, by default, the package is downloaded, unzipped, and cached in the following location:
%UserProfile%\.nuget\packages\{Package}\{Version}\
Note: This is obviously a Windows directory; the location will vary by operating system.
This will include e.g.,
\lib\net6.0\{AssemblyName}.dll
\lib\net6.0\{AssemblyName}.xml
When you build your .NET project, the {AssemblyName}.dll
is copied to your project's bin
directory, but not the {AssemblyName}.xml
. Since you've instructed DocFx to look in your project's bin
folder, it is therefore unable to collect the documentation.
The obvious workaround would be to simply source the metadata from the NuGet cache directly. This can easily be accomplished by adjusting the docfx.json
configuration as follows:
{
"metadata": [
{
"src": [
{
"files": "bin/Release/net6.0/{AssemblyName}.dll",
"src": "C:\\Users\\{User}\\.nuget\\packages\\{Package}\\{Version}\\lib\\net6.0\\"
}
],
"dest": "api/{AssemblyName}"
}
]
…
}
This works, of course. But it's also a really brittle solution that isn't feasible for production code. Most notably, DocFx doesn't recognize the %UserProfile%
alias, and so the {User}
must be hardcoded into the configuration. Not only will this be different for each developer, but it will also be different on the build server. And that's not even factoring in non-Windows environments.
Given this, the better solution is to instruct msbuild
to copy the XML documentation to the bin
folder along with the assembly. Fortunately, that's pretty easy, as discussed in How to include XML Documentation from Nuget Package in csproj build output. In your csproj
file, simply add the following:
<Project>
<Target Name="_ResolveCopyLocalNuGetPkgXmls" AfterTargets="ResolveReferences">
<ItemGroup>
<ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).xml')"
Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)'!='' and Exists('%(RootDir)%(Directory)%(Filename).xml')" />
</ItemGroup>
</Target>
</Project>
The main advantage of this approach is that it relies on msbuild
variable to abstract the location of the NuGet cache, the package name, the version number, and the destination folder. As a result, this should work on any machine or operating system.
With this in place, DocFx will successfully find the XML documentation alongside the assembly, and include it in the generated yml
metadata.