This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy under /_llms/. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Guides

Auto-generate an API reference tree for a class library

Wire the reflection metadata backend (a compiled .dll + .xml pair), call AddApiReference, and get one /reference/api/{type}/ page per public type plus inline Mdazor components for member tables, summaries, and extension-method catalogs.

To ship a DocSite whose reference section stays in sync with a class library's public API, register a metadata backend and call AddApiReference(). One Razor template renders every public type, and a handful of Mdazor components (<ApiMemberTable>, <ApiSummary>, <ExtensionMethods>, <ApiParameterTable>) are available inline in markdown for hand-authored reference pages. Every generated page, search entry, and xref keys off a single pass over the configured backend.

Pennington.ApiMetadata.Reflection.AddApiMetadataFromCompiledAssembly() is the metadata backend: it reflects over a compiled .dll and parses the companion xmldoc .xml file. It documents any assembly — one you build alongside the docs host, or a third-party NuGet package — without needing its source.

Before you begin

  • AddApiReference runs after AddDocSite. It appends its own assembly to DocSiteOptions.AdditionalRoutingAssemblies at registration time, so AddDocSite must already be wired.
  • One metadata backend is registered before AddApiReference. Without one, the content service has nothing to publish.

Wire the reflection backend

Add a project reference to Pennington.ApiMetadata.Reflection, add a <PackageReference> to the library you want to document, and have Pennington resolve the assembly by simple name. A complete single-package DocSite host:

csharp
using Pennington.ApiMetadata.Reflection;
using Pennington.DocSite;
using Pennington.DocSite.Api;
  
var builder = WebApplication.CreateBuilder(args);
  
// Standard DocSite wiring. No Areas — single default TOC.
builder.Services.AddDocSite(() => new DocSiteOptions
{
    SiteTitle = "FusionCache Docs",
    SiteDescription = "Demo of Pennington's reflection-based API metadata backend, documenting ZiggyCreatures.FusionCache straight from its NuGet package.",
    GitHubUrl = "https://github.com/ZiggyCreatures/FusionCache",
    HeaderContent = """<a href="/" class="font-bold text-lg">FusionCache Docs</a>""",
    FooterContent = """<footer class="mt-16 py-8 text-center text-sm text-base-500">FusionCache © ZiggyCreatures. Rendered by Pennington.</footer>""",
});
  
// Reflection-backed API metadata sourced from the ZiggyCreatures.FusionCache
// NuGet package reference in the .csproj — no live compilation, no staged
// dll/xml, no vendored source.
builder.Services.AddApiMetadataFromCompiledAssembly(opts =>
    opts.FromPackageReference("ZiggyCreatures.FusionCache"));
  
// Auto-publishes /api/{slug}/ pages off the metadata provider and registers
// the <ApiSummary>, <ApiMemberTable>, <ApiParameterTable>, ... Mdazor
// components. Sits in the Guides section of the sidebar so readers drop into
// the full type index from the same nav they read the stampede/fail-safe
// guides in.
builder.Services.AddApiReference(configure: opts =>
{
    opts.RoutePrefix = "/api/";
    opts.TocSectionLabel = "Guides";
});
  
var app = builder.Build();
app.UseDocSite();
await app.RunDocSiteAsync(args);

FromPackageReference resolves the .dll (and its companion xmldoc .xml) from a matching <PackageReference> by simple name — no staged dll, no committed binary, and bumping the documented version is a <PackageReference Version=…> change.

When the target isn't a normal NuGet reference — a locally-built assembly, a single-file bundle, or something else without a file location — fall back to the explicit form:

csharp
builder.Services.AddApiMetadataFromCompiledAssembly(opts =>
    opts.AssemblyFiles.Add(Path.Combine(builder.Environment.ContentRootPath, "lib", "net9.0", "Foo.dll")));

The reflection backend loads each .dll into a MetadataLoadContext — it inspects metadata without running the assembly's code, so it needs no MSBuild workspace and no source. It resolves <inheritdoc/> and union-case xmldoc from that metadata. Because it reads metadata rather than source text, this backend can populate the <ApiSummary> and <ApiMemberTable> components below, but it cannot back a fence that embeds a member's source — the kind of resolver you would write with an ICodeBlockPreprocessor has nothing to read.

Customize the route prefix

The default prefix is /reference/api/. Override it per registration via AddApiReference's RoutePrefix option when the shorter /api/ (or any other prefix) is a better fit:

csharp
builder.Services.AddApiMetadataFromCompiledAssembly(opts => { /* ... */ });
builder.Services.AddApiReference(configure: opts => opts.RoutePrefix = "/api/");

Type pages land at /api/{slug}/, and xref uids stay as reference.api.{slug} for back-compat with the default registration.

Document multiple libraries on one site

Pair each library with its own named AddApiMetadataFrom* call and a matching AddApiReference call. Names are the key that wires a reference tree to its provider, and every tree gets its own URL prefix:

csharp
builder.Services.AddApiMetadataFromCompiledAssembly("spectre-console", opts =>
    opts.FromPackageReference("Spectre.Console"));
builder.Services.AddApiMetadataFromCompiledAssembly("spectre-console-cli", opts =>
    opts.FromPackageReference("Spectre.Console.Cli"));
  
