LowCodeHub.OpenApi
0.0.2
dotnet add package LowCodeHub.OpenApi --version 0.0.2
NuGet\Install-Package LowCodeHub.OpenApi -Version 0.0.2
<PackageReference Include="LowCodeHub.OpenApi" Version="0.0.2" />
<PackageVersion Include="LowCodeHub.OpenApi" Version="0.0.2" />
<PackageReference Include="LowCodeHub.OpenApi" />
paket add LowCodeHub.OpenApi --version 0.0.2
#r "nuget: LowCodeHub.OpenApi, 0.0.2"
#:package LowCodeHub.OpenApi@0.0.2
#addin nuget:?package=LowCodeHub.OpenApi&version=0.0.2
#tool nuget:?package=LowCodeHub.OpenApi&version=0.0.2
LowCodeHub.OpenApi
A composable OpenAPI configuration library for ASP.NET Core built on top of Microsoft.AspNetCore.OpenApi. Every aspect of your OpenAPI document — security, error responses, deprecation, versioning — is configured through a single fluent builder. No boilerplate, no scattered configuration.
Why This Library?
| Feature | LowCodeHub.OpenApi | Raw Microsoft.AspNetCore.OpenApi |
|---|---|---|
| Security schemes | Fluent builder — JWT, API Key, OAuth2, OIDC in one line | Manual transformer per scheme |
| Error responses | Auto-added ProblemDetails (400, 401, 403, 500) | Write your own operation transformer |
| Operation security | Automatic [AllowAnonymous] / [Authorize] detection |
Manual metadata inspection |
| Deprecation | Automatic [Obsolete] → deprecated: true |
Manual transformer |
| Enum schemas | String enums by default | Manual schema transformer |
| Modular monolith | One-liner per module with group filtering | Manual ShouldInclude per document |
| API versioning | Built-in multi-version document generation | Register each version manually |
| Server URLs | Fluent .WithServer() with description |
Inline transformer |
Installation
dotnet add package LowCodeHub.OpenApi
Quick Start
builder.Services.AddOpenApiDoc("my-api", api => api
.WithTitle("My API")
.WithVersion("v1")
.WithSecurity(sec => sec.AddJwtBearer())
.WithErrorResponses(err => err.UseDefaults())
.WithOperationSecurity()
.WithDeprecationSupport());
app.MapOpenApi();
That's it. Your OpenAPI document now has JWT Bearer security on all operations, ProblemDetails error responses (400/401/403/500), smart [AllowAnonymous] detection, and [Obsolete] deprecation support — all from a single fluent chain.
Table of Contents
- Fluent Builder API
- Security Schemes
- Operation-Level Security
- Common Error Responses
- Deprecation Support
- Enum String Schemas
- Server URLs
- Modular Monolith — Document Grouping
- API Versioning
- How It Works
- Requirements
- License
Fluent Builder API
Every configuration option flows through a single OpenApiBuilder:
builder.Services.AddOpenApiDoc("api", api => api
.WithTitle("My Service")
.WithVersion("v2")
.WithServer("https://api.example.com", "Production")
.WithServer("https://staging.example.com", "Staging")
.WithSecurity(sec => sec.AddJwtBearer())
.WithErrorResponses(err => err.UseDefaults())
.WithOperationSecurity()
.WithDeprecationSupport()
.WithEnumStringSchemas()); // on by default
The first parameter is the document name — it becomes the OpenAPI endpoint path (/openapi/{documentName}.json). You can register as many documents as you need.
Security Schemes
Configure one or more security schemes through the security builder. All schemes are added to the document's components.securitySchemes and applied as requirements on every operation.
JWT Bearer
api.WithSecurity(sec => sec.AddJwtBearer());
Registers an HTTP Bearer scheme with JWT format. Customize the scheme name or format:
api.WithSecurity(sec => sec.AddJwtBearer(schemeName: "JWT", bearerFormat: "JWT"));
API Key
api.WithSecurity(sec => sec.AddApiKey("X-API-Key", ParameterLocation.Header));
Supports Header, Query, and Cookie locations. The name defaults to "X-API-Key".
OAuth2
api.WithSecurity(sec => sec.AddOAuth2(oauth => oauth
.WithAuthorizationCodeFlow(
authorizationUrl: "https://auth.example.com/authorize",
tokenUrl: "https://auth.example.com/token",
scopes: new()
{
["read"] = "Read access",
["write"] = "Write access"
})
.WithClientCredentialsFlow(
tokenUrl: "https://auth.example.com/token")));
All four OAuth2 flows are supported:
| Method | Flow |
|---|---|
WithAuthorizationCodeFlow() |
Authorization Code |
WithClientCredentialsFlow() |
Client Credentials |
WithImplicitFlow() |
Implicit |
WithPasswordFlow() |
Resource Owner Password |
OpenID Connect
api.WithSecurity(sec => sec.AddOpenIdConnect(
"https://auth.example.com/.well-known/openid-configuration"));
Custom Schemes
Escape hatch for any scheme not covered by the built-in methods:
api.WithSecurity(sec => sec.AddCustomScheme("MyScheme", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "digest"
}));
Multiple Schemes
Chain multiple schemes in a single builder — all are registered and applied:
api.WithSecurity(sec => sec
.AddJwtBearer()
.AddApiKey("X-API-Key"));
Auto-Detection
Automatically detect security schemes from registered ASP.NET Core authentication schemes:
api.WithSecurity(sec => sec.AutoDetect());
If a "Bearer" authentication scheme is registered in your app, it's automatically added to the OpenAPI document. Combine with explicit schemes:
api.WithSecurity(sec => sec
.AddApiKey("X-API-Key")
.AutoDetect()); // also picks up Bearer if registered
Operation-Level Security
api.WithOperationSecurity();
When enabled, the library inspects endpoint metadata at document generation time:
[AllowAnonymous]→ security requirements are removed from that operation- All other endpoints keep the document-level security requirements
This means your public endpoints (login, health checks, etc.) won't show a lock icon in the UI, while protected endpoints do.
app.MapGet("/health", () => "ok"); // anonymous — no lock
app.MapGet("/orders", [Authorize] () => GetOrders()); // secured — lock shown
app.MapGet("/public", [AllowAnonymous] () => GetPublicData()); // anonymous — no lock
Common Error Responses
Automatically add RFC 7807 ProblemDetails error responses to all operations — no need to repeat [ProducesResponseType] everywhere.
Defaults
api.WithErrorResponses(err => err.UseDefaults());
Adds these responses to every operation:
| Status | Description | Condition |
|---|---|---|
| 400 | Bad Request | Always |
| 401 | Unauthorized | Authenticated endpoints only |
| 403 | Forbidden | Authenticated endpoints only |
| 500 | Internal Server Error | Always |
Custom Error Responses
Add or remove specific status codes:
api.WithErrorResponses(err => err
.UseDefaults()
.Add(404, "Not Found")
.Add(409, "Conflict")
.Add(429, "Too Many Requests")
.Exclude(403));
Use a custom schema reference instead of inline ProblemDetails:
api.WithErrorResponses(err => err
.Add(422, "Validation Error", "ValidationProblemDetails"));
Authenticated-Only Responses
Control which error responses only appear on authenticated endpoints:
api.WithErrorResponses(err => err
.Add(401, "Unauthorized")
.Add(403, "Forbidden")
.ForAuthenticatedOnly(401, 403));
When combined with WithOperationSecurity(), endpoints marked [AllowAnonymous] won't show 401/403 responses — keeping your docs clean and accurate.
Deprecation Support
api.WithDeprecationSupport();
Endpoints decorated with [Obsolete] are automatically marked as deprecated in the OpenAPI document:
app.MapGet("/v1/users", [Obsolete("Use /v2/users instead")] () => GetUsersV1());
Produces:
{
"deprecated": true,
"description": "Deprecated: Use /v2/users instead"
}
The [Obsolete] message is copied to the operation description (only if no description is already set).
Enum String Schemas
Enabled by default. All enum properties in your request/response models are rendered as string values instead of integers:
public enum OrderStatus { Pending, Processing, Shipped, Delivered }
// OpenAPI schema:
// { "type": "string", "enum": ["Pending", "Processing", "Shipped", "Delivered"] }
Nullable enums (OrderStatus?) are handled automatically with "type": ["string", "null"].
To opt out:
api.DisableEnumStringSchemas();
Server URLs
Add one or more server URLs with optional descriptions:
api.WithServer("https://api.example.com", "Production")
.WithServer("https://staging.example.com", "Staging")
.WithServer("https://localhost:5001", "Local Development");
These appear in the OpenAPI document's servers array and in UI server dropdowns.
Modular Monolith — Document Grouping
Generate separate OpenAPI documents per module — each module gets its own entry in the UI dropdown:
// Program.cs — register one document per module
builder.Services.AddOpenApiDoc("orders", api => api
.WithTitle("Orders API")
.WithGroupName("Orders")
.WithSecurity(sec => sec.AddJwtBearer())
.WithErrorResponses(err => err.UseDefaults()));
builder.Services.AddOpenApiDoc("inventory", api => api
.WithTitle("Inventory API")
.WithGroupName("Inventory")
.WithSecurity(sec => sec.AddJwtBearer())
.WithErrorResponses(err => err.UseDefaults()));
builder.Services.AddOpenApiDoc("identity", api => api
.WithTitle("Identity API")
.WithGroupName("Identity")
.WithSecurity(sec => sec.AddJwtBearer()));
In your modules, tag endpoints with the group name:
// Orders module
app.MapGroup("/api/orders")
.WithGroupName("Orders")
.MapOrderEndpoints();
// Inventory module
app.MapGroup("/api/inventory")
.WithGroupName("Inventory")
.MapInventoryEndpoints();
Each module gets its own OpenAPI document at /openapi/{documentName}.json:
/openapi/orders.json— only Orders endpoints/openapi/inventory.json— only Inventory endpoints/openapi/identity.json— only Identity endpoints
Each module can have different security configurations, error responses, and server URLs — complete isolation.
API Versioning
Generate versioned OpenAPI documents from a single configuration:
builder.Services.AddOpenApiDoc(api => api
.WithTitle("My API")
.WithSecurity(sec => sec.AddJwtBearer())
.WithErrorResponses(err => err.UseDefaults())
.WithVersioning(v => v
.ForVersions("v1", "v2")
.WithTitleFormat("{0} — {1}")
.WithServerUrl("https://api.example.com")));
This registers one OpenAPI document per version:
| Document | Title | Group filter | Endpoint |
|---|---|---|---|
v1 |
My API — v1 | GroupName == "v1" |
/openapi/v1.json |
v2 |
My API — v2 | GroupName == "v2" |
/openapi/v2.json |
Tag your endpoints with the version group name:
var v1 = app.MapGroup("/api/v1").WithGroupName("v1");
v1.MapGet("/users", () => GetUsersV1());
var v2 = app.MapGroup("/api/v2").WithGroupName("v2");
v2.MapGet("/users", () => GetUsersV2());
Title format
The WithTitleFormat method uses string.Format with {0} = base title, {1} = version:
| Format | Result |
|---|---|
"{0} {1}" (default) |
My API v1 |
"{0} — {1}" |
My API — v1 |
"{0} (Version {1})" |
My API (Version v1) |
Server URL
WithServerUrl() overrides the server URL for all versioned documents — useful when all versions share the same base URL:
.WithVersioning(v => v
.ForVersions("v1", "v2")
.WithServerUrl("https://api.example.com", "Production"))
How It Works
┌─────────────────────────────────┐
│ AddOpenApiDoc("name", │
│ api => api.WithTitle(...) │
│ .WithSecurity(...) │ ← Your configuration
│ .WithErrorResponses(...) │
│ .WithOperationSecurity() │
│ ) │
└───────────────┬─────────────────┘
│
▼
┌─────────────────────────────────┐
│ OpenApiBuilder │
│ ├── LowCodeHubOpenApiOptions │ ← Accumulated config
│ └── Apply(OpenApiOptions) │
└───────────────┬─────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────┐
│Document │ │Operation│ │ Schema │
│Transform│ │Transform│ │Transform │
├─────────┤ ├─────────┤ ├──────────┤
│Security │ │OpSec │ │Enum │
│Scheme │ │(anon │ │String │
│Transf. │ │ detect) │ │Schemas │
│ │ │ │ │ │
│Info & │ │Deprec. │ │ │
│Servers │ │Transf. │ │ │
│ │ │ │ │ │
│ │ │Error │ │ │
│ │ │Response │ │ │
│ │ │Transf. │ │ │
└─────────┘ └─────────┘ └──────────┘
- Registration —
AddOpenApiDoc()creates a builder, runs your configuration lambda, stores the options as a keyed singleton, and callsAddOpenApi()with the builder'sApplymethod. - Document transformers run first — set document info, servers, and security schemes at the document level.
- Operation transformers run per-endpoint — remove security from anonymous endpoints, mark deprecated operations, and add error responses.
- Schema transformers run per-type — convert enums to string representations.
All transformers retrieve their configuration from LowCodeHubOpenApiOptions via keyed DI, scoped to the document name.
Requirements
- .NET 10 or later
Microsoft.AspNetCore.OpenApi10.0.3+ (included as a dependency)
License
MIT © Ahmed Abuelnour
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.OpenApi (>= 10.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.