Eigenverft.WebLib.Infrastructure 1.0.20264.59795-qa

This is a prerelease version of Eigenverft.WebLib.Infrastructure.
dotnet add package Eigenverft.WebLib.Infrastructure --version 1.0.20264.59795-qa
                    
NuGet\Install-Package Eigenverft.WebLib.Infrastructure -Version 1.0.20264.59795-qa
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Eigenverft.WebLib.Infrastructure" Version="1.0.20264.59795-qa" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Eigenverft.WebLib.Infrastructure" Version="1.0.20264.59795-qa" />
                    
Directory.Packages.props
<PackageReference Include="Eigenverft.WebLib.Infrastructure" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Eigenverft.WebLib.Infrastructure --version 1.0.20264.59795-qa
                    
#r "nuget: Eigenverft.WebLib.Infrastructure, 1.0.20264.59795-qa"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Eigenverft.WebLib.Infrastructure@1.0.20264.59795-qa
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Eigenverft.WebLib.Infrastructure&version=1.0.20264.59795-qa&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Eigenverft.WebLib.Infrastructure&version=1.0.20264.59795-qa&prerelease
                    
Install as a Cake Tool

🧱 Eigenverft.WebLib.Infrastructure

NuGet Version NuGet Downloads Build Status Targets License

Production-oriented ASP.NET Core adapters built on Eigenverft.NetLib.Infrastructure.

WebLib turns configuration, certificate files, and the shared application directory layout into a reload-safe Kestrel/SNI setup. It also connects ASP.NET Core Data Protection to NetLib's composable configuration-value protection without duplicating host-independent infrastructure.


✨ At a glance

Capability Problem solved Starting point
Kestrel and SNI Configuration-driven listeners, host-name certificate selection, and last-known-good certificate reloads ConfigureKestrelSniFromConfiguration(...)
Managed certificates Existing PFX loading or policy-controlled self-signed recovery CertificateRecoveryMode
Protected mappings Persist certificate passwords through composable protection instead of leaving clear text after provisioning AspNetDataProtectionConfigurationValueCodecs
Web host directories Apply NetLib's executable-rooted layout to content root, web root, and wwwroot WebApplicationBuilderFactory
Canonical redirects Normalize apex/www/aliases and HTTPS in one redirect without leaking an incoming HTTP port into the HTTPS target AddCanonicalHostRedirect(...) + UseCanonicalHostRedirect()
Health probe Short-circuit GET/HEAD /health before later filters and suppress probe-originated /favicon.ico noise UseHealthProbeFaviconAware()
HTML status responses Write a small explicit HTML status response for middleware short-circuits using ASP.NET Core reason phrases WriteHtmlStatusResponseAsync(...)

πŸ“¦ Installation

dotnet add package Eigenverft.WebLib.Infrastructure

πŸš€ Quick start

Create an executable-rooted ASP.NET Core host while using NetLib's shared directory layout:

using Eigenverft.NetLib.Infrastructure.Hosting.DirectoryLayout;
using Eigenverft.WebLib.Infrastructure.Hosting.DirectoryLayout;
using Microsoft.AspNetCore.Builder;

WebApplicationBuilder builder =
    WebApplicationBuilderFactory.CreateWithDefaultDirectory();

IAppDirectoryLayout directories = builder.GetDirectoryLayout();
string webRoot = directories["Web"];

WebApplication app = builder.Build();
app.MapGet("/", () => $"Web root: {webRoot}");
app.Run();

WebApplicationBuilderFactory adds only the web-specific projection: ASP.NET Core content/web roots and the semantic "Web" directory entry. Directory creation, validation, writable probes, standard directory keys, and DI registration are provided by NetLib.

πŸ”₯ Optional self-HTTP startup warmup

AddSelfHttpWarmup(...) can issue one pass of HTTP requests to the running application after startup has completed. This is useful when a deployment should pay first-use costs such as JIT compilation, dependency activation, TLS setup, and HTTP connection setup before normal traffic reaches selected endpoints.

For code-based setup, passing the target URL is the normal path and opts in immediately:

using Eigenverft.WebLib.Infrastructure.Hosting.SelfHttpWarmup;

builder.Services.AddSelfHttpWarmup("https://localhost:8443/health");

Multiple targets use the same API. The optional delegate is only for small feature-level tuning:

using System;
using Eigenverft.WebLib.Infrastructure.Hosting.SelfHttpWarmup;