builder.Services.AddApiReference("spectre-console", opts =>
    opts.RoutePrefix = "/api/spectre/");
builder.Services.AddApiReference("spectre-console-cli", opts =>
    opts.RoutePrefix = "/api/spectre-cli/");

Each FromPackageReference call resolves one DLL from its matching <PackageReference>. Cross-package type references resolve automatically across the NuGet cache root.

Cross-references between named trees: uids pick up a qualifier. Default-named registrations emit reference.api.{slug} (unchanged). Named registrations emit reference.api.{name}.{slug} — for example, <xref:reference.api.spectre-console.ansi-console> and <xref:reference.api.spectre-console-cli.command-app>.

Hand-authored markdown: components like <ApiSummary> auto-pick up the source from the enclosing generated page. For markdown pages outside the generated tree that reach into a specific named registration, add an explicit Source attribute:

markdown
<ApiSummary XmlDocId="T:Spectre.Console.Cli.CommandApp" Source="spectre-console-cli" />

Narrow what gets published

The reflection backend documents the assemblies you point it at, so narrowing is a matter of which assemblies you add. The built-in rules already exclude types that are not public, are delegates or attributes, derive from ComponentBase, or carry no xmldoc <summary>.

Use AssemblyFiles to document an explicit list of .dll paths when a folder holds more assemblies than you want documented — for example, dependencies copied alongside the target only so MetadataLoadContext can resolve them:

csharp
builder.Services.AddApiMetadataFromCompiledAssembly(opts =>
{
    opts.AssemblyFiles.Add(Path.Combine(libDir, "MyLibrary.dll"));
    opts.AssemblyFiles.Add(Path.Combine(libDir, "MyLibrary.Extensions.dll"));
});

Use AssemblyDirectories instead to document every .dll/.xml pair in a folder — the typical NuGet lib/<tfm>/ layout.

Render reference fragments inline

Summarize one symbol with <ApiSummary>

Pulls the <summary> tag off a type or member and renders it as prose. Pass an xmldocid as XmlDocId.

markdown
<ApiSummary XmlDocId="T:Pennington.ApiMetadata.ApiTypeSummary" />

Lightweight header describing a documented type, used for listings, slug disambiguation, and cross-link display names.

Enumerate type members with <ApiMemberTable>

Kind="All" groups members by category (Properties, Constructors, Fields, Methods, Events) with headings between; narrow it with Kind="Properties" or Kind="Methods" for a single bucket.

markdown
<ApiMemberTable XmlDocId="T:Pennington.ApiMetadata.ApiTypeSummary" Kind="Properties" />
Assembly string
Declaring assembly name without extension.
FullTypeName string
Fully-qualified type name (namespace + dot + type name).
Kind ApiTypeKind
Category of the type.
Name string
Short type name without namespace (e.g. ContentPipeline).
Namespace string
Fully-qualified containing namespace, empty for the global namespace.
Summary string
First-sentence plain-text summary, or null when no xmldoc summary is available.
Uid string
Canonical xmldocid (e.g. T:Namespace.TypeName). Normalized to xmldocid form regardless of source backend.

List a method's parameters with <ApiParameterTable>

Pass a method xmldocid (M:...). The table pulls parameter names and types from the provider's pre-formatted ApiMember and descriptions from each <param> tag.

markdown
<ApiParameterTable XmlDocId="M:Pennington.ApiMetadata.IApiMetadataProvider.GetMembersAsync(System.String,Pennington.ApiMetadata.MemberKind,Pennington.ApiMetadata.AccessFilter,Pennington.ApiMetadata.MemberOrder)" />
typeUid string
Uid of the type whose members are returned.
kind MemberKind
Member categories to include (properties, methods, and so on).
access AccessFilter
Accessibility levels to include.
order MemberOrder
Sort order applied to the returned members.

Catalog extension methods by receiver with <ExtensionMethods>

Groups every public extension method in the assembly by the unqualified short name of its first (receiver) parameter. Receiver="IServiceCollection" gathers every services.AddX() helper the library ships.

markdown
<ExtensionMethods Receiver="IServiceCollection" />

Result

Every public type with an xmldoc comment gets a route under /reference/api/{slug}/:

text
/reference/api/                        -> uid: reference.api
/reference/api/api-type-summary/       -> uid: reference.api.api-type-summary
/reference/api/api-member/             -> uid: reference.api.api-member

Xref links like <xref:reference.api.api-type-summary> resolve, the pages flow through search and llms.txt, and the index page at /reference/api/ lists every type grouped by namespace. One TOC entry — the index page, titled "API reference" by default — appears in the sidebar; per-type pages stay out of the sidebar and are reached via type-name search, xref links, and the index. Override TocTitle and TocSectionLabel on ApiReferenceRegistrationOptions to customize, or set TocTitle = null to suppress the index entry entirely.

Verify

  • Run dotnet run and visit /reference/api/ — expect one <li> per public documented type, grouped by namespace.
  • Visit /reference/api/{some-type-slug}/ for a type you know has an xmldoc <summary> — expect the summary prose and a member table grouped by kind.
  • Add <xref:reference.api.{slug}> to any markdown page and confirm it resolves to the generated page after a rebuild.