DRN.Framework.Hosting
0.9.5
Prefix Reserved
dotnet add package DRN.Framework.Hosting --version 0.9.5
NuGet\Install-Package DRN.Framework.Hosting -Version 0.9.5
<PackageReference Include="DRN.Framework.Hosting" Version="0.9.5" />
<PackageVersion Include="DRN.Framework.Hosting" Version="0.9.5" />
<PackageReference Include="DRN.Framework.Hosting" />
paket add DRN.Framework.Hosting --version 0.9.5
#r "nuget: DRN.Framework.Hosting, 0.9.5"
#:package DRN.Framework.Hosting@0.9.5
#addin nuget:?package=DRN.Framework.Hosting&version=0.9.5
#tool nuget:?package=DRN.Framework.Hosting&version=0.9.5
DRN.Framework.Hosting
Application shell for DRN web applications with security-first design, structured lifecycle, and type-safe routing.
TL;DR
- Secure by Default - MFA enforced (Fail-Closed), strict CSP with nonces, HSTS outside Development
- Opinionated Startup -
DrnProgramBasewith 20+ overrideable lifecycle hooks - Type-Safe Routing - Typed
EndpointandPageaccessors replace magic strings - Local Infrastructure - Optional Debug-time Postgres provisioning via
DRN.Framework.Testing - Frontend Integration - TagHelpers for Vite manifest, CSRF for HTMX, secure assets
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
- GDPR & Consent Integration
- Local Development
- Global Usings
- Related Packages
QuickStart: Beginner
DRN web applications inherit from DrnProgramBase<TProgram> to implement standard lifecycle hooks and behaviors.
using DRN.Framework.Hosting.DrnProgram;
using DRN.Framework.Hosting.HealthCheck;
namespace Sample.Hosted;
public class Program : DrnProgramBase<Program>, IDrnProgram
{
// Entry Point (Runs the opinionated bootstrapping)
public static async Task Main(string[] args) => await RunAsync(args);
// [Required] Service Registration Hook
protected override Task AddServicesAsync(WebApplicationBuilder builder, IAppSettings appSettings, IScopedLog scopedLog)
{
builder.Services.AddSampleInfraServices(appSettings);
builder.Services.AddSampleApplicationServices();
return Task.CompletedTask;
}
}
// Immediate API endpoint for testing and health checks (Inherits [AllowAnonymous] and Get())
[Route("[controller]")]
public class WeatherForecastController : WeatherForecastControllerBase;
QuickStart: Advanced
Test your application using DRN.Framework.Testing to spin up the full pipeline including databases.
[Theory, DataInline]
public async Task WeatherForecast_Should_Return_Data(DrnTestContext context, ITestOutputHelper outputHelper)
{
// Arrange
var client = await context.ApplicationContext.CreateClientAsync<Program>(outputHelper);
// Act
var response = await client.GetAsync("WeatherForecast");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var data = await response.Content.ReadFromJsonAsync<IEnumerable<WeatherForecast>>();
data.Should().NotBeEmpty();
}
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/ # HttpScopeLogger, exception handling, security middlewares
├── Nexus/ # NexusClient for inter-service HTTP communication
├── 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)
├── wwwroot/ # Framework style and script assets
Lifecycle & Execution Flow
DrnProgramBase manages application startup sequence to ensure security headers, logging scopes, and validation logic execute in order. Use DrnProgramActions to intercept these phases without modifying the primary Program class.
flowchart TD
subgraph CONTAINER [" "]
direction TB
Start(["RunAsync()"]) --> CAB["CreateApplicationBuilder()"]
subgraph BUILDER ["1. Builder Phase"]
direction TB
B_NOTE["Note: Handles Services & Config"]
CAB --> CLB["ConfigureLoggingBuilder()"]
CAB --> CWHB["ConfigureWebHostBuilder()"]
CAB --> CSO["ConfigureSwaggerOptions()"]
CAB --> CDSH["ConfigureDefaultSecurityHeaders()"]
CDSH --> CDCSP["ConfigureDefaultCsp()"]
CAB --> CSHPB["ConfigureSecurityHeaderPolicyBuilder()"]
CAB --> CCP["ConfigureCookiePolicy()"]
CAB --> CSFO["ConfigureStaticFileOptions()"]
CAB --> CRCO["ConfigureResponseCachingOptions()"]
CAB --> CRCMO["ConfigureResponseCompressionOptions()"]
CAB --> CCP2["ConfigureCompressionProviders()"]
CAB --> CFHO["ConfigureForwardedHeadersOptions()"]
CAB --> CMVCB["ConfigureMvcBuilder()"]
CAB --> CAO["ConfigureAuthorizationOptions()"]
CAB --> ASA["AddServicesAsync()"]
ASA --> ABC["ApplicationBuilderCreatedAsync (Action)"]
end
ABC --> Build["builder.Build()"]
subgraph APPLICATION ["2. Application Phase"]
direction TB
A_NOTE["Note: Handles Middleware Pipeline"]
Build --> CA["ConfigureApplication()"]
CA --> CAPS["ConfigureApplicationPipelineStart() (HSTS/Headers)"]
CAPS --> CAPR["ConfigureApplicationPreScopeStart() (Caching/Compression/Static)"]
CAPR --> HSM["HttpScopeMiddleware (TraceId/Logging)"]
HSM --> CPSS["ConfigureApplicationPostScopeStart()"]
CPSS --> UR["UseRouting()"]
UR --> PRL["PreAuthRateLimitingMiddleware"]
PRL --> CAPREA["ConfigureApplicationPreAuthentication()"]
CAPREA --> AUTH["UseAuthentication()"]
AUTH --> SUM["ScopedUserMiddleware"]
SUM --> PARL["UseRateLimiter() (PostAuth)"]
PARL --> CAPOSTA["ConfigureApplicationPostAuthentication()"]
CAPOSTA --> MFAE["MfaExemptionMiddleware"]
CAPOSTA --> MFAR["MfaRedirectionMiddleware"]
MFAE --> UA["UseAuthorization()"]
MFAR --> UA
UA --> CPSTAZ["ConfigureApplicationPostAuthorization() (Swagger UI)"]
CPSTAZ --> MAE["MapApplicationEndpoints()"]
end
MAE --> ABA["ApplicationBuiltAsync (Action)"]
ABA --> VE["ValidateEndpoints()"]
VE --> VSA["ValidateServicesAsync()"]
VSA --> AVA["ApplicationValidatedAsync (Action)"]
AVA --> Run(["application.RunAsync()"])
end
%% WCAG AA Compliant Styling
%% Outer Container
style CONTAINER fill:#F0F8FF,stroke:#B0C4DE,stroke-width:2px,color:#4682B4
%% Subgraph Backgrounds (Direct styling)
style BUILDER fill:#E1F5FE,stroke:#0288D1,stroke-width:2px,color:#01579B
style APPLICATION fill:#E8EAF6,stroke:#3F51B5,stroke-width:2px,color:#1A237E
%% Node Styles (White for contrast against subgraph)
classDef builderNode fill:#FFFFFF,stroke:#0288D1,stroke-width:2px,color:#01579B
classDef appNode fill:#FFFFFF,stroke:#3F51B5,stroke-width:2px,color:#1A237E
classDef action fill:#FFE0B2,stroke:#F57C00,stroke-width:2px,color:#E65100
classDef core fill:#E8F5E9,stroke:#43A047,stroke-width:2px,color:#1B5E20
classDef note fill:#FFF9C4,stroke:#F57C00,stroke-width:1px,color:#E65100,stroke-dasharray: 5 5
classDef decision fill:#FFE0B2,stroke:#E65100,stroke-width:3px,color:#E65100
%% Apply Styles
class CAB,CLB,CWHB,CSO,CDSH,CDCSP,CSHPB,CCP,CSFO,CRCO,CRCMO,CCP2,CFHO,CMVCB,CAO,ASA builderNode
class CA,CAPS,CAPR,HSM,CPSS,UR,PRL,CAPREA,AUTH,SUM,PARL,CAPOSTA,MFAE,MFAR,UA,CPSTAZ,MAE appNode
class ABC,ABA,AVA action
class Start,Build,VE,VSA,Run core
class B_NOTE,A_NOTE note
%% Link Styles for Decision Paths (Grey Arrows)
linkStyle default stroke:#666,stroke-width:2px
DrnProgramBase Deep Dive
This section details the hooks for customizing the application lifecycle. DrnProgramBase implements a Hook Method pattern where the base defines the workflow and specific logic is injected via overrides.
1. Configuration Hooks (Builder Phase)
These hooks run while the WebApplicationBuilder is active, allowing you to configure the DI container and system options.
| Category | Method | Purpose |
|---|---|---|
| Logging | ConfigureLoggingBuilder |
Configure logging providers (clears defaults, applies config section, registers NLog). |
| 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. Razor edit loops use Hot Reload, not runtime compilation. |
| MVC | ConfigureMvcOptions |
Add global filters, conventions, or customize model binding. |
| Auth | ConfigureAuthorizationOptions |
Define security policies. Note: Sets MFA as the default/fallback by default. |
| Security | ConfigureDefaultSecurityHeaders |
Define global headers (HSTS, CSP, FrameOptions). |
| Security | ConfigureDefaultCsp |
Customize CSP directives (Script, Image, Style sources). |
| 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 | ConfigureSecurityStampValidatorOptions |
Customize security stamp validation and claim preservation. |
| Infras. | ConfigureStaticFileOptions |
Customize caching (default: 1 year) 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 |
Configure server-side response caching with sensible defaults (16MB max body size, case-insensitive paths). |
| Infras. | ConfigureResponseCompressionOptions |
Configure response compression (Brotli/Gzip) for static assets. HTTPS compression disabled by default for BREACH prevention. |
| Infras. | ConfigureCompressionProviders |
Configure Brotli and Gzip compression provider options including compression levels. |
| Infras. | ConfigureBrotliCompressionLevel |
Customize Brotli compression level (default: SmallestSize for static assets). |
| Infras. | ConfigureGzipCompressionLevel |
Customize Gzip compression level (default: SmallestSize for static assets). |
| Global | AddServicesAsync |
[Required] The primary place to register your application services. |
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)
These hooks define the request processing middleware sequence.
| Order | Hook | Typical Usage |
|---|---|---|
| 1 | ConfigureApplicationPipelineStart |
UseForwardedHeaders, UseHostFiltering, UseCookiePolicy. |
| 2 | ConfigureApplicationPreScopeStart |
UseResponseCaching, UseResponseCompression, UseStaticFiles. Caching placed before compression for efficiency. |
| 3 | ConfigureApplicationPostScopeStart |
Add middleware that needs access to IScopedLog but runs before routing. |
| 4 | ConfigureApplicationPreAuthentication |
UseRequestLocalization. The built-in pre-auth rate limiter runs after routing and before this hook when enabled. |
| 5 | ConfigureApplicationPostAuthentication |
MfaRedirectionMiddleware, MfaExemptionMiddleware. The built-in post-auth UseRateLimiter() runs after ScopedUserMiddleware and before this hook when enabled. |
| 6 | ConfigureApplicationPostAuthorization |
UseSwaggerUI. Runs after access is granted but before the final endpoint. |
| 7 | MapApplicationEndpoints |
MapControllers, MapRazorPages, MapHubs. |
3. Verification Hooks
| Hook | Purpose |
|---|---|
ValidateEndpoints |
Ensures all type-safe endpoint accessors match actual mapped routes. |
ValidateServicesAsync |
Scans the container for [Attribute] based registrations and ensures they are resolvable at startup via ValidateServicesAddedByAttributesAsync. |
4. MFA Configuration Hooks
| Hook | Purpose |
|---|---|
ConfigureMFARedirection |
Configure MFA setup and login redirection URLs. Returns null to disable. |
ConfigureMFAExemption |
Configure authentication schemes exempt from MFA requirements. Returns null to disable. |
5. Internal Wiring (Automatic)
- Service Validation: Calls
ValidateServicesAsyncto scan[Attribute]-registered services and ensure they are resolvable at startup. - Secure JSON: Enforces
HtmlSafeWebJsonDefaultsto prevent XSS via JSON serialization. - Endpoint Accessor: Registers
IEndpointAccessorfor typed access toEndpointCollectionBase.
6. Properties
| Property | Default | Purpose |
|---|---|---|
AppBuilderType |
DrnDefaults |
Controls builder creation. Use Slim for minimal APIs. |
DrnProgramSwaggerOptions |
(Object) | Toggles Swagger generation. Defaults to IsDevelopmentEnvironment. |
NLogOptions |
(Object) | Controls NLog bootstrapping (e.g., replace logger factory). |
Configuration
Configuration Precedence: command line and mounted settings override environment variables, which override User Secrets and appsettings files.
Always use User Secrets for local connection strings to avoid committing credentials.
Layering
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
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)
Standard configuration for Console and Graylog output.
{
"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" }
}
}
}
Security Features
DRN Hosting enforces a "Fail-Closed" security model. If you forget to configure something, it remains locked.
1. MFA Enforcement (Fail-Closed)
The framework sets the FallbackPolicy for the entire application to require a Multi-Factor Authentication session.
- Result: Any new controller or page you add is secure by default.
- Opt-Out: Use
[AllowAnonymous]or[Authorize(Policy = AuthPolicy.MfaExempt)]for single-factor pages like Login or MFA Setup.
2. MFA Configuration
Configure MFA behavior by overriding these hooks in your DrnProgramBase implementation:
// Configure MFA redirection URLs
protected override MfaRedirectionConfig ConfigureMFARedirection()
=> new(
mfaSetupUrl: Get.Page.User.EnableAuthenticator,
mfaLoginUrl: Get.Page.User.LoginWith2Fa,
loginUrl: Get.Page.User.Login,
logoutUrl: Get.Page.User.Logout,
appPages: Get.Page.All
);
// Exempt specific authentication schemes from MFA
protected override MfaExemptionConfig ConfigureMFAExemption()
=> new() { ExemptAuthSchemes = ["ApiKey", "Certificate"] };
Disabling MFA Entirely
To disable MFA enforcement for your entire application (e.g., for internal tools or development):
public class Program : DrnProgramBase<Program>, IDrnProgram
{
// Return null to disable MFA redirection middleware
protected override MfaRedirectionConfig? ConfigureMFARedirection() => null;
// Return null to disable MFA exemption middleware
protected override MfaExemptionConfig? ConfigureMFAExemption() => null;
// Override authorization to remove MFA requirement from fallback policy
protected override void ConfigureAuthorizationOptions(AuthorizationOptions options)
{
// Remove MFA enforcement - authenticated users can access without MFA
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
}
}
Disabling MFA removes a critical security layer. Only do this for internal applications on secured networks.
3. Content Security Policy (Nonce-based)
DRN automatically generates a unique cryptographic nonce for every request.
- Baseline:
default-src 'none'with explicit same-origin allowlists for styles, images, fonts, connections, media, manifests, and workers. - Automatic Protection: Inline scripts and inline style elements without a matching nonce are blocked by the browser, stopping most XSS attacks.
- Usage: Use the
NonceTagHelper(see below) to automatically inject these nonces.
4. Transparent Security Headers
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.
5. GDPR & Cookie Security
The global cookie policy uses SameSite=Strict; Secure is Always outside Development and SameAsRequest in Development. Global HttpOnly is not forced because some consent/client-side cookies must remain script-readable under strict CSP. Antiforgery and TempData cookies are explicitly HttpOnly. The ConsentCookie system supports privacy preference handling.
6. Per-Route Security Headers
Customize security headers for specific routes by overriding ConfigureSecurityHeaderPolicyBuilder:
protected override void ConfigureSecurityHeaderPolicyBuilder(
SecurityHeaderPolicyBuilder builder,
IServiceProvider serviceProvider,
IAppSettings appSettings)
{
base.ConfigureSecurityHeaderPolicyBuilder(builder, serviceProvider, appSettings);
// Add route-specific CSP for embedding external content
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);
}
7. 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 for pre-auth IP partitions and 100 tokens/minute for post-auth authenticated users or anonymous IP fallback. Rejections return 429 Too Many Requests with Retry-After.
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.
Why DRN Rate Limiting
ASP.NET Core UseRateLimiter() supports a single global limiter plus endpoint policies. DRN keeps those native policies and adds framework-managed composition for common application needs:
| Capability | ASP.NET Core native | DRN |
|---|---|---|
| Pre-auth abuse rejection | Single post-routing limiter path | Pre-auth limiter before auth/MFA work |
| User, tenant, account, and IP limits together | Manual chained limiter wiring | Independent rules compose automatically |
| Rule addition | Update central GlobalLimiter configuration |
Add a rule class with DI attributes/base class |
| Scoped/user-aware partitioning | Manual HttpContext.User parsing |
Post-auth scoped rules can use IScopedUser and app helpers |
| Endpoint named policies | Native support | Preserved and enriched with DRN matching rules |
| Rejection diagnostics | Native policy result | Rule, phase, action, and redacted partition tags/logs |
Usage guidance:
- Use the default post-auth rule for ordinary per-user throttling.
- Raise pre-auth limits or add a singleton trusted-header rule when many legitimate users share one edge IP.
- Add scoped post-auth rules for tenant, account, or user-claim partitions that need
IScopedUseror other scoped collaborators. - Use
[DisableRateLimiting]only for trusted health and operational endpoints that must not consume quota. - Keep tenant plan, feature-flag, account, or endpoint-specific quota decisions in app-owned rules, not in global defaults.
- Pair process-local DRN limits with edge or distributed rate limiting when a quota must hold across replicas.
Settings Quick Reference
Configure defaults under DrnAppFeatures:DrnRateLimit. Application code reads the same values through IAppSettings.Features.RateLimit. Settings are a startup snapshot, so changes require restart.
| Setting group | Default | Used by | Meaning |
|---|---|---|---|
Disabled |
false |
Both phases | Disables DRN pre-auth and post-auth rate limiting. |
PartitionLogMode |
KeyedHash |
Both phases | Logs deterministic keyed hashes for rejected partitions. Use PlainText only in controlled development or dedicated audit sinks. |
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("reason") |
Allows and skips remaining rules. |
RateLimitRuleResult.DenyRequest("reason") |
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.
Use PolicyName to target endpoints marked with [EnableRateLimiting("policy-name")]. Native policies configured through builder.Services.AddRateLimiter(options => ...) remain available and run alongside DRN rule policies. DRN invokes rule-specific OnRejectedAsync only when that DRN rule's limiter rejects; native named-policy rejections still flow through the configured 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 identities are internally namespaced by phase and rule type:
({phase}, {rule type}, {your partition key})
The namespacing keeps metrics/logs diagnosable and prevents accidental key collisions between rules. Your rule still returns a simple key like tenant:acme-corp; DRN handles the namespace.
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;
return RateLimitRuleResult.TokenBucket(partitionKey, _ => new TokenBucketRateLimiterOptions
{
TokenLimit = features.RateLimit.TokenLimit,
ReplenishmentPeriod = TimeSpan.FromSeconds(features.RateLimit.ReplenishmentSeconds),
TokensPerPeriod = features.RateLimit.TokensPerPeriod,
QueueLimit = 0,
AutoReplenishment = true
});
}
}
Rule Behavior Quick Reference
| Return Value | Effect |
|---|---|
null |
Rule does not apply — skip to next rule |
RateLimitRuleResult.TokenBucket(key, ...) |
Apply token bucket limiter on this partition key |
RateLimitRuleResult.AllowRequest("health") |
Whitelist — no limiting, stop remaining rules |
RateLimitRuleResult.DenyRequest("blocked") |
Reject immediately with 429, stop remaining rules, optionally emit Retry-After |
Any result with stopRemainingRules: true |
Apply this limiter, then skip remaining rules |
Partition helpers: TokenBucket, FixedWindow, SlidingWindow, ConcurrencyLimiter, CustomPartition.
RateLimitRuleResult.Action is Limit, Allow, or Deny; StopRemainingRules only controls whether later rules compose after this result.
Rule Ordering and Composition
- Rules execute in ascending
Order. Framework defaults useint.MaxValue. Your rules run first. - Multiple matching rules compose via .NET's native chained limiter (e.g., tenant + user + IP all enforce together).
ShortCircuitOnMatch = true: the rule runs before normal same-order rules. If it returnsnull, later rules still evaluate. If it returns a result, that result decides the action and remaining rules are skipped.AllowRequestsucceeds without a limiter.DenyRequestfails immediately. Quota results such asTokenBucketstill acquire their limiter, then skip remaining rules whenShortCircuitOnMatchorstopRemainingRulesapplies.- Use a lower
Orderwhen an allow/deny rule must bypass earlier quotas entirely.
Partition Key Isolation
DRN rate limit rules namespace every partition identity with the phase and the rule type:
({phase}, {rule type}, {your partition key})
This namespacing provides:
- Diagnostics: Partition keys in metrics and logs identify the rule and phase.
- Defense in depth: Protects against future refactoring that might consolidate limiter instances.
Example: A request from IP 192.168.1.1 by tenant acme-corp hits three rules:
| Rule | Your partition key | DRN internal identity |
|---|---|---|
DefaultPreAuthRateLimitRule |
ip:192.168.1.1 |
(PreAuth, DefaultPreAuthRateLimitRule, ip:192.168.1.1) |
CustomIpRule |
ip:192.168.1.1 |
(PostAuth, CustomIpRule, ip:192.168.1.1) |
TenantRateLimitRule |
tenant:acme-corp |
(PostAuth, TenantRateLimitRule, tenant:acme-corp) |
The namespacing is internal. Your EvaluatePreAuth or EvaluatePostAuth method returns a partition key like "ip:192.168.1.1". The framework handles the namespacing.
Scoped Rules
- Scoped rules are post-auth only. They are not evaluated by the pre-auth limiter.
- DRN detects scoped rule registrations at startup, resolves them from the request scope, and preserves global
Orderacross singleton and scoped rules.
Claim-Based Partitions
- Use app-specific
RateLimitForwrappers (e.g.,Sample.Hosted.Helpers.RateLimitFor) with claim-access primitives fromGet.Claim.*. - This reads claims from the cached scoped user model instead of repeatedly parsing
HttpContext.User.
Factory capture safety: Partition option factories are cached by .NET per partition key. Do not capture HttpContext or scoped services inside the factory lambda — use only value-based parameters.
Named Policies
- Set
PolicyNameon a rule to scope it to endpoints marked with[EnableRateLimiting("policy-name")]. nullpolicy = global rule. Non-null policy names must be non-empty.- Native policies configured via
builder.Services.AddRateLimiter(options => ...)remain available and run alongside DRN rule policies. - DRN invokes rule-specific
OnRejectedAsynconly when that DRN rule's limiter rejects; native named-policy rejections still flow through the configured ASP.NET CoreOnRejectedcallback.
Telemetry
DRN emits OpenTelemetry-friendly metrics through the DRN.Framework.Hosting.RateLimiting meter:
| Metric | Tags |
|---|---|
drn.rate_limiting.requests |
drn.rate_limiting.phase, aspnetcore.rate_limiting.policy, aspnetcore.rate_limiting.result, drn.rate_limiting.action, drn.rate_limiting.rule |
drn.rate_limiting.rejections |
drn.rate_limiting.phase, aspnetcore.rate_limiting.policy, aspnetcore.rate_limiting.result, drn.rate_limiting.action, drn.rate_limiting.rule |
drn.rate_limiting.active_request_leases |
drn.rate_limiting.phase, aspnetcore.rate_limiting.policy, aspnetcore.rate_limiting.result, drn.rate_limiting.action, drn.rate_limiting.rule |
drn.rate_limiting.request_lease.duration |
drn.rate_limiting.phase, aspnetcore.rate_limiting.policy, aspnetcore.rate_limiting.result, drn.rate_limiting.action, drn.rate_limiting.rule |
ASP.NET Core's native rate limiting middleware continues to provide its built-in post-auth metrics.
The action tag is limit, allow, deny, or unknown; this makes whitelist, blocklist, and quota decisions visible without inspecting rule names.
When a native ASP.NET Core named policy rejects after DRN's global limiter succeeds, DRN records the rejection without a DRN rule tag because no DRN rule caused the failed lease.
By default, pre-auth and post-auth rejection logs write IP and partition values as deterministic keyed hashes with a blake3-keyed: prefix. This preserves correlation for audits without exposing raw API-key, tenant-hint, service-identifier, user, or IP values. Set DrnAppFeatures:DrnRateLimit:PartitionLogMode to 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;
});
}
Static files served by UseStaticFiles() run before routing and are automatically exempt from rate limiting.
Use [DisableRateLimiting] for trusted health checks or operational endpoints that must not consume pre-auth or post-auth quota. Use [EnableRateLimiting] for ASP.NET Core endpoint-specific post-auth policies; DRN pre-auth remains the global early-abuse limiter.
Configure defaults under DrnAppFeatures:DrnRateLimit. Shared TokenLimit, ReplenishmentSeconds, and TokensPerPeriod must be positive values. Phase-specific overrides can be 0 to inherit the shared value.
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
Avoid "magic strings" in your code. DRN provides a type-safe way to reference routes that is verified at startup.
1. Define Your Accessors
Create a class inheriting from EndpointCollectionBase<Program> or PageCollectionBase<Program>.
public class Get : EndpointCollectionBase<Program>
{
public static UserEndpoints User { get; } = new();
}
public class UserEndpoints : ControllerForBase<UserController>
{
// Template: /Api/User/[controller]/[action]
public UserEndpoints() : base("/Api/User/[controller]") { }
// Properties matching Controller Action names
public ApiEndpoint Login { get; private set; } = null!;
public ApiEndpoint Profile { get; private set; } = null!;
}
2. Usage in Code
Resolve routes at compile-time with full IDE support (intellisense).
// Get the typed endpoint object
ApiEndpoint endpoint = Get.User.Login;
// Generate the path string
string url = endpoint.Path(); // "/Api/User/User/Login"
// Generate path with route parameters
string profileUrl = Get.User.ProfileDetail.Path(new() { ["id"] = userId.ToString() });
Razor TagHelpers
| 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, etc. |
Automatically adds RequestVerificationToken to HTMX headers for non-GET requests. |
AuthorizedOnlyTagHelper |
*[authorized-only] |
Renders the element only if the user has an active MFA session. |
AnonymousOnlyTagHelper |
*[anonymous-only] |
Renders the element only if the user is not authenticated. |
PageAnchorAspPageTagHelper |
<a asp-page="..."> |
Automatically adds active CSS class if the link matches current page. |
PageAnchorHrefTagHelper |
<a href="..."> |
Automatically adds active CSS class if the link matches current path. |
ScriptDefaultsTagHelper |
<script> |
Modern defaults: defer for external scripts, type="module" for inline scripts. Opt-out via defer="false" or explicit type. |
Vite Manifest Publish Support
DRN.Framework.Hosting ships a transitive MSBuild target that adds wwwroot/**/.vite/manifest.json files to Web SDK publish output. At runtime, ViteManifest scans for .vite/manifest.json below IWebHostEnvironment.WebRootPath; when WebRootPath is empty, it resolves ContentRootPath/wwwroot. This keeps manifest lookup, SRI generation, and static asset pre-warming working after publish, including Vite's default dot-directory manifest location.
When changing environment defaults, Staging-from-build-output behavior, or static-web-asset content roots, verify manifest discovery against the running app, not only server startup. A Razor page can render while CSS/JS is absent if the Vite manifests are outside the active manifest root.
Disable the publish item injection when an application owns this behavior itself:
<PropertyGroup>
<DrnHostingViteManifestPublishItemsEnabled>false</DrnHostingViteManifestPublishItemsEnabled>
</PropertyGroup>
Developer Diagnostics
DRN Hosting provides deep observability into application failures, especially during the critical startup phase.
Startup Exception Reports
In Development, if the application fails to start during RunAsync, it generates a StartupExceptionReport.html beside the application assembly. Production and staging fail with normal logs only. Development reports 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
RequestBufferingState provides size-gated request body capture for diagnostic error pages. It follows a producer/consumer pattern:
- Producer —
TryEnableBufferingruns inHttpScopeMiddlewareearly in the pipeline. For POST, PUT, and PATCH requests with a knownContent-Lengthwithin the configured limit, it enablesRequest.EnableBuffering()so the body stream becomes seekable. - Consumer —
ReadBodyAsyncis called by the error page model builder (ExceptionUtils.CreateErrorPageModelAsync) to include the request body in diagnostic reports.
Security design:
- Size gate — requests exceeding the buffer limit are silently skipped (no buffering, no memory risk)
- Method filter — only POST/PUT/PATCH are buffered; GET/HEAD/DELETE/OPTIONS carry no semantic body
- Chunked transfer — requests without
Content-Length(chunked encoding) are skipped to prevent unbounded DoS - Kestrel enforcement — Content-Length is validated per-protocol (HTTP/1.1 slicing, HTTP/2 PROTOCOL_ERROR, HTTP/3 QUIC framing)
Configuration via DrnAppFeatures (in appsettings.json):
| Key | Type | Default | Effect |
|---|---|---|---|
DisableRequestBuffering |
bool |
false |
Kill switch — disables all body buffering |
MaxRequestBufferingSize |
int |
0 (→ 30,000) |
Max bytes to buffer. Values below 10,000 are ignored |
{
"DrnAppFeatures": {
"DisableRequestBuffering": false,
"MaxRequestBufferingSize": 50000
}
}
When buffering is skipped, ReadBodyAsync returns a descriptive reason string (e.g., "Content-Length exceeded limit") instead of the body, so error pages always display useful context.
Modern HTTP Standards
DRN Hosting enforces modern web standards to improve security and predictability:
- 303 See Other: The middleware automatically converts
302 Foundredirects to303 See Other. This ensures that following a POST request, the browser correctly usesGETfor the redirected URL, adhering to established web patterns. - Strict Caching: By default,
Cache-Control: no-store, no-cache, must-revalidateis applied to all sensitive responses to prevent data leaking into shared or browser caches.
GDPR & Consent Integration
The framework provides a structured way to handle user privacy choices:
- ConsentCookie: A strongly-typed model to track analytics and marketing preferences.
- Middleware Integration:
ScopedUserMiddlewareautomatically extracts consent data and makes it available viaScopeContext.Data, allowing services to check consent status without reaching into the raw cookie.
Example: Secure Script Loading
<script src="buildwww/app/main.ts" crossorigin="anonymous"></script>
<script src="/app/main.abc123.js"
integrity="sha256-xyz..."
nonce="random_nonce_here"
crossorigin="anonymous"></script>
Static Asset Pre-Warming
StaticAssetWarmService is a [HostedService] that populates the ResponseCaching middleware cache with compressed static assets immediately after application startup.
How it works:
- Waits for the host to fully start via
IAppStartupStatus - Reads all entries from the Vite manifest
- Requests each asset with
Accept-Encoding: brandAccept-Encoding: gzipagainst the loopback address (viaIServerSettings) ResponseCachingstores each compressed variant keyed onVary: Accept-Encoding
The warm-up client only accepts loopback base addresses before installing its certificate-bypass handler. Wildcard server bindings are normalized to localhost; non-loopback bindings are ignored for warm-up.
Compression defaults — both use CompressionLevel.SmallestSize (maximum compression) since only static files are compressed and the cost is paid once at startup:
| Provider | Default Level | Override Hook |
|---|---|---|
| Brotli | SmallestSize (Level 11) |
ConfigureBrotliCompressionLevel() |
| Gzip | SmallestSize |
ConfigureGzipCompressionLevel() |
First request after startup returns pre-compressed content from cache — zero compression latency for end users.
Local Development Infrastructure
Use DRN.Framework.Testing to provision Postgres during local development without manual Docker management.
1. Add Conditional Reference
Add the following to your .csproj file to ensure the testing library and Testcontainers dependencies are only included during development.
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\DRN.Framework.Testing\DRN.Framework.Testing.csproj" />
</ItemGroup>
2. Configure Startup Actions
Implement DrnProgramActions to trigger the auto-provisioning.
#if DEBUG
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
}
};
// Auto-starts containers if not running 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.
public class MyWorker(IAppStartupStatus startupStatus) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!await startupStatus.WaitForStartAsync(stoppingToken))
return; // Cancelled before startup completed
// Application is fully started — safe to proceed
}
}
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.
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-06-14 21:30:19 +0300
Hash: ebe902574f06c0a2c2c0d8b4b2e28aafbfe418a6
| 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.9.5)
- NetEscapades.AspNetCore.SecurityHeaders (>= 1.3.1)
- NLog.Targets.Network (>= 6.0.4)
- NLog.Web.AspNetCore (>= 6.1.3)
- Swashbuckle.AspNetCore (>= 10.2.1)
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-06-14 21:30:19 +0300 Hash: ebe902574f06c0a2c2c0d8b4b2e28aafbfe418a6 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.5 | 43 | 6/14/2026 |
| 0.9.5-preview011 | 40 | 6/14/2026 |
| 0.9.5-preview010 | 38 | 6/14/2026 |
| 0.9.5-preview009 | 42 | 6/14/2026 |
| 0.9.5-preview008 | 42 | 6/14/2026 |
| 0.9.5-preview007 | 40 | 6/13/2026 |
| 0.9.5-preview006 | 100 | 6/7/2026 |
| 0.9.5-preview005 | 97 | 6/7/2026 |
| 0.9.5-preview004 | 98 | 6/7/2026 |
| 0.9.5-preview003 | 100 | 6/6/2026 |
| 0.9.5-preview002 | 100 | 6/2/2026 |
| 0.9.5-preview001 | 102 | 6/1/2026 |
| 0.9.4 | 114 | 5/13/2026 |
| 0.9.3 | 132 | 4/25/2026 |
| 0.9.2 | 115 | 4/18/2026 |
| 0.9.1 | 131 | 3/26/2026 |
| 0.9.0 | 118 | 3/25/2026 |
| 0.9.0-preview001 | 118 | 3/22/2026 |
| 0.8.0 | 125 | 3/14/2026 |
| 0.7.0 | 122 | 3/8/2026 |
Not every version includes changes, features or bug fixes. This project can increment version to keep consistency with other DRN.Framework projects.
## Version 0.9.5
### Breaking Changes
* **Host Filtering Configuration**: `AllowedHosts` must now be configured outside Development and cannot contain `*`. Development still falls back to `*` for local convenience, but Staging and Production fail closed when host filtering is missing or wildcarded.
### Changed
* **Razor Development Workflow**: Removed the default `Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation` dependency and `AddRazorRuntimeCompilation()` registration. DRN now relies on Razor SDK build-time/publish-time compilation and IDE or `dotnet watch` Hot Reload for local `.cshtml` iteration, following .NET 10 guidance that Razor runtime compilation is obsolete.
* References: [Razor runtime compilation is obsolete](https://learn.microsoft.com/en-us/aspnet/core/breaking-changes/10/razor-runtime-compilation-obsolete), [.NET Hot Reload support for ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/test/hot-reload).
* **Production Error Responses**: Production exception responses and `ProblemDetails` no longer include raw exception messages or stack details. Development keeps detailed diagnostics.
### Bug Fixes
* **Vite Manifest Integrity and Validation**: Vite manifest parsing now resolves from the web root, validates that manifest files and referenced assets stay under the expected output folders, fails fast on missing manifest assets, and emits SHA-256 integrity hashes in standard Base64 for browser SRI compatibility.
### New Features
* **Dual-Layer Rate Limiting**: Added pre-auth and post-auth rate limiting with lifetime-specific `ISingletonRateLimitRule` / `IScopedRateLimitRule` support, safe partition-based rule results, and extensibility for tenant/user/IP policies.
* `SingletonRateLimitRule` and `ScopedRateLimitRule` now provide automatic attribute-based DI registration for derived rules; direct interface implementations can still opt into explicit DI attributes.
* Pre-auth rate limiting honors ASP.NET Core `[DisableRateLimiting]` endpoint metadata and keeps `[EnableRateLimiting]` aligned with ASP.NET Core global-limiter semantics.
* Default post-auth partitioning now uses stable user id claims (`NameIdentifier`/`sub`) with auth scheme instead of mutable display names.
* Matching rules compose through .NET's native chained limiter so tenant + user + IP policies can be enforced together.
* Scoped rules are post-auth only, preserve global ordering with singleton rules, compose together, and same-order rules can opt into `ShortCircuitOnMatch` for allow/deny precedence.
* Rule-level `PolicyName` filters DRN rules by ASP.NET Core `[EnableRateLimiting("policy-name")]` endpoint metadata without replacing native named policies.
* Added app-specific `RateLimitFor` pattern (e.g., `Sample.Hosted.Helpers.RateLimitFor`) for claim-based scoped partitions composed from `Get.Claim.*` primitives backed by cached `IScopedUser` claims.
* Post-auth rate limiting now preserves named policies and rejection callbacks configured through `AddRateLimiter(options => ...)`, so `[EnableRateLimiting("policy-name")]` works alongside DRN's global rule chain.
* DRN rule rejection attribution now tracks the rule that actually failed, so native named-policy rejections do not trigger unrelated DRN rule `OnRejectedAsync` callbacks.
* Hot-path rule selection uses value-based rule results/matches and cached default-rule option factories to reduce avoidable per-request allocation pressure.
* Added `RateLimitRuleResult.DenyRequest(...)` and explicit `RateLimitRuleAction` values for immediate 429 denials, keeping allow, deny, quota, and short-circuit semantics separate and testable.
* Added `DRN.Framework.Hosting.RateLimiting` metrics for OpenTelemetry exports, including pre-auth lease metrics, DRN rule-level rejection counters, and an `action` tag for `limit` / `allow` / `deny` visibility.
* Pre-auth and post-auth rejection logging now use `DrnRateLimit.PartitionLogMode`, defaulting to deterministic keyed hashes for correlation without raw API-key, tenant-hint, service-id, user-id, or IP leakage. `PlainText` can be enabled explicitly for controlled development or dedicated audit sinks.
* Pre-auth and post-auth token bucket settings can now diverge via phase-specific `DrnAppFeatures` overrides; pre-auth defaults are intentionally coarser for B2B NAT/VPN/CDN egress addresses.
* Production docs clarify rate limit settings, endpoint metadata usage, reference links, dynamic tenant-plan guidance, and that built-in limiter state is process-local and should be paired with edge or Redis-backed distributed limiting for horizontally scaled enforcement.
* **Vite Manifest Publish Support**: Added a transitive MSBuild target that includes `wwwroot/**/.vite/manifest.json` in Web SDK publish output so published applications preserve Vite manifest lookup, SRI generation, and static asset pre-warming. Set `DrnHostingViteManifestPublishItemsEnabled=false` to opt out.
## Version 0.9.4
Dependencies upgraded to dotnet 10.0.8
## Version 0.9.3
Dependencies upgraded to dotnet 10.0.7
## Version 0.9.2
Dependencies upgraded to dotnet 10.0.6
## Version 0.9.1
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and is proud to inherit his spiritual legacy: 'I am not leaving behind any definitive text, any dogma, any frozen, rigid rule as my spiritual legacy. My spiritual wealth is science and reason. Those who wish to embrace me after my death will become my spiritual heirs if they accept the guidance of reason and science on this fundamental axis.'
### New Features
* **Composable Builder Configuration**: Extracted `ConfigureLoggingBuilder` and `ConfigureWebHostBuilder` as `protected virtual` methods from `ConfigureApplicationBuilder` for independent subclass customization.
## Version 0.9.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and stands behind his remarkable words: 'Peace at home, peace in the world.'
## Version 0.8.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals, rooted in his timeless words that 'science is the truest guide in life.' In that spirit, and to honor the 14 March Scientists Day, this release is dedicated to the researchers working for the benefit of humanity, and to the rejection of my first academic paper :) ([JOSS #10176](https://github.com/openjournals/joss-reviews/issues/10176)).
### New Features
* **ApplicationLifetime Shutdown Hook**: `DrnProgramBase` now registers `IHostApplicationLifetime.StopApplication` as `ApplicationLifetime.ShutdownAction` during application bootstrap. This enables `TimeStampManager`'s clock drift handler to trigger graceful application shutdown when critical drift is detected.
## Version 0.7.0
My family celebrates the enduring legacy of Mustafa Kemal Atatürk's enlightenment ideals and honors 8 March, International Women's Day, a cause inseparable from his vision of equality. This release is dedicated to freedom of speech, democracy, women's rights, and Prof. Dr. Ümit Özdağ, a defender of Mustafa Kemal Atatürk’s enlightenment ideals.
> [!WARNING]
> Since v0.6.0 (released 10 November 2024), substantial changes have occurred. This release notes file has been reset to reflect the current state of the project as of 08 March 2026. Previous history has been archived to maintain a clean source of truth based on the current codebase.
### New Features
* **Security First Architecture**
* **Fail-Closed MFA**: `MfaEnforcingAuthorizationPolicyProvider` enforces Multi-Factor Authentication by default. Opt-out via `[AllowAnonymous]` or `[Authorize(Policy = AuthPolicy.MfaExempt)]`.
* **Strict CSP & Nonce**: Content Security Policy with automatic nonce generation for all scripts and styles.
* **Security & GDPR Headers**: Automatic injection of `HSTS`, `FrameOptions`, `ContentTypeOptions`, and `SameSite=Strict`/`HttpOnly` cookies.
* **MFA Hooks**: `ConfigureMFARedirection` and `ConfigureMFAExemption` for customizing authentication flow.
* **DrnProgramBase Lifecycle Hooks**
* **Builder Phase**:
* `ConfigureSwaggerOptions`: Customize OpenAPI metadata.
* `ConfigureDefaultSecurityHeaders` / `ConfigureDefaultCsp`: Define security policies.
* `ConfigureMvcBuilder` / `ConfigureMvcOptions`: Customize MVC conventions and Razor Pages options.
* `ConfigureStaticFileOptions` / `ConfigureResponseCachingOptions`: Optimize asset delivery with server-side response caching (16MB max, case-insensitive) and automatic static asset caching.
* `ConfigureResponseCompressionOptions` / `ConfigureCompressionProviders`: Brotli and Gzip compression for static assets with built-in BREACH/CRIME protection.
* `ConfigureCookiePolicy`: Centralized security settings for cookies (HttpOnly, Secure, SameSite) with environment-aware defaults via `IsDevelopmentEnvironment`.
* **Pipeline Phase**:
* `ConfigureApplicationPipelineStart`: HSTS, Forwarded Headers.
* `ConfigureApplicationPreScopeStart`: Static files, caching, and compression.
* `ConfigureApplicationPreAuthentication` / `PostAuthentication`: Localization, MFA logic.
* `MapApplicationEndpoints`: Route mapping.
* **DrnProgramActions**: "Hook Method" pattern for intercepting startup (`ApplicationBuilderCreatedAsync`, `ApplicationBuiltAsync`, `ApplicationValidatedAsync`) without modifying Program.cs.
* **Type-Safe Routing**
* **EndpointCollectionBase**: Strongly-typed API accessors (e.g., `Get.Endpoint.User.Login.Path()`).
* **PageCollectionBase**: Type-safe Razor Page navigation (e.g., `Get.Page.User.Profile`).
* **Validation**: `ValidateEndpoints` ensures all typed routes match actual mapped endpoints at startup.
* **Frontend Integration & TagHelpers**
* **Asset Management**: `ViteScriptTagHelper` and `ViteLinkTagHelper` for resolving manifest-based assets with integrity checks.
* **Security**: `NonceTagHelper` (auto-injects CSP nonce) and `CsrfTokenTagHelper` (auto-injects token for HTMX).
* **Conditional Rendering**: `AuthorizedOnlyTagHelper` (MFA-aware) and `AnonymousOnlyTagHelper`.
* **Navigation**: `PageAnchorAspPageTagHelper` and `PageAnchorHrefTagHelper` automatically mark active links.
* **Modern Defaults**: `ScriptDefaultsTagHelper` applies `defer` for external scripts and `type="module"` for inline scripts by default, with explicit opt-out support.
* **Advanced Middleware & HTTP Standards**
* **Standardized Redirects**: Automatically converts 302 (Found) to 303 (See Other) for modern HTTP/1.1 POST response compliance.
* **Security-First Headers**: Default `Cache-Control: no-store` and strictly configured HSTS/CSP/Nonce headers.
* **Malicious Request Detection**: Automatically aborts requests to protected developer URIs or suspicious paths.
* **Flurl Resilience**: Integrated mapping of `FlurlHttpException` to standard gateway status codes.
* **Developer Diagnostics**
* **Startup Exception Reports**: Generates detailed `StartupExceptionReport.html` if the application fails during initialization (Development only).
* **Enhanced Error Pages**: Custom `RuntimeExceptionPage` and `CompilationExceptionPage` with stack trace analysis and model capture.
* **Diagnostic Events**: Built-in integration with `DiagnosticSource` for unhandled exception tracking.
* **Identity & GDPR Consent**
* **Consent Integration**: Automatic extraction and propagation of `ConsentCookie` model via `ScopedUserMiddleware`.
* **Identity Helpers**: `IdentityApiHelper` for standardized validation problem reporting.
* **Static Asset Pre-Warming**
* **`StaticAssetWarmService`**: `[HostedService]` that populates `ResponseCaching` with Brotli and Gzip compressed Vite manifest assets at startup — zero compression latency for end users.
* **Compression**: `SmallestSize` (maximum) for both Brotli (Level 11) and Gzip by default, overrideable via `ConfigureBrotliCompressionLevel()` / `ConfigureGzipCompressionLevel()`.
* **Infrastructure & Development**
* **`IAppStartupStatus`**: Singleton gate for background services to await full host startup before executing.
* **`IServerSettings`**: Resolves bound Kestrel addresses with wildcard-to-localhost normalization for internal self-requests.
* **Local Provisioning**: `LaunchExternalDependenciesAsync` auto-starts PostgreSQL Testcontainers in Debug mode; RabbitMQ is available through the explicit testing helper.
* **Validation**: `ValidateEndpoints` and `ValidateServicesAddedByAttributesAsync` ensure system integrity at startup.
* **Identity Integration**: `IdentityControllerBase` and `ScopedUserMiddleware` for deep identity context propagation.
---
Documented with the assistance of [DiSC OS](https://github.com/duranserkan/DRN-Project/blob/develop/.agent/rules/DiSCOS.md)
---
**Semper Progressivus: Always Progressive**
## Commit Info
Author: Duran Serkan KILIÇ
Date: 2026-06-14 21:30:19 +0300
Hash: ebe902574f06c0a2c2c0d8b4b2e28aafbfe418a6