builder.Services.AddSelfHttpWarmup(
    new[]
    {
        "https://localhost:8443/health",
        "https://localhost:8443/",
    },
    options =>
    {
        options.InitialDelay = TimeSpan.FromSeconds(1);
        options.RequestTimeout = TimeSpan.FromSeconds(5);
    });

The parameterless AddSelfHttpWarmup() overload is the configuration-binding path. It remains opt-in through Enabled; URL-based and code-based overloads enable warmup automatically. Values are bound from the SelfHttpWarmup section before code-based options are applied.

{
  "SelfHttpWarmup": {
    "Enabled": true,
    "InitialDelay": "00:00:01",
    "RequestTimeout": "00:00:05",
    "TargetUrls": [
      "https://localhost:8443/health",
      "https://localhost:8443/"
    ]
  }
}

Targets run sequentially once after startup. Shutdown cancels both the post-start delay and any in-flight request. A request timeout or connection failure is logged and does not prevent later targets from being attempted. Redirects are not followed. Standard platform certificate validation remains enabled; self-HTTP warmup intentionally bypasses proxies so the request connects directly to the configured target.

πŸ” ASP.NET Core Data Protection adapter

AspNetDataProtectionStringTransforms adapts an ASP.NET Core IDataProtectionProvider to NetLib's ReversibleStringTransform abstraction. The generic transform and configuration-value codec infrastructure remains in NetLib, so Data Protection can participate without duplicating the generic codec stack in WebLib.

AspNetDataProtectionConfigurationValueCodecs.DataProtection(...) provides the configuration-value convenience layer. Pass the application directory layout and a stable purpose; WebLib derives the standard key-ring path and entry-assembly discriminator. The returned ConfigurationValueCodec can be used independently or at any position in ConfigurationValueCodecs.Compose(...).

🧰 Small request-pipeline helpers

WebLib includes a few intentionally small ASP.NET Core helpers that are useful outside the larger RequestFilters stack.

Canonical host and HTTPS redirect

The normal case needs one registration and one middleware call:

using Eigenverft.WebLib.Infrastructure.Hosting.Middleware.CanonicalHostRedirect;

builder.Services.AddCanonicalHostRedirect(options =>
    options.PrimaryApexHost = "example.com");

WebApplication app = builder.Build();
app.UseCanonicalHostRedirect();

The defaults canonicalize to www, require HTTPS, return 308 Permanent Redirect, and target implicit HTTPS/443. Set RedirectFromHosts, Canonicalization = CanonicalHostMode.ToApex, or one HttpsTargetPort only when the deployment needs them. AddCanonicalHostRedirect() without a delegate binds the CanonicalHostRedirect configuration section.

A redirect combines host and scheme normalization into one hop and preserves PathBase, path, and query. Incoming HTTP ports are never copied to HTTPS. When a reverse proxy supplies the external scheme or host, configure ASP.NET Core Forwarded Headers normally and call UseForwardedHeaders() before UseCanonicalHostRedirect().

Health probe and favicon suppression

UseHealthProbeFaviconAware() handles only GET and HEAD for /health, returns 200 OK with OK for GET, and short-circuits the rest of the pipeline. A GET or HEAD for /favicon.ico returns 204 No Content only when its Referer points to /health. Keep this middleware before filters that a health probe must bypass.

Explicit HTML status response

For a middleware that intentionally terminates a request with an HTML response, use:

using Eigenverft.WebLib.Infrastructure.Hosting;
using Microsoft.AspNetCore.Http;

await context.Response.WriteHtmlStatusResponseAsync(StatusCodes.Status403Forbidden);

The helper uses ReasonPhrases.GetReasonPhrase(...); WebLib does not maintain its own HTTP status code description table. General application error handling remains the responsibility of ASP.NET Core Status Code Pages or Problem Details.

Host filtering remains framework-owned

WebLib intentionally does not provide an AddAllowedHosts replacement. WebApplication.CreateBuilder() already wires ASP.NET Core host filtering to the live configuration object. Clearing builder.Configuration.Sources and adding replacement sources does not remove that wiring, so a rebuilt AllowedHosts value is still consumed by the built-in host-filtering options.

🌐 Kestrel and SNI

ConfigureKestrelSniFromConfiguration(...) is the package's top-level server setup. It configures HTTP/HTTPS listeners, TLS policy, managed PFX files, SNI selection, and atomic certificate reloads while using NetLib's certificate primitives underneath.

The setup separates startup policy, reloadable mappings, certificates, and protection keys:

