DRN.Framework.Hosting
0.10.0-preview002
Prefix Reserved
dotnet add package DRN.Framework.Hosting --version 0.10.0-preview002
NuGet\Install-Package DRN.Framework.Hosting -Version 0.10.0-preview002
<PackageReference Include="DRN.Framework.Hosting" Version="0.10.0-preview002" />
<PackageVersion Include="DRN.Framework.Hosting" Version="0.10.0-preview002" />
<PackageReference Include="DRN.Framework.Hosting" />
paket add DRN.Framework.Hosting --version 0.10.0-preview002
#r "nuget: DRN.Framework.Hosting, 0.10.0-preview002"
#:package DRN.Framework.Hosting@0.10.0-preview002
#addin nuget:?package=DRN.Framework.Hosting&version=0.10.0-preview002&prerelease
#tool nuget:?package=DRN.Framework.Hosting&version=0.10.0-preview002&prerelease
DRN.Framework.Hosting
Application shell for DRN web applications with security-first design, structured lifecycle, and type-safe routing.
TL;DR
- Security defaults:
DrnDefaultsenforces MFA, nonce-based script CSP, and HSTS outside Development. - Startup:
DrnProgramBaseprovides hooks for registration, middleware, and validation. - Routing: Typed controller endpoint accessors bind to mapped routes; page accessors generate Razor paths.
- Local infrastructure:
DRN.Framework.Testingprovides opt-in Postgres provisioning. - Frontend: Razor TagHelpers handle Vite manifests, CSP nonces, and HTMX CSRF headers.
Table of Contents
- QuickStart: Beginner
- QuickStart: Advanced
- Directory Structure
- Lifecycle & Execution Flow
- DrnProgramBase Deep Dive
- Configuration
- Security Features
- Endpoint Management
- Razor TagHelpers
- Developer Diagnostics
- Modern HTTP Standards
- Static Asset Pre-Warming
- Local Development
- Hosting Utilities
- Global Usings
- Related Packages
QuickStart: Beginner
Use a .NET 10 web project with a DRN.Framework.Hosting package reference. Inherit from DrnProgramBase<TProgram> and register application services. Configure Environment and NLog before calling RunAsync; see Configuration.
using DRN.Framework.Hosting.DrnProgram;
using DRN.Framework.Hosting.HealthCheck;
using DRN.Framework.Utils.DependencyInjection;
using DRN.Framework.Utils.Logging;
using DRN.Framework.Utils.Settings;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
namespace Sample.Hosted;
public class Program : DrnProgramBase<Program>, IDrnProgram
{
public static async Task Main(string[] args) => await RunAsync(args);
protected override Task AddServicesAsync(
WebApplicationBuilder builder,
IAppSettings appSettings,
IScopedLog scopedLog)
{
builder.Services.AddServicesWithAttributes();
return Task.CompletedTask;
}
}
// WeatherForecastControllerBase supplies [AllowAnonymous] and [HttpGet].
[Route("[controller]")]
public class WeatherForecastController : WeatherForecastControllerBase;
QuickStart: Advanced
Use DRN.Framework.Testing for integration tests that create the application pipeline and its configured database dependencies. Place tests using DrnTestContext in an integration test project.
public class WeatherForecastTests
{
[Theory, DataInline]
public async Task WeatherForecast_Should_Return_Data(DrnTestContext context)
{
var client = await context.ApplicationContext.CreateClientAsync<Program>();
var response = await client.GetAsync("WeatherForecast");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var data = await response.Content.ReadFromJsonAsync<IEnumerable<WeatherForecast>>();
data.Should().NotBeEmpty();
}
}
ApplicationContext automatically uses the active xUnit v3 output helper when each application is created, but only
while a debugger is attached to preserve the log privacy gate. Do not request ITestOutputHelper as a [DataInline]
theory parameter for application logging: AutoFixture supplies an interface substitute, not xUnit's runner-owned
helper.
Directory Structure
DRN.Framework.Hosting/
├── DrnProgram/ # DrnProgramBase, options, actions, conventions
├── Endpoints/ # EndpointCollectionBase, PageForBase, type-safe accessors
├── Auth/ # Policies, MFA configuration, requirements
├── BackgroundServices/ # StaticAssetWarmService (pre-warm compressed assets)
├── Consent/ # GDPR cookie consent management
├── Extensions/ # Configuration, controller context, endpoint helpers
├── HealthCheck/ # WeatherForecastControllerBase for quick health checks
├── Identity/ # Identity integration and scoped user middleware
├── Middlewares/ # HttpScopeMiddleware, exception handling, security middlewares
├── Nexus/ # Nexus HTTP request and client helpers
├── RateLimiting/ # Pre-auth and post-auth rate-limit rules
├── TagHelpers/ # Razor TagHelpers (Vite, Nonce, CSRF, Auth-Only, Anon-Only)
├── Utils/ # AppStartupStatus, ServerSettings, Vite manifest, ResourceExtractor
├── Areas/ # Framework-provided Razor Pages (e.g., Error pages)
├── buildTransitive/ # NuGet publish integration
├── wwwroot/ # Framework style and script assets
Lifecycle & Execution Flow
Use configuration hooks for services and options, pipeline hooks for middleware, and DrnProgramActions for startup integration. The diagram shows startup order; the pipeline table lists the complete request sequence.
flowchart TD
Start(["RunAsync: settings and bootstrap logging"])
Start --> Builder["ConfigureSwaggerOptions; create builder and load settings"]
Builder --> Configure["ConfigureApplicationBuilder; AddServicesAsync"]
Configure --> Callback["Optional configureBuilder callback"]
Callback --> Created["ApplicationBuilderCreatedAsync"]
Created --> Build["Build; ConfigureApplication"]
Build --> Built["ApplicationBuiltAsync"]
Built --> Validate["ValidateEndpoints; ValidateServicesAsync"]
Validate --> Validated["ApplicationValidatedAsync"]
Validated --> Temporary{"Temporary application?"}
Temporary -->|"Yes"| Return(["Return without starting"])
Temporary -->|"No"| Run["StartAsync; WaitForShutdownAsync"]
Run --> Dispose(["Dispose application"])
%% Phase and decision styles
classDef step fill:#FFFFFF,stroke:#37474F,stroke-width:2px,color:#263238
classDef decision fill:#FFE0B2,stroke:#E65100,stroke-width:3px,color:#263238
class Start,Builder,Configure,Callback,Created,Build,Built,Validate,Validated,Return,Run,Dispose step
class Temporary decision
The four-argument CreateApplicationAsync(args, appSettings, scopeLog, configureBuilder) overload invokes its builder callback after AddServicesAsync and before ApplicationBuilderCreatedAsync. The three-argument overload omits that callback. Both return a configured application without starting it; RunAsync owns startup and shutdown.
Define at most one concrete DrnProgramActions subclass in the application assembly, with a public parameterless constructor. Its three callbacks run at the points shown above. See local development for an example.
Temporary applications build and configure the request pipeline, then enter the service-validation phase, which honors DrnDevelopmentSettings:SkipValidation. They skip endpoint validation and endpoint-accessor population, and return before the host calls StartAsync.
DrnProgramBase Deep Dive
Override these hooks to customize startup and request processing. Preserve base calls when extending defaults; replacing a hook makes the application responsible for the behavior it removes.
1. Configuration Hooks (Builder Phase)
These hooks register services or customize configuration. Options callbacks run when their options are created. Security-header callbacks run when the policy provider builds its policies.
| Category | Method | Purpose |
|---|---|---|
| Builder | ConfigureApplicationBuilder |
Register hosting services and callbacks before AddServicesAsync. Call base to retain DRN wiring. |
| Logging | ConfigureLoggingBuilder |
Clear default providers, apply logging configuration, and register NLog when its section exists. See NLog for bootstrap requirements. |
| WebHost | ConfigureWebHostBuilder |
Configure Kestrel options (suppresses Server header, applies optional Kestrel section, registers static web assets). |
| OpenAPI | ConfigureSwaggerOptions |
Customize Swagger UI title, version, and visibility settings. |
| MVC | ConfigureMvcBuilder |
Add ApplicationParts, custom formatters, or MVC/Razor options. See Razor Development. |
| MVC | ConfigureMvcOptions |
Add global filters, conventions, or customize model binding. |
| Auth | ConfigureAuthorizationOptions |
Define policies; both default and fallback policies require MFA. |
| Security | ConfigureDefaultSecurityHeaders |
Define global headers (HSTS, CSP, FrameOptions). |
| Security | ConfigureDefaultCsp |
Customize CSP directives (Script, Image, Style sources). |
| Security | ConfigureDefaultCspBase |
Customize shared CSP directives such as base URI, form actions, and frame ancestors. |
| Security | ConfigureSecurityHeaderPolicyBuilder |
Advanced conditional security policies (e.g., per-route CSP). |
| Cookies | ConfigureCookiePolicy |
Set GDPR consent logic and security attributes for all cookies. |
| Cookies | ConfigureCookieTempDataProvider |
Configure TempData cookie settings (HttpOnly, IsEssential). |
| Identity | ConfigureIdentityRenewal(IServiceCollection, IAppSettings); ConfigureSecurityStampValidatorOptions(SecurityStampValidatorOptions, IAppSettings, AuthenticationClaimConfig) |
Register/customize Identity cookie renewal while retaining shared claim configuration. |
| Infras. | ConfigureStaticFileOptions |
Customize static-file serving, one-year public caching, and HTTPS compression. |
| Infras. | ConfigureForwardedHeadersOptions |
Configure proxy/load-balancer header forwarding. |
| Infras. | ConfigureRequestLocalizationOptions |
Configure culture providers and supported cultures. |
| Infras. | ConfigureHostFilteringOptions |
Configure allowed hosts for host header validation. |
| Infras. | ConfigureResponseCachingOptions |
Set cache limits; defaults to a 16 MiB maximum body size and case-insensitive paths. |
| Infras. | ConfigureResponseCompressionOptions |
Configure MIME types and HTTPS eligibility. See compression defaults. |
| Infras. | ConfigureCompressionProviders |
Configure Brotli and Gzip provider options. |
| Infras. | ConfigureBrotliCompressionLevel, ConfigureGzipCompressionLevel |
Set provider levels; both default to SmallestSize. |
| Global | AddServicesAsync |
Required application service registration hook. |
Razor Development
DRN uses Razor SDK build-time and publish-time compilation. For local .cshtml iteration, use IDE Hot Reload or dotnet watch instead of Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; runtime compilation is obsolete in .NET 10 and disables Hot Reload.
References:
2. Pipeline Hooks (Application Phase)
With DrnDefaults, ConfigureApplication installs the following sequence. Rows without a hook name are framework middleware between hooks.
| Order | Hook or middleware | Default behavior |
|---|---|---|
| 1 | ConfigureApplicationPipelineStart |
Forwarded headers, host filtering, cookie policy, security headers, and HSTS outside Development. |
| 2 | ConfigureApplicationPreScopeStart |
Response caching, response compression, then static files. Caching can store compressed variants. |
| 3 | HttpScopeMiddleware |
Request scope, logging, buffering, exception handling, and response controls. |
| 4 | ConfigureApplicationPostScopeStart |
Empty hook for middleware needing the request scope before routing. |
| 5 | UseRouting |
Select the endpoint and its metadata. |
| 6 | PreAuthRateLimitingMiddleware |
Pre-auth rules when rate limiting is enabled. |
| 7 | ConfigureApplicationPreAuthentication |
Request localization when enabled. |
| 8 | UseAuthentication, then ScopedUserMiddleware |
Authenticate and populate the scoped user. |
| 9 | UseRateLimiter |
Post-auth rules and named policies when rate limiting is enabled. |
| 10 | ConfigureApplicationPostAuthentication |
MFA exemption middleware, then MFA redirection middleware, each when configured. |
| 11 | UseAuthorization |
Enforce endpoint authorization. |
| 12 | ConfigureApplicationPostAuthorization |
Map Swagger and add its UI when enabled. |
| 13 | MapApplicationEndpoints |
Map controllers and Razor Pages. Override to add other endpoints, such as hubs. |
Static files served at step 2 bypass the request scope, authentication, authorization, and rate limiting. Serve only public assets through this stage.
3. Verification Hooks
| Hook | Purpose |
|---|---|
ValidateEndpoints |
Binds and validates controller endpoint accessors against mapped routes and records mapped page endpoints. |
ValidateServicesAsync |
Scans the container for [Attribute] based registrations and ensures they are resolvable at startup via ValidateServicesAddedByAttributesAsync. |
For MFA hooks and examples, see MFA.
4. Properties
| Property | Default | Purpose |
|---|---|---|
AppBuilderType |
DrnDefaults |
DrnDefaults applies the complete DRN hosting and security pipeline. Empty, Slim, and Default are advanced opt-out modes; the application must configure its required services, middleware, and endpoints. |
DrnProgramSwaggerOptions |
(Object) | Toggles Swagger generation. Defaults to IsDevelopmentEnvironment. |
NLogOptions |
(Object) | Controls NLog bootstrapping (e.g., replace logger factory). |
DrnProgramBase also automatically registers the public IEndpointAccessor dependency-injection contract as a singleton. Consumers can inject IEndpointAccessor to query the endpoint collections (Endpoints, ApiEndpoints, PageEndpoints, and PageEndpointByPaths) populated by ValidateEndpoints before service validation (ValidateServicesAsync) runs.
Configuration
Layering
Later sources override earlier ones. Keep local connection strings in User Secrets rather than committed settings files.
appsettings.jsonappsettings.{Environment}.json- User Secrets when the application assembly can be loaded
- Environment Variables (
ASPNETCORE_,DOTNET_, then unprefixed) - Mounted Directories (default:
/appconfig) - Command Line Arguments
Environment is required and must be Development, Staging, or Production. Bootstrap resolves it from appsettings.json, environment variables, mounted settings, or command-line arguments before loading environment-specific settings or User Secrets. Missing, NotDefined, or unknown values fail startup with ConfigurationException.
Host Filtering
AllowedHosts must be configured outside Development and cannot be *. Development may fall back to * for local convenience; production and staging should use explicit host names such as example.com;api.example.com.
Reference Configurations
NLog (Logging)
Minimal NLog configuration for console output. Add and route a Graylog target if your deployment uses Graylog. Logging providers are cleared and NLog is added during host construction only when the NLog configuration section exists, while RunAsync still requires the section for bootstrap logging.
{
"NLog": {
"throwConfigExceptions": true,
"targets": {
"async": true,
"console": {
"type": "Console",
"layout": "${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}"
}
},
"rules": [
{ "logger": "*", "minLevel": "Info", "writeTo": "console" }
]
}
}
Kestrel (Server)
{
"Kestrel": {
"EndpointDefaults": { "Protocols": "Http1" },
"Endpoints": {
"All": { "Url": "http://*:5988" }
}
}
}
Forwarded Headers (Reverse Proxy & Gateway)
ConfigureForwardedHeadersOptions configures ASP.NET Core ForwardedHeadersOptions for reverse proxy, load balancer, and gateway header forwarding.
DRN trusts loopback and RFC 1918 networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) with ForwardLimit = 2. This supports private-network proxies. Without trusted forwarding, requests can share the proxy IP and its rate-limit quota.
Remove private-network defaults with
TrustPrivateNetworks: false:{ "ForwardedHeaders": { "TrustPrivateNetworks": false } }With no other entries configured, this retains loopback (
127.0.0.0/8,::1/128).Configure networks and proxies:
{ "ForwardedHeaders": { "ForwardLimit": 2, "KnownIPNetworks": [ "10.244.0.0/16", { "BaseAddress": "192.168.1.0", "PrefixLength": 24 } ], "KnownProxies": [ "10.0.0.100" ] } }A nonempty
KnownIPNetworkslist replaces the network defaults.KnownProxiesadds individual proxies and does not clear existing proxies or networks. For an exact allowlist, override the options callback and clear both collections before adding trusted entries.
Security Features
The defaults in this section apply to AppBuilderType.DrnDefaults when the base lifecycle hooks are preserved. Other builder modes require the application to configure its complete security and middleware pipeline.
MFA
MFA is required by default, including endpoints with role or named policies.
Use [AllowAnonymous] for public endpoints such as login. Authenticated endpoints have two separate MFA exemption paths:
| Path | Requirement |
|---|---|
AuthPolicy.MfaExempt |
Authentication without MFA. Add a scheme restriction if required. |
ConfigureMFAExemption |
An eligible scheme must also be selected by the endpoint policy or default authentication scheme. |
For restricted enrollment or challenge access, use the Identity policies below. They add scheme and credential-state checks.
Sample and Nexus register Identity.BearerAndApplication as their default through AddIdentityApiEndpoints. It tries bearer authentication first, then application cookies when no bearer token is present. An invalid bearer token does not fall back to cookies. See the ASP.NET Core implementation.
Configuration
Configure MFA behavior by overriding these hooks in your DrnProgramBase implementation:
// Page accessors from Sample.Hosted.
protected override MfaRedirectionConfig ConfigureMFARedirection()
=> new(
mfaSetupUrl: Get.Page.User.Management.EnableAuthenticator,
mfaLoginUrl: Get.Page.User.LoginWith2Fa,
loginUrl: Get.Page.User.Login,
logoutUrl: Get.Page.User.Logout,
appPages: Get.Page.All
);
// Optional; ordinary Identity applications need no claim override.
protected override AuthenticationClaimConfig ConfigureAuthenticationClaims()
=> AuthenticationClaimConfig.Default;
// Eligible schemes must also be selected for authentication.
protected override MfaExemptionConfig ConfigureMFAExemption()
=> new() { ExemptAuthSchemes = ["ApiKey", "Certificate"] };
ConfigureAuthenticationClaims defaults to Identity claim types and amr=mfa. See Renewal and assurance for custom mappings and their migration requirements.
For example, register ApiKey, add it to the exemption list, and select it in an API policy that requires an API scope. Listing ApiKey alone does not let it access default endpoints using Identity.BearerAndApplication. Programmatic MFA checks without an HTTP policy context require completed MFA.
Shared MFA authorization does not require ASP.NET Core Identity or its database. External providers need trusted authentication handlers and claim mapping. Local browser redirection is opt-in; return null from ConfigureMFARedirection to omit it.
Absent or incomplete authentication configuration
Register an effective authentication, challenge, and forbid scheme through AddAuthentication or endpoint policies. With multiple schemes, select them explicitly. A missing challenge/forbid scheme causes a server configuration error (HTTP 500), not an authentication response. An app can start and serve [AllowAnonymous] endpoints without schemes.
References: MVC registration, scheme selection, authentication errors.
Renewal and assurance
Register the Identity sign-in integration and MFA policies through the standard builder:
services.AddIdentityApiEndpoints<AppUser>()
.AddSignInManager<DrnSignInManager<AppUser>>();
services.AddDrnIdentityMfaPolicies();
AddDrnIdentityMfaPolicies is in DRN.Framework.Hosting.Identity. IdentityMfaPolicy.Enrollment uses the Identity cookie/bearer composite. BrowserEnrollment and Challenge use application cookies only. Pass identityApiScheme to select an equivalent application-owned composite.
Derive custom sign-in managers from DrnSignInManager<TUser> to retain DRN's renewal behavior. When customizing ConfigureSecurityStampValidatorOptions, set callbacks before calling base. Omit ConfigureIdentityRenewal wiring only when Identity cookie renewal is unused.
Refresh preserves the original authentication age; it does not count as fresh MFA. MfaPrincipal.IsRecent and IsPhishingResistant are opt-in checks requiring supporting evidence from the authentication provider.
For a provider with custom claim names:
protected override AuthenticationClaimConfig ConfigureAuthenticationClaims() => new()
{
Subject = new("sub"), Name = new("preferred_username"), Email = new("email"),
Roles = new("roles"), Mfa = new("acr", "urn:example:mfa")
};
Replace the example MFA value with one guaranteed by your provider. Custom subject, name, email, role, and MFA mappings also replace their corresponding aliases. Configure the validating handler to issue these claims and native name/role mappings. This hook does not implement provider login or refresh.
Use this hook rather than changing IdentityOptions.ClaimsIdentity separately. Changing subject types may require reauthentication. See the Utils claim contract.
Audit events
With global MFA enabled, these Information-level events support log filters and alerts.
| Event name | ID | Meaning |
|---|---|---|
MfaAuthorizationChallenge |
7401 | Authentication challenge. |
MfaAuthorizationForbid |
7402 | Access denied. |
MfaAuthorizationExemption |
7403 | Effective policy or scheme exemption. |
HostingLogEvents in DRN.Framework.Hosting.Logging exposes these public EventId fields. See Logging conventions for consumer catalogs and filtering.
MFA decisions use the shared scope-event API:
scopedLog.WithEvent(new ScopeEvent(
HostingLogEvents.MfaAuthorizationForbid,
Outcome: "forbid",
Reason: "mfa_required"));
ScopeEvent comes from DRN.Framework.Utils.Logging. Filter audit events by logger category and event ID; use scoped logs for request diagnostics. Request logs may contain identifiers, so configure retention accordingly.
Identity Revocation Contract
Under the default Identity handlers, credential revocation depends on the credential type:
| Credential | After a persisted security-stamp change |
|---|---|
| Refresh token | The next /Refresh request rejects it; expiration is checked independently. |
| Application cookie | Rejected on the next request eligible for stamp validation: elapsed time since ticket issuance must be greater than SecurityStampValidatorOptions.ValidationInterval. This is request-driven, not a background deadline. |
| Opaque bearer access token | Remains usable until its own expiration, unless the application adds rejection checks. Stamp rotation alone does not invalidate it. |
Cookie timing follows SecurityStampValidator; access-token timing follows BearerTokenHandler. Configured handlers, stores, validation intervals and token lifetimes can change these bounds.
UpdateSecurityStampAsync, factor enable/disable, authenticator-key reset, and password reset rotate the stamp. Recovery-code generation and redemption do not. To revoke sessions after recovery, rotate the stamp and account for outstanding access-token lifetime. See UserManager.
Identity API MFA Setup Flow
With global MFA enabled, password login without an enrolled factor issues a five-minute MfaSetupRequired credential:
- Cookie requests: Return an empty HTTP 200 response and set a non-persistent cookie with refresh disabled.
- Bearer requests: Return an HTTP 200
AccessTokenResponsecontaining the setup access token,ExpiresIn = 300, and an emptyRefreshToken.
Use the credential with IdentityManagementControllerBase.TwoFactorAuth to retrieve a shared key and enroll with a valid code. Discard it after enrollment. Call Login again with the password and an authenticator or recovery code to obtain completed MFA.
A setup credential does not grant normal MFA-protected access.
With global MFA disabled, users without a factor can enroll using an ordinary login credential. After enrollment, factor management requires completed MFA; denied operations return HTTP 403. Other management endpoints retain normal MFA requirements. Register the Identity policies as shown in Renewal and assurance.
Remaining MFA work
Fresh step-up/replay protection, recovery workflows, passkeys, provider-specific OIDC integrations, and factor/recovery audit events are not complete. See the implementation roadmap before relying on these capabilities.
Disabling global MFA
In your existing DrnProgramBase subclass, replace both default policies with an authenticated-user policy. Explicit MFA policies still apply. Remove any redirection or exemption overrides, or return null from them.
protected override void ConfigureAuthorizationOptions(AuthorizationOptions options)
{
base.ConfigureAuthorizationOptions(options);
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.DefaultPolicy = policy;
options.FallbackPolicy = policy;
}
Browser security
DRN generates a request-specific cryptographic nonce.
- Baseline:
default-src 'none'; script elements require the request nonce. Other directives permit same-origin styles, images, fonts, connections, media, manifests, and workers, plus inline style attributes,data:images and fonts, andblob:workers. - Automatic Protection: Inline scripts and inline style elements without a matching nonce are blocked. Inline style attributes remain allowed by the default policy.
- Usage: Activate and use the
NonceTagHelperbelow to add the request nonce.
Standard security headers are injected into responses:
- HSTS: Strict-Transport-Security (2 years, includes subdomains) outside Development.
- FrameOptions:
DENY(prevents clickjacking). - ContentTypeOptions:
nosniff. - ReferrerPolicy:
strict-origin-when-cross-origin. - Cross-Origin: COOP
same-origin, COEPcredentialless, and CORPsame-site. - PermissionsPolicy: Secure default directives with fullscreen limited to self.
Cookies and consent
Cookies use SameSite=Strict. Secure is Always outside Development and SameAsRequest in Development. Antiforgery and TempData cookies are HttpOnly; the global policy does not force it for client-readable cookies.
CheckConsentNeeded = true withholds nonessential response cookies until consent is granted. ConsentContext exposes the current request's preferences. Applications decide which scripts need consent and which cookies are essential.
For example, load an application-owned analytics entry only after analytics consent:
@using DRN.Framework.Hosting.Consent
@if (ConsentContext.ConsentCookie.Values.AnalyticsConsent == true)
{
<script src="buildwww/app/js/analytics.js"
crossorigin="anonymous"></script>
}
Route-specific security headers
Leading and trailing slashes are removed from the configured Swagger UI prefix before both middleware routing and CSP selection; /docs/ becomes docs.
When Swagger is enabled, its UI RoutePrefix must be nonempty after trimming slashes, for example swagger, docs/swagger, or api-docs. Validation runs after ConfigureSwaggerUIOptionsAction. A null, empty, or slash-only prefix fails startup with ConfigurationException: a root mount would apply Swagger CSP across the application.
The complete configured subtree is reserved for CspFor.CspPolicySwagger, including application endpoints beneath it. Matching is case-insensitive and respects path segment boundaries. Keep ordinary application pages outside that subtree. Disabling Swagger leaves application policies unchanged.
The Swagger policy allows scripts from 'self' and styles from 'self' 'unsafe-inline' to support inline SVG styles without changing the generated document or its caching. It replaces the shared style-src directive. Self/inline endpoint policies retain their nonce-based styles. Replace CspFor.CspPolicySwagger through builder.AddPolicy after calling base to customize Swagger independently; selecting that name through CspPolicyName does not opt unrelated endpoints into it.
Customize security headers for specific routes by overriding ConfigureSecurityHeaderPolicyBuilder. This example replaces the base policy selector: routes outside /legacy receive the default policy, including Swagger routes. Preserve the base selection rules too if the application uses Swagger or endpoint-selected CSP policies.
protected override void ConfigureSecurityHeaderPolicyBuilder(
SecurityHeaderPolicyBuilder builder,
IServiceProvider serviceProvider,
IAppSettings appSettings)
{
base.ConfigureSecurityHeaderPolicyBuilder(builder, serviceProvider, appSettings);
// Allow legacy inline scripts on /legacy routes.
var legacyPolicy = new HeaderPolicyCollection();
ConfigureDefaultSecurityHeaders(legacyPolicy, serviceProvider, appSettings);
legacyPolicy.Remove("Content-Security-Policy");
legacyPolicy.AddContentSecurityPolicy(csp =>
{
ConfigureDefaultCspBase(csp);
csp.AddFrameAncestors().Self();
csp.AddScriptSrc().Self().UnsafeInline(); // Only for selected legacy routes
});
builder.AddPolicy("legacy-inline-csp", legacyPolicy);
builder.SetPolicySelector(selector =>
selector.HttpContext.Request.Path.StartsWithSegments("/legacy")
? selector.ConfiguredPolicies["legacy-inline-csp"]
: selector.DefaultPolicy);
}
Rate Limiting
DRN Hosting adds two composable limiter phases:
- Pre-auth runs after routing and before authentication. It evaluates singleton rules only and uses a coarse IP default to reject obvious abuse before auth and MFA work. Add a custom singleton rule for trusted-header partitioning behind a correctly configured edge proxy.
- Post-auth runs after
ScopedUserMiddleware. It can use singleton and scoped rules, including user, tenant, account, claim, or endpoint partitions.
Defaults are token buckets: 1,000 tokens/minute per pre-auth IP partition and 100 tokens/minute per post-auth user partition. Post-auth uses the authenticated subject and authentication type; requests without a usable authenticated subject fall back to IP. Rejections return 429 Too Many Requests; Retry-After is included only when the rejecting limiter supplies retry metadata.
DRN's built-in limiter state is process-local. In horizontally scaled production deployments, enforce coarse limits at the edge (WAF/CDN/API gateway/load balancer) or add a distributed/custom limiter for quotas that must hold across every application instance.
Endpoint metadata behavior:
[DisableRateLimiting]bypasses DRN pre-auth and post-auth limiting, plus ASP.NET Core post-auth policies.[EnableRateLimiting("policy-name")]selects ASP.NET Core named post-auth policies. DRN pre-auth remains global; DRN rules with matchingPolicyNamecompose with the named policy.- Static files served before routing are naturally outside the limiter path.
When many users share an edge IP, raise the pre-auth quota or use a trusted-header rule. Use scoped post-auth rules for account or tenant quotas, as shown below.
Settings Quick Reference
Configure defaults under DrnAppFeatures:DrnRateLimit. Read them through IAppSettings.Features.RateLimit. Changes require restart. Shared bucket values must be positive; phase overrides can be 0 to inherit them.
| Setting group | Default | Used by | Meaning |
|---|---|---|---|
Disabled |
false |
Both phases | Disables DRN pre-auth and post-auth rate limiting. |
PartitionLogMode |
KeyedHash |
Both phases | Controls rejected IP and partition logging. See Telemetry for hash format and privacy limits. |
TokenLimit, ReplenishmentSeconds, TokensPerPeriod |
100, 60, 100 |
Shared fallback | Base token bucket values for both phases. |
PreAuthTokenLimit, PreAuthReplenishmentSeconds, PreAuthTokensPerPeriod |
1000, 60, 1000 |
Pre-auth | Coarse IP limits before authentication. 0 inherits the shared value. |
PostAuthTokenLimit, PostAuthReplenishmentSeconds, PostAuthTokensPerPeriod |
0, 0, 0 |
Post-auth | Authenticated user or anonymous IP limits after ScopedUserMiddleware. 0 inherits the shared value. |
Rule Extension Points
Add rules by deriving from SingletonRateLimitRule or ScopedRateLimitRule; the base classes include attribute-based DI registration. Direct interface implementations must opt into multi-registration with [Singleton<ISingletonRateLimitRule>(tryAdd: false)] or [Scoped<IScopedRateLimitRule>(tryAdd: false)].
Rules run by ascending Order; framework defaults run last. Matching rules compose through .NET's chained limiter, so tenant + user + IP policies can all apply to one request. ScopedRateLimitRule is post-auth only.
| Return value | Effect |
|---|---|
null |
Rule does not apply. |
RateLimitRuleResult.TokenBucket(key, ...) |
Applies a token bucket to this partition. |
RateLimitRuleResult.AllowRequest("partition-key") |
Skips later rules in this phase. Earlier limits and native policies still apply. |
RateLimitRuleResult.DenyRequest("partition-key") |
Rejects immediately with 429. |
Any result with stopRemainingRules: true |
Applies this result and skips later rules. |
Partition helpers include TokenBucket, FixedWindow, SlidingWindow, ConcurrencyLimiter, and CustomPartition. RateLimitRuleResult.Action is Limit, Allow, or Deny; StopRemainingRules only controls whether later rules compose after this result.
Set PolicyName to match [EnableRateLimiting("policy-name")], or leave it null for a global rule. Empty names are invalid. Native policies registered through AddRateLimiter run alongside DRN rules. A rejecting DRN rule receives OnRejectedAsync; native policy rejections use the ASP.NET Core callback.
Use ShortCircuitOnMatch and lower Order for allow/deny rules that must bypass quota checks. Rules with the same Order evaluate short-circuit rules first; if a short-circuit rule returns null, later rules still evaluate.
Partition option factories are cached by .NET per partition key. Do not capture HttpContext or scoped services inside factory lambdas; pass only immutable values.
Dynamic tenant plans belong in rules, not global settings. Rule evaluation is synchronous, so do not perform database, Redis, or HybridCache I/O inside EvaluatePreAuth / EvaluatePostAuth. Load plan data earlier in the request or maintain an in-memory snapshot refreshed in the background. HybridCache and IDistributedCache can share policy data, but they are not hard distributed counters by themselves.
// Sample.Hosted/Helpers/RateLimitFor.cs
public class RateLimitFor
{
public string? AccountPartition => Get.Claim.Account.Id == null ? null : $"account:{Get.Claim.Account.Id:N}";
public string? TenantPartition => Get.Claim.Tenant.Id == null ? null : $"tenant:{Get.Claim.Tenant.Id:N}";
}
public class AccountRateLimitRule(DrnAppFeatures features) : ScopedRateLimitRule
{
public override RateLimitRuleResult? EvaluatePostAuth(HttpContext context)
{
var partitionKey = Get.RateLimit.AccountPartition;
if (partitionKey == null)
return null;
var tokenLimit = features.RateLimit.TokenLimit;
var period = TimeSpan.FromSeconds(features.RateLimit.ReplenishmentSeconds);
var tokensPerPeriod = features.RateLimit.TokensPerPeriod;
return RateLimitRuleResult.TokenBucket(partitionKey, _ => new TokenBucketRateLimiterOptions
{
TokenLimit = tokenLimit,
ReplenishmentPeriod = period,
TokensPerPeriod = tokensPerPeriod,
QueueLimit = 0,
AutoReplenishment = true
});
}
}
Telemetry
DRN emits metrics through the DRN.Framework.Hosting.RateLimiting meter:
| Metric | DRN coverage |
|---|---|
drn.rate_limiting.requests |
Pre-auth lease acquisition attempts |
drn.rate_limiting.rejections |
Pre-auth and post-auth rejections |
drn.rate_limiting.active_request_leases |
Active pre-auth leases |
drn.rate_limiting.request_lease.duration |
Pre-auth lease duration in seconds |
All instruments use drn.rate_limiting.phase, aspnetcore.rate_limiting.policy, aspnetcore.rate_limiting.result, and drn.rate_limiting.action. The action is limit, allow, deny, or unknown. drn.rate_limiting.rule is added when a DRN rule is known; native policy rejections have no DRN rule tag. ASP.NET Core supplies post-auth request and lease metrics.
By default, rate-limit-specific IP and partition fields are written as deterministic keyed hashes with a blake3-keyed: prefix. This supports correlation but does not anonymize the complete request log; standard request and user fields may still contain raw identifiers. Treat logs as sensitive, and enable PlainText only for controlled development or a dedicated encrypted audit sink.
Overriding Defaults
Override CreatePreAuthRateLimiter or ConfigurePostAuthRateLimiterOptions in DrnProgramBase to change global algorithms, add named policies, or preserve custom RateLimiterOptions callbacks:
protected override void ConfigurePostAuthRateLimiterOptions(
RateLimiterOptions options,
IServiceProvider serviceProvider,
IAppSettings appSettings)
{
base.ConfigurePostAuthRateLimiterOptions(options, serviceProvider, appSettings);
options.AddTokenBucketLimiter("strict", opt =>
{
opt.TokenLimit = 10;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(60);
opt.TokensPerPeriod = 10;
opt.QueueLimit = 0;
});
}
References
- ASP.NET Core rate limiting middleware
- RateLimiterOptions API
- RateLimitPartition API
- HybridCache library in ASP.NET Core
- Distributed caching in ASP.NET Core
- Redis token bucket rate limiter with .NET
- RFC 6585 Section 4: 429 Too Many Requests
- RFC 9110: Retry-After header
Endpoint Management
DRN provides compile-time-typed accessor members for controller endpoints and Razor Page paths. Controller endpoint accessors are bound to mapped routes and validated at startup. Razor Page accessors are convention-generated path strings; keep them synchronized with page routes.
1. Define Accessors
Create application-owned endpoint and page collections:
public sealed class AppEndpoints : EndpointCollectionBase<Program>
{
public UserEndpoints User { get; } = new();
}
public sealed class UserEndpoints()
: ControllerForBase<UserController>("/Api/User/[controller]")
{
// Property names match controller action method names.
public ApiEndpoint Login { get; private set; } = null!;
public ApiEndpoint Profile { get; private set; } = null!;
}
public sealed class AppPages : PageCollectionBase<AppPages>
{
public UserPages User { get; } = new();
}
public sealed class UserPages : PageForBase
{
protected override string[] PathSegments { get; } = ["User"];
public string Login { get; init; } = string.Empty;
}
public static class Get
{
public static AppEndpoints Endpoint { get; } =
(AppEndpoints)EndpointCollectionBase<Program>.EndpointCollection!;
public static AppPages Page { get; } =
PageCollectionBase<AppPages>.PageCollection;
}
2. Usage in Code
Use the typed accessor members with IDE completion:
// Get the typed endpoint object
ApiEndpoint endpoint = Get.Endpoint.User.Login;
// Return the mapped controller route.
string url = endpoint.Path();
// For an action route containing {id:guid}
Guid userId = Guid.NewGuid();
string profileUrl = Get.Endpoint.User.Profile.Path(userId);
<a asp-page="@Get.Page.User.Login">Log in</a>
Razor TagHelpers
Activate the framework TagHelpers in Pages/_ViewImports.cshtml:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, DRN.Framework.Hosting
Without the DRN directive, Vite resolution, CSP nonces, HTMX CSRF headers, active-page marking, and visibility helpers do not run.
| TagHelper | Target | Purpose |
|---|---|---|
ViteScriptTagHelper |
<script src="buildwww/..."> |
Resolves Vite manifest entries and adds subresource integrity (SRI). |
ViteLinkTagHelper |
<link href="buildwww/..."> |
Resolves Vite manifest entries for CSS assets, adds SRI. |
NonceTagHelper |
<script>, <style>, <link>, <iframe> |
Automatically injects the request-specific CSP nonce. |
CsrfTokenTagHelper |
hx-post, hx-put, hx-delete, hx-patch, or add-csrf-token |
Adds RequestVerificationToken to hx-headers. On matched elements, disable-csrf-token opts out. |
AuthorizedOnlyTagHelper |
*[authorized-only] |
Renders the element only if the user is authenticated. |
AnonymousOnlyTagHelper |
*[anonymous-only] |
Renders the element only if the user is not authenticated. |
PolicyOnlyTagHelper |
*[policy-only="PolicyName"] |
Renders the element only when the current request user satisfies the named authorization policy. Accepts an optional policy-resource. |
PageAnchorAspPageTagHelper |
<a asp-page="..."> |
Adds active fw-bold and aria-current="page" when the link matches the current page. |
PageAnchorHrefTagHelper |
<a href="..."> |
Adds active fw-bold when the href path matches the Razor page identifier. Custom URL routes may differ. |
ScriptDefaultsTagHelper |
<script> |
Modern defaults: defer for external scripts, type="module" for inline scripts. Opt-out via defer="false" or explicit type. |
Authorization Visibility
Use named policies for claim, role, or resource requirements instead of duplicating authorization rules in markup:
<a policy-only="ManageUsers" asp-page="/Admin/Users">Manage users</a>
<button policy-only="EditDocument" policy-resource="@Model.Document">Edit</button>
<nav authorized-only>Signed-in navigation</nav>
<a anonymous-only asp-page="/User/Login">Sign in</a>
policy-only checks a registered policy against the current user. Invalid policy names raise errors; denied elements are hidden. Supply policy-resource when the policy requires one. The helper does not sign users in or check the linked endpoint; ordinary named policies retain the default MFA requirement.
authorized-only checks ScopeContext.Authenticated; anonymous-only checks its inverse. Both are presence-only markers, so even "false" activates the filter. Write them without values, remove them to omit filtering, and use Razor conditionals for dynamic rendering. Both markers are removed from rendered HTML. If several visibility helpers apply, any one can suppress the element.
These markers do not require completed MFA. On anonymous or MFA-exempt pages, authorized-only can render for users with pending MFA or incomplete setup. Use policy-only for policy-specific visibility. All three helpers control HTML rendering only; enforce endpoint access through server authorization policies.
Vite Manifest Publish Support
Vite manifests under wwwroot/**/.vite/manifest.json are included automatically in publish output.
ViteManifest uses IWebHostEnvironment.WebRootPath, falling back to Path.Combine(environment.ContentRootPath, "wwwroot") when it is unset or whitespace.
When publishing with a custom web root, build targets do not automatically capture manifests outside wwwroot/**; applications must separately include manifests and referenced assets in publish items.
During loading, ViteManifest rejects manifests outside the resolved root and assets outside their output directory or missing from disk with ConfigurationException. Normal startup loads manifests during pipeline configuration; temporary hosts and SkipValidation skip that eager check. Asset hashes populate Integrity as base64 SHA-256 (sha256-...) for ViteScriptTagHelper and ViteLinkTagHelper.
Keep manifests and referenced assets under the application's web root, and verify CSS and JavaScript loading after publish.
Disable the publish item injection when an application owns this behavior itself:
<PropertyGroup>
<DrnHostingViteManifestPublishItemsEnabled>false</DrnHostingViteManifestPublishItemsEnabled>
</PropertyGroup>
Developer Diagnostics
Request logging and diagnostic buffering apply wherever HttpScopeMiddleware runs. Detailed exception pages and startup reports are Development features.
Logging conventions
EventId is provided by .NET's Microsoft.Extensions.Logging. It holds a numeric ID and an optional name. Use it with a public static <Module>LogEvents catalog. For example, HostingLogEvents.MfaAuthorizationChallenge carries ID 7401 and name MfaAuthorizationChallenge.
- Define events as
public static readonly EventIdfields in the owning module'sLoggingnamespace. - Give each event a descriptive name and an ID unique within its module. Do not renumber, rename, or reuse published events for different meanings.
- Filter dedicated logs by logger category and event ID. Numeric IDs are not globally unique across applications or libraries.
- Use
ScopeEventandIScopedLog.WithEventinstead of event dictionaries. Standard fields areEventId,EventName,EventOutcome, andEventReason. - Consumers can define a companion catalog, such as
SampleLogEvents. Reuse Hosting definitions only for the same event meaning.
Use IScopedLog for request and operation diagnostics. Direct ILogger calls are appropriate for scope flushing, bootstrap failures, and dedicated audit events. Keep request audit decisions in the scoped log too. Dedicated audit events must not include the full request log.
Every ScopedLog has a stable CorrelationId. Its TraceId is captured from an active W3C activity, or left absent. HTTP TraceIdentifier remains separate. LogScoped emits the primary event ID with the aggregate. Dedicated audit records use EventOutcome, EventReason, nullable TraceId, and CorrelationId. See Utils OpenTelemetry correlation.
Startup Exception Reports
In Development, if the application fails during RunAsync, DRN Hosting attempts to write StartupExceptionReport.html beside the application assembly. Report generation is best effort; when no report can be created, use the startup logs. Production and staging use normal startup logs only.
When generated, the report can include:
- Full stack traces with source code highlighting (if symbols available).
- Environment details and configuration snapshots.
- Scoped logs leading up to the crash.
Custom Error Pages
The framework includes built-in Razor Pages for developer-time exception handling:
- RuntimeExceptionPage: Detailed breakdown of unhandled exceptions with request state and logs.
- CompilationExceptionPage: Visualizes Razor or code compilation errors with line-specific highlighting.
Request Body Buffering
Request diagnostics buffer POST, PUT, and PATCH bodies only when Content-Length is known and within the configured limit. Kestrel request-size limits still apply.
Configuration via DrnAppFeatures (in appsettings.json):
| Key | Type | Default | Effect |
|---|---|---|---|
DisableRequestBuffering |
bool |
false |
Disables this diagnostic buffering feature |
MaxRequestBufferingSize |
int |
0 (uses 30,000) |
Max bytes to buffer. Values below 10,000 use the 30,000-byte fallback |
{
"DrnAppFeatures": {
"DisableRequestBuffering": false,
"MaxRequestBufferingSize": 50000
}
}
Skipped reads return a reason, such as an unknown Content-Length or a length above the configured byte limit.
Modern HTTP Standards
HttpScopeMiddleware applies these defaults to responses passing through the request scope:
- 303 See Other: Middleware converts
302 Foundto303 See Other. For example, a redirect after form submission uses GET for the destination. Use307or308when the destination must preserve the request method. - Secure Caching Default: Dynamic responses that do not set their own
Cache-Controlreceiveno-store, no-cache, must-revalidate. Explicit response caching directives take precedence, while static assets opt into public caching.
Static Asset Pre-Warming
StaticAssetWarmService is a best-effort hosted service that requests Vite assets after the host starts so response caching can store Brotli and Gzip variants.
Warm-up uses local server addresses only; non-loopback bindings are skipped.
Compression defaults: Static assets allow HTTPS compression and caching of eligible variants. Dynamic HTTP responses can also be compressed. Dynamic HTTPS compression is disabled (EnableForHttps = false) to mitigate BREACH. MIME types extend ASP.NET Core defaults with raw TTF/OTF fonts; WOFF and WOFF2 are already compressed.
| Provider | Default Level | Override Hook |
|---|---|---|
| Brotli | SmallestSize |
ConfigureBrotliCompressionLevel() |
| Gzip | SmallestSize |
ConfigureGzipCompressionLevel() |
After a successful warm-up, subsequent requests can use cached compressed variants. Warm-up runs after startup and is best effort, so early requests may perform compression themselves.
Local Development Infrastructure
Use DRN.Framework.Testing to provision Postgres during local Development without manual Docker management. The following setup keeps Testcontainers dependencies in Debug builds and explicitly enables local dependency launch.
1. Add the Debug-Only Package
NuGet consumers should reference the DRN.Framework.Testing version matching DRN.Framework.Hosting:
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="DRN.Framework.Testing" Version="VERSION" />
</ItemGroup>
Repository contributors may use the sibling project reference instead.
2. Enable External Dependency Launch
Add the explicit opt-in to appsettings.Development.json:
{
"DrnDevelopmentSettings": {
"LaunchExternalDependencies": true
}
}
External dependencies launch only for a Development host with this setting enabled. Test and temporary hosts are excluded.
3. Configure Startup Actions
Implement DrnProgramActions to launch the configured local dependencies.
#if DEBUG
using DRN.Framework.Testing.Extensions;
public class SampleProgramActions : DrnProgramActions
{
public override async Task ApplicationBuilderCreatedAsync<TProgram>(
TProgram program, WebApplicationBuilder builder,
IAppSettings appSettings, IScopedLog scopedLog)
{
var options = new ExternalDependencyLaunchOptions
{
PostgresContainerSettings = new()
{
Reuse = true, // Faster restarts
HostPort = 6432 // Avoid conflicts with local Postgres
}
};
// When enabled, starts missing containers and updates AppSettings.
await builder.LaunchExternalDependenciesAsync(scopedLog, appSettings, options);
}
}
#endif
Hosting Utilities
IAppStartupStatus
Singleton gate for background services that need to wait until the host has fully started before executing.
using DRN.Framework.Hosting.Utils;
using DRN.Framework.Utils.DependencyInjection.Attributes;
using Microsoft.Extensions.Hosting;
[HostedService]
public sealed class MyWorker(IAppStartupStatus startupStatus) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!await startupStatus.WaitForStartAsync(stoppingToken))
return;
// The application has started.
}
}
[HostedService] is discovered when the worker's assembly is registered with AddServicesWithAttributes().
IServerSettings
Resolves bound server addresses from Kestrel. Normalizes wildcard hosts (0.0.0.0, [::], +, *) to localhost for internal self-requests. Prefers HTTP over HTTPS to avoid TLS overhead.
using DRN.Framework.Hosting.Utils;
public class MyService(IServerSettings server)
{
public void LogAddresses()
{
var loopback = server.GetLoopbackAddress(); // e.g. "http://localhost:5988"
var all = server.GetAllAddresses(); // All normalized bound addresses
}
}
Global Usings
Suggested global usings for Hosted applications to reduce boilerplate:
global using DRN.Framework.Hosting.DrnProgram;
global using DRN.Framework.Hosting.Endpoints;
global using DRN.Framework.Utils.DependencyInjection;
global using DRN.Framework.Utils.Logging;
global using DRN.Framework.Utils.Settings;
global using Microsoft.AspNetCore.Mvc;
Related Packages
- DRN.Framework.SharedKernel - Domain primitives and exceptions
- DRN.Framework.Utils - Configuration and DI utilities
- DRN.Framework.EntityFramework - EF Core integration
- DRN.Framework.Testing - Testing utilities
For complete examples, see Sample.Hosted.
Documented with the assistance of DiSC OS
Semper Progressivus: Always Progressive
Commit Info
Author: Duran Serkan KILIÇ
Date: 2026-09-09 11:24:34 +0300
Hash: 042fe6657917c7b6edc21dd2cc7879143639d9ca
| 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
- DRN.Framework.Utils (>= 0.10.0-preview002)
- NetEscapades.AspNetCore.SecurityHeaders (>= 1.3.1)
- NLog.Targets.Network (>= 6.1.5)
- NLog.Web.AspNetCore (>= 6.2.0)
- Swashbuckle.AspNetCore (>= 10.2.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on DRN.Framework.Hosting:
| Package | Downloads |
|---|---|
|
DRN.Framework.Testing
DRN.Framework.Testing package encapsulates testing dependencies and provides practical, effective helpers such as resourceful data attributes and test context. This package enables a new encouraging testing technique called as DTT(Duran's Testing Technique). With DTT, any developer can write clean and hassle-free unit and integration tests without complexity. ## Commit Info Author: Duran Serkan KILIÇ Date: 2026-09-09 11:24:34 +0300 Hash: 042fe6657917c7b6edc21dd2cc7879143639d9ca |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.10.0-preview002 | 39 | 9/9/2026 |
| 0.10.0-preview001 | 84 | 9/7/2026 |
| 0.9.9-preview004 | 96 | 8/27/2026 |
| 0.9.9-preview003 | 94 | 8/26/2026 |
| 0.9.9-preview002 | 93 | 8/20/2026 |
| 0.9.9-preview001 | 105 | 8/16/2026 |
| 0.9.8 | 144 | 8/12/2026 |
| 0.9.8-preview004 | 93 | 8/12/2026 |
| 0.9.8-preview003 | 92 | 8/9/2026 |
| 0.9.8-preview002 | 98 | 8/8/2026 |
| 0.9.8-preview001 | 107 | 8/8/2026 |
| 0.9.7 | 122 | 7/29/2026 |
| 0.9.6 | 128 | 7/15/2026 |
| 0.9.6-preview004 | 137 | 7/7/2026 |
| 0.9.6-preview003 | 126 | 7/1/2026 |
| 0.9.6-preview002 | 158 | 6/29/2026 |
| 0.9.6-preview001 | 122 | 6/28/2026 |
| 0.9.5 | 142 | 6/14/2026 |
| 0.9.5-preview011 | 119 | 6/14/2026 |
| 0.9.5-preview010 | 125 | 6/14/2026 |
## Version 0.10.0
### Bug Fixes
* **NuGet Release Notes**: Package metadata includes only the latest version section, excluding historical releases and the documentation footer. Packing rejects missing version sections and release notes over 35,000 characters; the bundled Markdown retains the full history.
### New Features
* **Hosting Log Events**: Added `HostingLogEvents` in `DRN.Framework.Hosting.Logging` for consumer filters and event references. Applications can define companion catalogs for their own events.
* **Policy-Based Razor Visibility**: Added `policy-only="PolicyName"` with optional `policy-resource` for policy-based HTML visibility. Endpoint authorization remains required.
* **Programmatic Application Builder Hook**: Added a four-argument `CreateApplicationAsync` overload for host integrations while retaining the existing three-argument overload for binary compatibility.
* **Unified Authentication Claims**: Use `ConfigureAuthenticationClaims()` for subject, name, email, role, and MFA mappings. Ordinary Identity applications use the defaults; external providers need their own authentication integration.
* **Identity Lifecycle Integration**: Added `ConfigureIdentityRenewal` and `DrnSignInManager<TUser>` for Identity sign-in and refresh integration. Register the manager explicitly, including when customizing sign-in behavior.
### Breaking Changes
* **Swagger UI Prefix Validation**: When enabled, Swagger UI requires a `RoutePrefix` that is nonempty after trimming slashes. Root mounts now fail startup; migrate to a non-root prefix such as `swagger`, `docs/swagger`, or `api-docs`. The configured subtree is reserved for Swagger CSP.
* **Authenticated UI Visibility Convention**: `authorized-only` now checks `ScopeContext.Authenticated` instead of completed MFA. Authenticated users with pending or incomplete MFA can see these elements on anonymous or MFA-exempt pages. Endpoint policies remain responsible for MFA enforcement; use `policy-only` for policy-specific visibility.
* **Presence-Only Visibility Markers**: Removed the `AuthorizedOnly` and `AnonymousOnly` boolean properties from their TagHelpers. Use bare `authorized-only` / `anonymous-only` attributes; presence activates filtering regardless of the value, including `"false"`. Omit the attribute to omit its filter. Both markers are removed from rendered HTML.
* **Policy-Scoped MFA Exemptions**: Exempt schemes must also be selected for authentication. Programmatic authorization without an HTTP policy context no longer uses scheme exemptions.
* **Identity MFA Policy Registration**: Call `AddDrnIdentityMfaPolicies()` after registering Identity when using `IdentityManagementControllerBase`. Factor management requires completed MFA after enrollment and returns HTTP 403 when denied.
* **Claim Configuration Consolidation**: Removed `ConfigureMFAClaim` and separate MFA DI configuration. Move the marker to `Mfa = new(type, value)` in `ConfigureAuthenticationClaims`. MFA handlers and management helpers now receive `AuthenticationClaimConfig`; recompile and migrate affected calls. `ConfigureSecurityStampValidatorOptions` receives `(SecurityStampValidatorOptions, IAppSettings, AuthenticationClaimConfig)`; forward those arguments to base. Identity claim-option overrides are superseded by the unified config. Canonical subject changes may require reauthentication; aliases do not automatically migrate existing Identity tickets.
* **Identity MFA Setup Response**: When MFA is globally enforced, password-valid accounts without two-factor authentication now receive an HTTP 200 five-minute setup credential instead of the ordinary authenticated credential returned previously. Cookie requests receive an empty response with a non-persistent, non-refreshable setup cookie; bearer requests receive an `AccessTokenResponse` with `ExpiresIn = 300` and an empty `RefreshToken`. Clients must use the setup credential with `TwoFactorAuth`, enable two-factor authentication, discard the setup credential, and log in again with an authenticator or recovery code.
### Security
* **Swagger Prefix Normalization**: Leading and trailing slashes are removed from the UI prefix for both Swagger routing and CSP selection, preserving redirects from `/docs` and `/docs/` to the document when configured with `/docs/`.
* **Swagger CSP**: Added the independently replaceable `CspFor.CspPolicySwagger` policy with same-origin scripts and inline styles for Swagger SVG rendering, preserving document caching. Selection matches the validated UI prefix with path segment boundaries. Disabled Swagger retains the default CSP. Existing self/inline policies outside the Swagger subtree retain nonce-based styles.
* **Revocation Guidance**: Documented cookie, refresh-token, and access-token revocation limits. Existing token lifetimes and default MFA requirements are unchanged.
* **Authorization Audit Events**: Added challenge, forbid, and exemption events (7401–7403) for log filtering and alerts. Factor, recovery, and revocation events are not included.
* **Reverse Proxy Trust & Forwarded Headers**: Binds `ForwardedHeaders` settings with CIDR and proxy parsing. Invalid formats throw `ConfigurationException`. Defaults trust RFC 1918 private networks and loopback with `ForwardLimit = 2`. `TrustPrivateNetworks = false` removes the private-network defaults. A nonempty `KnownIPNetworks` list replaces network defaults; `KnownProxies` adds entries without clearing existing trust.
---
Documented with the assistance of [DiSC OS](https://github.com/duranserkan/DRN-Project/blob/develop/.agent/rules/DiSCOS.md)
---
**Semper Progressivus: Always Progressive**