How it works
The pieces that power a SharpDocs site.
AddSharpDocs() registers the singletons, and an internal hosted service warms them at startup so disk scanning happens once, before Kestrel begins listening.
ProjectRegistry
The entry point. At construction it reads the root sharpdocs.json, decides single- vs multi-project mode (presence of a projects array), and constructs a Project per entry.
- Single-project mode — the registry contains exactly one synthetic project, derived from the root config. Today's default.
- Multi-project mode — one
Projectper entry inprojects. Each carries its own docs index, search index, and resolved artifacts path.
Per-project failures degrade rather than crash. A bad sharpdocs.json or missing slug in one project marks that project with an error and lets the rest of the site boot.
DocsIndex (per project)
Walks a project's docs root, parses every *.md (YAML frontmatter + Markdig), and exposes a slug lookup plus the sidebar nav declared in that project's config.
Slug rules:
- File path relative to the project's docs root, with the extension stripped
\is normalized to/- A trailing
/indexis collapsed (sooverview/index.mdbecomes the slugoverview)
The sidebar must reference slugs that exist on disk. Missing slugs throw — in multi-project mode that throw is caught by the registry and degrades just that project.
SearchIndex (per project)
Tokenizes its project's pages and answers scoped search. Search is per-project, not unified — each project has its own index and search results never cross project boundaries. The scorer is hand-rolled:
- Title match: 10 points
- Heading match: 3 points
- Body token match: 1 point
LocalFolderSource
A single instance that scans *.nupkg across every project's artifacts root (one root in single-project mode, N in multi-project). Skips *.symbols.nupkg, parses each nuspec, and dedups (id, version) first-wins by source order. Pure read-through — restart the host to pick up new packages.
Upstream NuGet feeds
Optional. Configure one or more upstream NuGet v3 feeds via AddUpstream(...) or appsettings.json, and the /v3/* endpoints layer them behind your local artifacts. Service-index resolution runs once at startup with a per-upstream timeout — a slow or broken upstream logs and is excluded, never blocks the site. See NuGet feed.
Routing
The host calls app.MapSharpDocs(), which registers routes based on mode:
| Mode | URL | Handler |
|---|---|---|
| Single-project | /{**slug} |
docs page |
| Single-project | /search |
search |
| Multi-project | / |
landing picker (or single-project shortcut) |
| Multi-project | /docs/{projectId}/{**slug} |
docs page |
| Multi-project | /docs/{projectId}/search |
per-project search |
| Both | /v3/* |
NuGet feed |
| Both | /packages, /packages/{id} |
package browser + detail |
| Both | /api/manifest |
site/project manifest (JSON) |
Docs and search controllers return their _Article / _Results partials when the request has an HX-Request header (htmx swap), otherwise the full view.