<application>/
β”œβ”€β”€ AppSettings/
β”‚   β”œβ”€β”€ KestrelSettings.json                ← startup-fixed listener policy
β”‚   └── CertificatesMappingSettings.json    ← protected, reloadable SNI mappings
β”œβ”€β”€ AppCerts/
β”‚   └── localhost.pfx                       ← existing or WebLib-managed certificate
β”œβ”€β”€ AppProtectionKeys/
β”‚   └── ...                                 ← persistent Data Protection key ring
└── wwwroot/

Register configuration and protect certificate passwords

using System;
using System.IO;
using Eigenverft.NetLib.Infrastructure.Hosting.Configuration.Sources;
using Eigenverft.NetLib.Infrastructure.Hosting.Configuration.SwitchableJson;
using Eigenverft.NetLib.Infrastructure.Hosting.Configuration.Values;
using Eigenverft.NetLib.Infrastructure.Hosting.DirectoryLayout;
using Eigenverft.WebLib.Infrastructure.Hosting.Configuration.Values;
using Eigenverft.WebLib.Infrastructure.Hosting.DirectoryLayout;
using Eigenverft.WebLib.Infrastructure.Hosting.Kestrel;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;

WebApplicationBuilder builder = WebApplicationBuilderFactory.CreateWithDefaultDirectory(args);

IAppDirectoryLayout directories = builder.GetDirectoryLayout();
string settingsDirectory = directories[DefaultDirectory.ApplicationSettings];

// Replace the implicit host sources with the selected process-level sources.
builder.ResetToMinimalConfigurationSources(
    includeCommandLineArguments: true,
    includeEnvironmentVariables: true);

// Listener policy is read once while Kestrel is configured.
builder.Configuration.AddJsonFile(
    path: Path.Combine(settingsDirectory, "KestrelSettings.json"),
    optional: false,
    reloadOnChange: false);

// Generate a different stable factor for each application.
byte[] applicationFactor =
{
    0x23, 0x52, 0x66, 0x37, 0x5A, 0x39, 0x27, 0x27,
    0x5E, 0x52, 0x6C, 0x2E, 0x36, 0x49, 0x45, 0x4E,
    0x79, 0x4A, 0x52, 0x43, 0x4E, 0x4D, 0x3F, 0x5E,
    0x50, 0x5A, 0x6A, 0x5F, 0x4E, 0x32, 0x28, 0x4E,
};

string configurationProtectionSecret =
    Environment.GetEnvironmentVariable("APP_CONFIGURATION_PROTECTION_SECRET")
    ?? throw new InvalidOperationException(
        "APP_CONFIGURATION_PROTECTION_SECRET is required.");

ConfigurationValueCodec certificatePasswordCodec =
    ConfigurationValueCodecs.Compose(
        codecs:
        [
            // Separate this application and purpose from another use of the deployment secret.
            ConfigurationValueCodecs.AesPassword(passwordAsciiBytes: applicationFactor),
            // Add the externally supplied secret factor.
            ConfigurationValueCodecs.AesPassword(password: configurationProtectionSecret),
            // Resist an offline copy to another physical-machine identity.
            ConfigurationValueCodecs.PhysicalMachineBoundAes(),
            // Persist the outer key material through ASP.NET Core Data Protection.
            AspNetDataProtectionConfigurationValueCodecs.DataProtection(
                directories: directories,
                purpose: nameof(certificatePasswordCodec)),
        ]);

SwitchableJsonRegistrationOptions certificateSourceOptions = new()
{
    // Follow and reload the active certificate-mapping file.
    ReloadOnChange = true,
    // Protect only certificate passwords, not routing or certificate file names.
    ValueProtection = JsonConfigurationValueProtection.ForPaths(
        codec: certificatePasswordCodec,
        patterns: ["CertificatesMappingSettings:*:Password"]),
};

// Publish complete mapping generations and keep last-known-good data on failure.
builder.AddSwitchableJsonFile(
    name: "KestrelCertificateMappings",
    initialPath: Path.Combine(settingsDirectory, "CertificatesMappingSettings.json"),
    options: certificateSourceOptions);

The reset clears every existing source and then re-adds environment variables and command-line arguments. The explicit JSON sources are registered afterwards and therefore have higher precedence for overlapping keys.

Configure Kestrel and run

// Resolve PFX files below NetLib's validated application certificate directory.
builder.WebHost.ConfigureKestrelSniFromConfiguration(
    certDirOverride: directories[DefaultDirectory.ApplicationCerts]);

WebApplication app = builder.Build();
app.Run();

