DI and middleware extension methods
Index of every Add
The list of public extension methods Pennington exposes for wiring the library into an ASP.NET Core host — Add* (DI registration), Use* (middleware and endpoints), and Run* (host entry points). Grouped below by receiver type; each method is declared in an *Extensions static class under its owning feature namespace.
IServiceCollection extensions
DI registration entry points. The three composition roots and the options record each configures:
The full set follows, each tagged with its owning package.
AddApiMetadataFromCompiledAssemblyIServiceCollection AddApiMetadataFromCompiledAssembly(this IServiceCollection services, Action<CompiledAssemblyApiOptions> configure)Package
Convenience overload: registers under thePennington.ApiMetadata.Reflection"default"name for sites documenting a single library.AddApiMetadataFromCompiledAssemblyIServiceCollection AddApiMetadataFromCompiledAssembly(this IServiceCollection services, string name, Action<CompiledAssemblyApiOptions> configure)Package
RegistersPennington.ApiMetadata.ReflectionCompiledAssemblyApiMetadataProvideras a keyedIApiMetadataProviderundername. Call once per library you want to document — each call builds its ownMetadataLoadContextand xmldoc index scoped to the suppliedAssemblyDirectories. The sharedIXmlDocParser/IXmlDocHtmlRendererservices are registered once (idempotent).AddApiReferenceIServiceCollection AddApiReference(this IServiceCollection services, string name, Action<ApiReferenceRegistrationOptions> configure)Package
Registers one named API-reference tree. Call once per library you want to document. Each call pairs with a matchingPennington.DocSite.ApiAddApiMetadataFrom*(name, …)provider registration and publishes its type pages at the configuredRoutePrefix.AddBlogSiteIServiceCollection AddBlogSite(this IServiceCollection services, Func<BlogSiteOptions> configureOptions)Package
Registers BlogSite services with the provided options.Pennington.BlogSiteAddDataDirectory<TItem>IServiceCollection AddDataDirectory<TItem>(this IServiceCollection services, string name, string path)Package
Registers everyPennington.yml,.yaml, and.jsonfile inpathas a single aggregatedIReadOnlyListaccessible throughIDataFilesunder the lookup keyname. Each file contributes one record, or several when its root is an array; files are ordered by name. Edits, additions, and removals in the directory invalidate the cached value so the next read returns the fresh content.AddDataFile<T>IServiceCollection AddDataFile<T>(this IServiceCollection services, string name, string path)Package
RegistersPenningtonpathas a data file accessible throughIDataFilesunder the lookup keyname. Format is inferred from the file extension (.yml,.yaml,.json). Edits to the file invalidate the cached value so the next read returns the fresh content.AddDocSiteIServiceCollection AddDocSite(this IServiceCollection services, Func<DocSiteOptions> configureOptions)Package
Registers DocSite services with the provided options.Pennington.DocSiteAddFileWatched<T>IServiceCollection AddFileWatched<T>(this IServiceCollection services)Package
Register a concrete service whose instance is managed byPenningtonFileWatchDependencyFactory.AddFileWatched<TService, TImplementation>IServiceCollection AddFileWatched<TService, TImplementation>(this IServiceCollection services)Package
Register a service whose instance is managed byPenningtonFileWatchDependencyFactory. The factory (singleton) recreates the instance when the implementation'sOnFileChangedreturnsRecreate. The service (transient) always returns the current instance from the factory.AddHeadIServiceCollection AddHead(this IServiceCollection services)Package
Registers the head composition rewriter. Inert until at least onePenningtonIHeadContributoris also registered, so adding this on its own leaves head output byte-identical.AddHeadContributor<T>IServiceCollection AddHeadContributor<T>(this IServiceCollection services)Package
Registers a single head contributor. Transient so contributors capturing a file-watched dependency (e.g. the content registry) pick up the current instance per request.PenningtonAddLlmsSubtreeIServiceCollection AddLlmsSubtree(this IServiceCollection services, LlmsSubtree subtree)Package
Registers aPenningtonLlmsSubtreeso all leaves underRoutePrefixget split out into a dedicated{RoutePrefix}llms.txt. Multiple registrations are allowed; programmatic registrations override_meta.yml-discovered subtrees with the same prefix.AddMonorailCssIServiceCollection AddMonorailCss(this IServiceCollection services, Func<IServiceProvider, MonorailCssOptions> optionFactory)Package
Registers MonorailCSS services and the runtime class-discovery pipeline. With no configuration, the discovery pipeline force-loads every non-BCL assembly the app references, scans each one's IL, watches the project's source files in development, and loadsPennington.MonorailCsswwwroot/app.cssas the source CSS prefix when present. The CSS endpoint served byUseMonorailCssregenerates whenever the class set changes.AddPenningtonIServiceCollection AddPennington(this IServiceCollection services, Action<PenningtonOptions> configure)Package
Register all Pennington services.PenningtonAddPenningtonBookIServiceCollection AddPenningtonBook(this IServiceCollection services, Action<BookOptions> configure)Package
Adds PDF book generation: a per-locale book perPennington.BookBookDefinition(or one whole-site book when none are configured), served on demand at/pdf/{slug}.pdfin dev and emitted into the static build. Registers anIDownloadLinkProvidera host's chrome can advertise.AddPenningtonStylesIServiceCollection AddPenningtonStyles(this IServiceCollection services, IReadOnlyDictionary<string, string> templateSkin, Func<IReadOnlyDictionary<string, string>> styleOverrides, Func<string, string, string> classMerger)Package
Registers thePennington.UIStyleRegistry. Site templates call this internally with their skin; bare hosts composing Pennington.UI components directly can call it themselves (the components fall back to the built-in defaults when nothing is registered). When called more than once the last registration wins; the diag command is registered once.AddTaxonomy<TFrontMatter, TKey>IServiceCollection AddTaxonomy<TFrontMatter, TKey>(this IServiceCollection services, Action<TaxonomyOptions<TFrontMatter, TKey>> configure)Package
Registers aPenningtonTaxonomyContentServiceconfigured byconfigure. MultipleAddTaxonomycalls with the sameTFrontMatter/TKeypair coexist as long as each uses a distinctBaseUrl.AddTranslationAuditIServiceCollection AddTranslationAudit(this IServiceCollection services, Action<TranslationAuditOptions> configure)Package
RegisterPennington.TranslationAuditTranslationAuditoras anIBuildAuditor. Diagnostics land in the dev overlay (per-page) and in the build report (site-wide) automatically.AddTreeSitterIServiceCollection AddTreeSitter(this IServiceCollection services, Action<TreeSitterOptions> configure)Package
Adds tree-sitter based multi-language code-fragment extraction — thePennington.TreeSitter:symbolfence modifier. Services are registered only whenContentRootis configured.AddWordBreakIServiceCollection AddWordBreak(this IServiceCollection services, Action<WordBreakOptions> configure)Package
RegistersPenningtonWordBreakHtmlRewriterin the shared HTML rewriting pipeline, so long identifiers in the configured elements get<wbr>break opportunities without an extra DOM parse.AddYamlContextIServiceCollection AddYamlContext(this IServiceCollection services, YamlSerializerContext context)Package
Register a source-generatedPenningtonYamlSerializerContextso the types it covers deserialize without reflection (NativeAOT/trim-friendly). Types not covered by any registered context fall back to reflection. Satellite templates call this for their own front-matter records; end users call it for theirs.ReplaceContentRenderer<TOld, TNew>IServiceCollection ReplaceContentRenderer<TOld, TNew>(this IServiceCollection services)Package
Replaces every registeredPenningtonIContentRendererwithTNew, resolved through DI as a transient. TheTOldtype parameter documents the renderer being swapped out — it is informational and unused at runtime, but lets the call site read as "replace TOld with TNew".ReplaceContentRenderer<TOld, TNew>IServiceCollection ReplaceContentRenderer<TOld, TNew>(this IServiceCollection services, Func<IServiceProvider, TNew> factory)Package
Replaces every registeredPenningtonIContentRendererwith one produced byfactory. Use this overload when the new renderer takes ctor arguments DI cannot resolve (e.g. a version string or per-site constant).
WebApplication extensions
Middleware and endpoint wiring. The template Use* methods each wrap a fixed sequence, listed below.
RunBlogSiteAsyncTask RunBlogSiteAsync(this WebApplication app, string[] args)Package
Runs the BlogSite: either serves the app or performs a static build, based on command-line args.Pennington.BlogSiteRunDocSiteAsyncTask RunDocSiteAsync(this WebApplication app, string[] args)Package
Runs the DocSite: either serves the app or performs a static build, based on command-line args.Pennington.DocSiteRunOrBuildAsyncTask RunOrBuildAsync(this WebApplication app, string[] args)Package
Runs the host: serves live (no verb), builds the static site (Penningtonbuild), or runs a diagnostic command (diag <sub>). Everything flows through one System.CommandLine pipeline, so--help/--versionwork at the root and every subcommand. Build and diag run one-shot against a started in-memory host that is disposed afterward; serve hands off toRunAsync.UseBlogSiteWebApplication UseBlogSite(this WebApplication app)Package
Wires BlogSite middleware, Razor components, and RSS endpoint into the request pipeline.Pennington.BlogSiteUseDocSiteWebApplication UseDocSite(this WebApplication app)Package
Wires DocSite middleware and Razor components into the request pipeline.Pennington.DocSiteUseLiveReloadWebApplication UseLiveReload(this WebApplication app)Package
Adds live reload WebSocket support for development. Skipped during static build (seePenningtonPenningtonCli).UseLocaleRoutingWebApplication UseLocaleRouting(this WebApplication app)Package
Adds locale detection and URL path rewriting middleware. Must be calledPenningtonMapRazorComponentsso that Blazor routing sees the locale-stripped path (e.g.,/gen-z/schedulebecomes/schedule). Called automatically byUsePenningtonwhen it hasn't been called yet, but at that point it is too late for Blazor endpoint routing. Sites that use@pagedirectives with locale prefixes must call this explicitly.UseMonorailCssWebApplication UseMonorailCss(this WebApplication app, string path)Package
Maps the MonorailCSS stylesheet endpoint. The endpoint pulls the current class set from the discovery pipeline registered inPennington.MonorailCssAddMonorailCss, generates CSS, and serves it.UsePenningtonWebApplication UsePennington(this WebApplication app)Package
Configure the Pennington middleware pipeline.Pennington
UseDocSite middleware order
UseDocSite registers this sequence before mapping the Razor component endpoint:
UseLocaleRoutingUseAntiforgeryUseStaticFilesUseMonorailCssUsePenningtonMapRazorComponents<App>()
UseBlogSite middleware order
UseBlogSite registers the same sequence minus locale routing, which BlogSite does not wire:
UseAntiforgeryUseStaticFilesUseMonorailCssUsePenningtonMapRazorComponents<App>()
For why each step lands where it does, see Dev mode and build mode share one code path.
Run* host entry points
Host entry points that run one System.CommandLine pipeline: serve live with no verb, build the static site with build, or run a read-only inspection with diag <sub>. Build and diag run one-shot against a started in-memory host that is disposed afterward; serve hands off to RunAsync.
RunOrBuildAsync— the core dispatcher; call it directly on a bareAddPenningtonhost.RunDocSiteAsync— DocSite wrapper overRunOrBuildAsync.RunBlogSiteAsync— BlogSite wrapper overRunOrBuildAsync.
Example
A complete DocSite host wiring all three layers — AddDocSite, UseDocSite, RunDocSiteAsync — in call order.
using Pennington.DocSite;
var builder = WebApplication.CreateBuilder(args);
// Swap the bare `AddPennington` host for the DocSite template. `AddDocSite`
// wires the full documentation experience on top of Pennington core — a
// Blazor-rendered layout with sidebar navigation, header, search surface,
// outline nav, dark-mode toggle — driven entirely from `DocSiteOptions`.
builder.Services.AddDocSite(() => new DocSiteOptions
{
SiteTitle = "Scaffold Docs",
SiteDescription = "A minimal DocSite scaffold built on AddDocSite.",
GitHubUrl = "https://github.com/usepennington/pennington",
HeaderContent = """<a href="/">Scaffold Docs</a>""",
FooterContent = """<footer class="mt-16 py-8 text-center text-sm text-base-500">Built with Pennington DocSite.</footer>""",
});
var app = builder.Build();
// `UseDocSite` mounts locale routing, antiforgery, static files, Razor
// component routing (`Pages.razor` owns `/{*fileName:nonfile}`), MonorailCSS,
// SPA navigation, and the core Pennington middleware in the right order.
app.UseDocSite();
// `RunDocSiteAsync` delegates to `RunOrBuildAsync`, so `dotnet run` serves live
// and `dotnet run -- build <baseUrl> <outputDir>` generates static HTML. Both
// positional args are optional (defaults: `/` and `output`).
await app.RunDocSiteAsync(args);
See also
- Reference: CLI and build arguments
- Reference:
PenningtonOptions - Reference:
DocSiteOptions - Background: Dev mode and build mode share one code path