Most OpenAPI UIs for ASP.NET Core work the same way: you generate a spec,
then host a separate set of static files to render it. That's fine until
you want it to just... work, out of the box, offline, without another
build step in your pipeline.
I spent the last few months building Folio.AspNetCore — an OpenAPI
documentation and testing UI that ships entirely as a NuGet package. No
static files to host yourself, no CDN dependencies, no separate build
step. Install the package, point it at your OpenAPI document, done.
dotnet add package Folio.AspNetCore
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.UseFolio(options =>
{
options.SpecUrl = "/openapi/v1.json";
});
app.Run();
That's the whole setup. Open /folio and you get the full UI.
What you get
Browsing
- Endpoint list, grouped and searchable, generated straight from the spec
- A full, separately-browsable model/schema catalog — not just inline types buried in each endpoint
- Markdown-rendered descriptions (from the spec's own
descriptionfields)
A real request builder, not a raw JSON textarea
Parameters and request body fields get the right control for their
schema type — text inputs, number steppers, boolean toggles, enum
dropdowns, date pickers, nested object/array editors. You can still drop
into raw JSON if you want to.
Live code generation, in six languages
Every request gets an equivalent code snippet — cURL, JavaScript
(fetch), Python (requests), C# (HttpClient), Go (net/http),
PowerShell (Invoke-RestMethod) — updated live as you fill in the form,
before you even hit Send. Syntax-highlighted too.
Per-endpoint role visibility
If your endpoints use [Authorize(Roles = "...")], Folio shows exactly
which roles are required, right in the sidebar and on the endpoint page —
read directly from your routing metadata via EndpointDataSource.
Nothing to configure, nothing duplicated: it's a live mirror of what your
code already declares.
app.MapPost("/api/items", CreateItem)
.RequireAuthorization(new AuthorizeAttribute { Roles = "Admin" });
That attribute alone is enough — Folio picks it up automatically.
Environments, headers, auth
- Switch between base URLs (local/staging/prod) without touching code,
or import
servers[]straight from the spec - Global headers (e.g. an API key) applied to every request, separate from per-endpoint headers
- A dedicated modal for the spec's own security schemes (
apiKey, HTTP Bearer/Basic)
Gating the docs themselves
Two ways to put a login in front of the UI:
// Integrate with auth you already have
app.UseFolio(options =>
{
options.AuthorizationPolicy = "FolioAccess";
options.LogoutUrl = "/account/logout";
});
// Or a self-contained cookie-based login, zero existing auth required
builder.Services.AddFolioAuth(options =>
{
options.Users = builder.Configuration.GetSection("Users").Get<List<FolioUser>>() ?? [];
});
app.UseFolioAuth();
app.UseFolio(options => options.SpecUrl = "/openapi/v1.json");
Fine-grained control over what's shown
Every major UI element — the execute button, code generator, role
badges, schema viewer, environment switcher, and more — can be toggled
independently:
app.UseFolio(options =>
{
options.ShowTryItOut = false; // read-only docs deployment, say
});
...or bound from appsettings.json explicitly (Folio never reads config
on its own — this only runs if you call it):
app.UseFolio((context, options) =>
{
options.ReadFrom.Configuration(context.Configuration, "Folio");
});
How it's built
It's a classic ASP.NET Core middleware (app.Use...), not endpoint
routing. The UI's HTML/CSS/JS live as embedded resources, served through
a real StaticFileMiddleware on top of ManifestEmbeddedFileProvider —
proper ETag/Last-Modified/conditional requests, not hand-rolled file
serving.
Multi-targets .NET 6 through 9.
Try it
dotnet add package Folio.AspNetCore
- GitHub: https://github.com/bekov0004/Folio.AspNetCore
- NuGet: https://www.nuget.org/packages/Folio.AspNetCore
- Wiki (getting started, auth setup, FAQ): https://github.com/bekov0004/Folio.AspNetCore/wiki
Feedback and issues welcome — especially if something's missing for your
use case.
Top comments (0)