The two JSON files keep startup-fixed listener configuration separate from reloadable certificate mappings. The switchable source protects only CertificatesMappingSettings:*:Password on disk, decodes it before publication, and retains the last-known-good configuration after a rejected reload. Generate a stable application-specific byte-array factor, protect APP_CONFIGURATION_PROTECTION_SECRET, and provision the file on its target machine. This generic external deployment secret can protect other application configuration; the factor and selected path separate this certificate use. It is not itself a certificate password. The byte array avoids an assembly string-table entry but remains a recoverable structural factor rather than a secret. The outer ASP.NET Core Data Protection layer uses the persistent ApplicationProtectionKeys directory. The convenience codec derives that path from the directory layout and its application discriminator from Assembly.GetEntryAssembly(), rather than mutable host configuration. Preserve the complete key ring, application name, and purpose while protected values may still need to be decoded. Because the purpose comes from nameof(certificatePasswordCodec), treat that variable name as a persisted compatibility contract.

Defense in depth and limits

The stored password is protected in this order:

clear text β†’ application byte factor β†’ deployment secret β†’ machine binding β†’ Data Protection β†’ JSON

Offline reversal requires the protected JSON value, exact codec composition and order, application factor, deployment secret, original platform UUID, complete Data Protection key ring, and matching application name and purpose. The live codec object is unnecessary if its recipe is reconstructed from the assembly or source. A leak of only the JSON file, executable, environment secret, or key-ring directory is insufficient. This makes accidental single-source exposure less likely to reveal the PFX password.

It does not protect against code execution inside the application process: such an attacker can read the decoded configuration or invoke the same pipeline. Losing any factor also prevents legitimate recovery. Preserve the key ring and deployment secret, keep identities stable, and omit machine binding when portable restore or multi-machine deployment is required.

KestrelSettings.json:

{
  "KestrelSettings": {
    "HTTP_PORT": 8080,
    "HTTPS_PORT": 8443,
    "ListenScope": "Localhost",
    "AddServerHeader": false,
    "Protocols": "Http1AndHttp2",
    "PreferLongestSuffixMatch": true,
    "TlsProtocolPolicy": "Default"
  }
}

CertificatesMappingSettings.json:

{
  "CertificatesMappingSettings": [
    {
      "SNI": "localhost",
      "FileName": "localhost.pfx",
      "Password": "change-me"
    }
  ]
}

The extension loads configured PFX files and performs self-signed recovery only when explicitly enabled. It matches exact SNI names and DNS suffixes, prefers the longest suffix by default, and uses the first mapping as the fallback when SNI is absent or unmatched.

Certificate-directory resolution uses the explicit override first, then the top-level CertificatesDirectory value, and finally certs below the content root. Mapping paths and symbolic-link targets cannot escape that directory.

Setting Default Behavior
HTTP_PORT disabled Positive values enable a plaintext HTTP/1 listener.
HTTPS_PORT disabled Values from 1 through 65535 enable the SNI HTTPS listener.
ListenScope Localhost Use AnyIP to bind all available addresses.
AddServerHeader false Controls Kestrel's Server response header.
Protocols Http1AndHttp2 HTTPS HttpProtocols value.
PreferLongestSuffixMatch true Tries the most-specific configured suffix first.
TlsProtocolPolicy Default TLS 1.2/1.3 by default; Strict selects TLS 1.3 only.

At least one listener and one usable certificate mapping are required. Provision the initial PFX password on the target machine rather than committing it; NetLib rewrites the selected value as a codec envelope during source registration and exposes clear text only in memory.

Recovery is opt-in. An omitted or invalid CertificateRecoveryMode selects None, which performs classic PFX loading without generating or persisting fallback certificates. PreserveExisting enables memory-only self-signed recovery without changing the configured path, ReplaceExpired permits missing-file creation and managed expiry renewal, and ReplaceAnyUnusable is for fully application-managed disposable certificates.

PFX state or failure None PreserveExisting ReplaceExpired ReplaceAnyUnusable
Missing file or parent directory Fail; create nothing Memory recovery only Create and persist Create and persist
Valid and contains a private key Load Load Load Load
Imported and expired Fail Keep + memory recovery Replace Replace
Imported but not yet valid Fail Keep + memory recovery Keep + memory recovery Replace
Imported but missing private key Fail Keep + memory recovery Keep + memory recovery Replace
Password mismatch, corrupt/unsupported PFX, or other import failure Fail Keep + memory recovery Keep + memory recovery Authorize replacement
I/O read failure Fail Keep + memory recovery Keep + memory recovery Authorize replacement
Access denied Fail Keep + memory recovery Keep + memory recovery Authorize replacement; the write may still fail
Persistence or atomic-move failure during an authorized create/replace Not applicable Not applicable Return generated certificate in memory and report the failure Same
Concurrent creator wins missing-file race Not applicable Not applicable Keep the winner; return this process’s generated certificate in memory Same

At startup, an invalid PFX under None fails without recovery. During reload, WebLib rejects an invalid candidate and keeps the last-known-good generation active. Memory recovery can keep TLS available at startup for explicit recovery modes. Deleting an application-managed self-signed PFX requests fresh creation only under ReplaceExpired or ReplaceAnyUnusable; PreserveExisting remains memory-only. ReplaceAnyUnusable can overwrite an externally managed certificate if selected incorrectly.

Only CertificatesMappingSettings is hot-reloadable. WebLib publishes a complete replacement generation atomically and keeps the last-known-good certificates active if a reload fails. Listener changes require a host restart. A PFX file change is observed on the next configuration reload; changing the file alone does not emit a reload token.

When migrating from the earlier helper, replace SanNames with the typed AdditionalSelfSignedCertificateDnsNames and AdditionalSelfSignedCertificateIpAddresses properties and use the Eigenverft.WebLib.Infrastructure.Hosting.Kestrel namespace.

πŸ“ Isolated static and PWA hosting

Use MapIsolated(...) for URL subtrees that must be exclusively owned by a static/PWA branch and MapRemaining(...) for the remaining shell pipeline. Both are thin wrappers over native non-rejoining ASP.NET Core branch semantics; no separate routing or mount system is introduced.

using Eigenverft.WebLib.Infrastructure.Hosting.Pipeline;
using Eigenverft.WebLib.Infrastructure.Hosting.StaticFiles;

app.MapIsolated("/apps", apps =>
{
    apps.UseDefaultFiles();
    apps.UseStaticFiles(AdditionalMappings.WebApp);
});

app.MapIsolated("/downloads", downloads =>
{
    downloads.UseStaticFiles(AdditionalMappings.Media);
});

app.MapRemaining(shell =>
{
    shell.UseRouting();
    shell.UseEndpoints(endpoints => endpoints.MapRazorComponents<App>());
});

MapRemaining deliberately exposes a normal IApplicationBuilder; endpoint APIs such as MapStaticAssets() and MapRazorComponents<T>() therefore stay inside native UseEndpoints(...) rather than requiring a WebLib-specific hybrid pipeline/router builder.

MapIsolated preserves the matched path segment, so /apps/... resolves against wwwroot/apps/... using normal ASP.NET Core default-file/static-file middleware. The web-app case composes native UseDefaultFiles() with UseStaticFiles(AdditionalMappings.WebApp); WebLib does not add a PWA-specific hosting primitive. Missing files end with the native branch 404 and do not fall through into the shell. Outer UseStatusCodePagesWithReExecute(...) handling is disabled for isolated requests so global re-execution cannot escape that ownership boundary.

Mappings are strictly additive to ASP.NET Core defaults: AdditionalMappings.WebApp backfills only .br and .dat; AdditionalMappings.Media backfills .avif only on net8.0 and is a no-op on net10.0 where that mapping is already built in. AdditionalMappings.Combine(...) composes typed groups. The underlying FileExtensionContentTypeProvider remains internal, and there is no separate legacy-style UseStaticFilesWithPwaAndBlazorContentTypes(...) API.

🎯 Target frameworks

The package ships dedicated assets for:

  • net8.0
  • net10.0

A .NET 9 consumer can use the compatible net8.0 asset.

πŸ“„ License

Licensed under the MIT License by Eigenverft.


Made with ❀️ by Eigenverft

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.20264.59795-qa 0 8/27/2026
1.0.20264.58170-qa 0 8/26/2026
1.0.20264.58144-qa 0 8/26/2026
1.0.20264.56997-qa 0 8/25/2026
1.0.20264.55888-qa 0 8/24/2026
1.0.20264.55735-qa 0 8/24/2026
1.0.20264.55718-qa 0 8/24/2026
1.0.20264.55668-qa 0 8/24/2026
1.0.20264.55643-qa 0 8/24/2026

Adds small ASP.NET Core request-pipeline helpers for canonical host/HTTPS redirects, fixed health-probe short-circuiting, ReasonPhrases-backed HTML status responses, typed HttpContext/client-network features, deduplicated middleware composition, and non-activating service-registration checks. Uses Eigenverft.NetLib.Infrastructure 1.0.20264.58446 and keeps Kestrel/SNI certificate mappings on classic PFX loading by default without automatic recovery.