Indiko.Hosting.Web
2.7.6
See the version list below for details.
dotnet add package Indiko.Hosting.Web --version 2.7.6
NuGet\Install-Package Indiko.Hosting.Web -Version 2.7.6
<PackageReference Include="Indiko.Hosting.Web" Version="2.7.6" />
<PackageVersion Include="Indiko.Hosting.Web" Version="2.7.6" />
<PackageReference Include="Indiko.Hosting.Web" />
paket add Indiko.Hosting.Web --version 2.7.6
#r "nuget: Indiko.Hosting.Web, 2.7.6"
#:package Indiko.Hosting.Web@2.7.6
#addin nuget:?package=Indiko.Hosting.Web&version=2.7.6
#tool nuget:?package=Indiko.Hosting.Web&version=2.7.6
Indiko.Hosting.Web
A convention-over-configuration ASP.NET Core Web API hosting bootstrap library. Drop in WebHostBootstrapper and a single WebStartup subclass, and the full production middleware stack — CORS, response caching, health checks, API versioning, HTTPS enforcement, forwarded headers, and more — is wired for you automatically.
Table of Contents
- Features
- Installation
- Quick Start
- WebStartupOptions: Options-Driven Configuration
- Flexible Host Builder
- CORS Configuration
- Response Caching and CacheProfiles
- API Versioning
- Health Checks
- HTTPS and HSTS
- Forwarded Headers
- IRequestMetadataService
- Environment-Specific Behavior
- Best Practices
- Related Packages
Features
- Zero-boilerplate startup — subclass
WebStartup, point the bootstrapper at it, and the entire middleware pipeline is configured. - Options-driven flags —
WebStartupOptionsis bound fromappsettings.json; toggle behaviour per environment without changing code. - Virtual property overrides — each flag is also a
protected virtualproperty; subclasses can override to hard-code architecturally fixed values. - Health check endpoint —
/healthzregistered automatically; extend with custom checks. - Response caching —
AddResponseCaching()andUseResponseCaching()wired; declare cache profiles in configuration. - CORS — Development: any origin; Production: restricted to
AllowOriginsfrom configuration, with named policiesCorsPolicyandAllowHeaders(wildcard subdomain support). - API versioning — URL-segment versioning enabled in Development; disabled in non-Development environments.
- Controllers-only or ControllersWithViews — toggled via
AddControllersWithViews; static files andMapDefaultControllerRoutefollow automatically. - JSON cycle handling —
ReferenceHandler.IgnoreCyclesapplied globally. - Forwarded headers — opt-in via
EnableForwardedHeaderOptions. - Force HTTPS — opt-in via
ForceHttps; applied on top of the environment-level redirection. - HSTS — enabled automatically in Production.
- IRequestMetadataService — extracts tenant and user from the JWT
Authorizationheader; registered asTransient. - Flexible host builder — fluent
.ConfigureHostBuilder(Action<IHostBuilder>)for host extensions such asUseWindowsService(). - Block system integration —
BlockManagerpropagatesConfigureServices,Configure,ConfigureBuilder, andPreRunAsyncto all registered Indiko blocks.
Installation
dotnet add package Indiko.Hosting.Web
Quick Start
1. Create your Startup class
using Indiko.Hosting.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebApi;
public class MyStartup : WebStartup
{
public MyStartup(IConfiguration configuration, IWebHostEnvironment environment)
: base(configuration, environment)
{
}
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services); // always call base first
// Register your own services
services.AddScoped<IOrderService, OrderService>();
}
}
2. Write your entry point
// Program.cs
await new WebHostBootstrapper().RunAsync<MyStartup>(args);
3. Add appsettings.json
{
"ServiceName": "MyWebApi",
"AllowOrigins": "https://app.example.com,https://admin.example.com",
"WebStartupOptions": {
"AddControllersWithViews": false,
"EnableForwardedHeaderOptions": true,
"ForceHttps": false
},
"CacheProfiles": {
"Default": { "Duration": 60 },
"NoCache": { "Duration": 0, "NoStore": true }
}
}
4. Add a controller
using Microsoft.AspNetCore.Mvc;
namespace MyWebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orders;
public OrdersController(IOrderService orders) => _orders = orders;
[HttpGet]
public async Task<IActionResult> GetAll() =>
Ok(await _orders.GetAllAsync());
}
Run with dotnet run. The API is available immediately; the health endpoint is at /healthz.
WebStartupOptions: Options-Driven Configuration
WebStartupOptions extends HostStartupOptions and exposes three flags that control how the middleware pipeline is assembled:
| Property | Type | Default | Description |
|---|---|---|---|
AddControllersWithViews |
bool |
false |
Registers AddControllersWithViews instead of AddControllers. Enables static files and MapDefaultControllerRoute. |
EnableForwardedHeaderOptions |
bool |
false |
Calls UseForwardedHeaders() at the start of the pipeline. Required when running behind a reverse proxy. |
ForceHttps |
bool |
false |
Calls UseHttpsRedirection() regardless of environment. Production always includes HTTPS redirection anyway. |
Configure via appsettings.json
All three properties are automatically bound from the "WebStartupOptions" configuration section. No code change is required:
{
"WebStartupOptions": {
"AddControllersWithViews": false,
"EnableForwardedHeaderOptions": true,
"ForceHttps": false
}
}
Use per-environment overrides by maintaining separate files:
// appsettings.Production.json
{
"WebStartupOptions": {
"EnableForwardedHeaderOptions": true,
"ForceHttps": true
}
}
// appsettings.Development.json
{
"WebStartupOptions": {
"ForceHttps": false
}
}
Configure via property override
Each option is exposed as a protected virtual property. Override it in your subclass to hard-code a value that takes precedence over configuration:
public class MyStartup : WebStartup
{
public MyStartup(IConfiguration configuration, IWebHostEnvironment environment)
: base(configuration, environment) { }
// Always behind a reverse proxy in every deployment
protected override bool EnableForwardedHeaderOptions => true;
// This API never renders Razor views
protected override bool AddControllersWithViews => false;
}
When to use each approach
| Scenario | Recommended approach |
|---|---|
| Different values per environment | appsettings.{Environment}.json |
| Feature is architecturally fixed (e.g., always behind a proxy) | Property override in subclass |
| Same binary deployed with different configuration | appsettings.json |
| Quick local prototyping | Either |
Flexible Host Builder
WebHostBootstrapper is sealed, but IHostBuilder can be extended fluently before RunAsync is called:
await new WebHostBootstrapper()
.ConfigureHostBuilder(b => b.UseWindowsService())
.RunAsync<MyStartup>(args);
Multiple calls chain without conflict:
await new WebHostBootstrapper()
.ConfigureHostBuilder(b => b.UseWindowsService())
.ConfigureHostBuilder(b =>
{
b.ConfigureAppConfiguration((ctx, cfg) =>
cfg.AddEnvironmentVariables("MYAPP_"));
})
.RunAsync<MyStartup>(args);
Actions registered via .ConfigureHostBuilder() are applied after all BlockManager.ConfigureBlock calls and after the internal ConfigureWebHostDefaults, so they execute last and can override anything set by blocks.
CORS Configuration
CORS behaviour differs between Development and all other environments.
Development
Any origin, any method, any header — no configuration needed. The default CORS policy is registered and applied by app.UseCors() (no policy name argument).
Production and Staging
Origins are read from the top-level "AllowOrigins" key as a comma-separated string. Two named policies are registered:
| Policy name | Behaviour |
|---|---|
CorsPolicy |
Exact origin match, any method, any header, credentials allowed |
AllowHeaders |
Wildcard subdomain match, any method, any header, credentials allowed |
app.UseCors("CorsPolicy") is applied in the pipeline. AllowHeaders is available for explicit [EnableCors("AllowHeaders")] decoration on individual controllers or actions.
{
"AllowOrigins": "https://app.example.com,https://admin.example.com"
}
Warning: If
AllowOriginsis absent or empty in a non-Development environment, a warning is logged and no CORS policy is registered. Browsers will block all cross-origin requests. Always set this key in production configuration.
Custom CORS policies
Override ConfigureServices to add supplementary policies alongside the base ones:
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services); // registers base policies first
services.AddCors(options =>
{
options.AddPolicy("MobileClients", builder =>
{
builder.WithOrigins("https://mobile.example.com")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
Then decorate the relevant controllers:
[EnableCors("MobileClients")]
[ApiController]
[Route("mobile/[controller]")]
public class MobileOrdersController : ControllerBase { ... }
Response Caching and CacheProfiles
AddResponseCaching() and UseResponseCaching() are registered unconditionally. Cache profiles are loaded from the "CacheProfiles" configuration section and made available to all MVC controller options:
{
"CacheProfiles": {
"Default": { "Duration": 60 },
"Long": { "Duration": 3600 },
"NoCache": { "Duration": 0, "NoStore": true }
}
}
Use profiles on individual actions with the [ResponseCache] attribute:
[HttpGet("{id}")]
[ResponseCache(CacheProfileName = "Default")]
public async Task<IActionResult> GetById(int id) { ... }
[HttpGet("live-feed")]
[ResponseCache(CacheProfileName = "NoCache")]
public IActionResult GetLiveFeed() { ... }
All CacheProfile properties supported by ASP.NET Core are available:
| Property | Type | Description |
|---|---|---|
Duration |
int |
Cache duration in seconds |
Location |
ResponseCacheLocation |
Any, Client, or None |
NoStore |
bool |
Sets Cache-Control: no-store |
VaryByHeader |
string |
Adds a Vary response header |
VaryByQueryKeys |
string[] |
Varies the cache by query string keys (requires response caching middleware) |
API Versioning
URL-segment API versioning (via Asp.Versioning) is registered only in Development. In all other environments it is disabled and an Information-level log message is emitted.
Development URL pattern:
GET /v1/orders
GET /v2/orders
When versioning is active, unversioned requests assume the default version (ApiVersion.Default), and responses include api-supported-versions and api-deprecated-versions headers.
using Asp.Versioning;
[ApiController]
[ApiVersion("1.0")]
[Route("v{version:apiVersion}/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll() => Ok("v1 orders");
}
[ApiController]
[ApiVersion("2.0")]
[Route("v{version:apiVersion}/[controller]")]
public class OrdersV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetAll() => Ok("v2 orders with extra fields");
}
API versioning is disabled in Production by design. If production versioning is required, override
ConfigureServicesand registerAddApiVersioningexplicitly after callingbase.ConfigureServices.
Health Checks
AddHealthChecks() is registered unconditionally and the endpoint is mapped at /healthz.
Custom health checks
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddHealthChecks()
.AddSqlServer(Configuration.GetConnectionString("Default"))
.AddRedis(Configuration.GetConnectionString("Redis"))
.AddCheck<MyCustomHealthCheck>("my-service");
}
Custom health check implementation
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class MyCustomHealthCheck : IHealthCheck
{
private readonly IMyDependency _dependency;
public MyCustomHealthCheck(IMyDependency dependency)
=> _dependency = dependency;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var isHealthy = await _dependency.PingAsync(cancellationToken);
return isHealthy
? HealthCheckResult.Healthy("Service responding normally.")
: HealthCheckResult.Unhealthy("Service did not respond.");
}
}
Invoke the endpoint:
GET /healthz
200 OK → Healthy
503 Service Unavailable → Unhealthy
HTTPS and HSTS
| Environment | Behaviour |
|---|---|
| Production | UseHttpsRedirection() + UseHsts() always active |
| Non-production | Neither, unless ForceHttps = true |
Any + ForceHttps = true |
UseHttpsRedirection() added unconditionally |
Configure HSTS settings in ConfigureServices if you need to adjust max-age or enable preload:
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
}
Forwarded Headers
When running behind a reverse proxy (nginx, Traefik, Azure Application Gateway, etc.), UseForwardedHeaders() rewrites HttpContext.Connection.RemoteIpAddress, HttpContext.Request.Scheme, and the Host header from the X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers.
Enable via configuration:
{
"WebStartupOptions": {
"EnableForwardedHeaderOptions": true
}
}
Or via property override:
protected override bool EnableForwardedHeaderOptions => true;
To restrict which proxy IPs are trusted, configure ForwardedHeadersOptions in ConfigureServices:
using System.Net;
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
options.KnownNetworks.Clear(); // limit to explicit list
options.KnownProxies.Clear();
options.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
});
}
IRequestMetadataService
IRequestMetadataService is registered as Transient and provides the tenant and user identity extracted from the current HTTP request's Authorization header.
Interface
public interface IRequestMetadataService
{
Task<(string tenant, string user)> GetRequestMetdataAsync();
}
User resolution order
HttpContext.User.Identity.Name— populated when ASP.NET Core authentication middleware is active.- JWT
subclaim from the rawAuthorization: Bearer <token>header — used when auth middleware is not wired but a JWT is still present.
Usage
[ApiController]
[Route("[controller]")]
public class AuditController : ControllerBase
{
private readonly IRequestMetadataService _metadata;
public AuditController(IRequestMetadataService metadata)
=> _metadata = metadata;
[HttpGet("whoami")]
public async Task<IActionResult> WhoAmI()
{
var (tenant, user) = await _metadata.GetRequestMetdataAsync();
return Ok(new { tenant, user });
}
}
Note: The
tenantfield is returned as an empty string in the current implementation. To populate it, subclassRequestMetadataService, override the tenant extraction logic, and register your implementation inConfigureServicesafter thebase.ConfigureServicescall.
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Replace the default implementation
services.AddTransient<IRequestMetadataService, MyTenantAwareMetadataService>();
}
Environment-Specific Behavior
| Feature | Development | Production / Staging |
|---|---|---|
| CORS policy | Any origin (default policy) | AllowOrigins from config (CorsPolicy + AllowHeaders) |
| CORS middleware call | UseCors() (no policy name) |
UseCors("CorsPolicy") |
| API versioning | Enabled (URL segment) | Disabled |
| Developer exception page | Enabled | Disabled |
| HTTPS redirection | Only if ForceHttps = true |
Always |
| HSTS | Disabled | Enabled |
| Forwarded headers | Conditional on EnableForwardedHeaderOptions |
Conditional on EnableForwardedHeaderOptions |
| Static files | Only if AddControllersWithViews = true |
Only if AddControllersWithViews = true |
| Controller routing | MapControllers() or MapDefaultControllerRoute() depending on AddControllersWithViews |
Same |
Best Practices
- Always call
base.ConfigureServices(services)andbase.Configure(app, env, logger)first in your overrides. The base methods register health checks, caching, controllers, CORS, API versioning,IHttpContextAccessor,IRequestMetadataService, and all block services. Calling base last may silently overwrite your registrations. - Set
AllowOriginsin every non-Development environment. If it is absent, CORS is not registered, and a warning log is the only indication. Browsers will block all cross-origin requests. - Use
appsettings.{Environment}.jsonto toggleWebStartupOptionsrather than conditional code in startup. This keeps the binary environment-agnostic. - Register custom health checks by chaining on
AddHealthChecks()rather than callingservices.AddHealthChecks()again — the builder returned frombase.ConfigureServicesis sufficient. - Do not add
UseHttpsRedirection()manually whenForceHttps = true; the baseConfigurecall already adds it and a double registration would redirect twice. - Behind a load balancer or proxy, always enable
EnableForwardedHeaderOptionsto preserve the originating IP and scheme for logging, rate limiting, and IP-based authorization. - Avoid mixing the two configuration approaches for the same property. Pick one: either drive it from
appsettings.jsonor from a property override, so the source of truth is unambiguous.
Related Packages
| Package | Purpose |
|---|---|
Indiko.Hosting.Mvc |
MVC + Razor Pages bootstrap with views, Razor Pages, and static files |
Indiko.Hosting.Abstractions |
Base bootstrapper, HostStartupOptions, IRequestMetadataService interface |
License
MIT — Copyright © INDIKO and contributors.
Repository: https://github.com/0xc3u/BuildingBlocks
| 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
- Asp.Versioning.Mvc (>= 8.1.1)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.1)
- Indiko.Hosting.Abstractions (>= 2.7.6)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.6)
- Microsoft.Extensions.DependencyModel (>= 10.0.6)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.6)
- System.IdentityModel.Tokens.Jwt (>= 8.17.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.7.8 | 98 | 5/7/2026 |
| 2.7.7 | 87 | 5/7/2026 |
| 2.7.6 | 148 | 4/23/2026 |
| 2.7.5 | 157 | 4/23/2026 |
| 2.7.4 | 121 | 4/23/2026 |
| 2.7.3 | 101 | 4/23/2026 |
| 2.7.2 | 105 | 4/23/2026 |
| 2.7.1 | 102 | 4/23/2026 |
| 2.7.0 | 114 | 4/23/2026 |
| 2.6.4 | 122 | 4/21/2026 |
| 2.6.3 | 104 | 4/21/2026 |
| 2.6.2 | 97 | 4/21/2026 |
| 2.6.1 | 95 | 4/18/2026 |
| 2.6.0 | 97 | 4/17/2026 |
| 2.5.1 | 103 | 4/14/2026 |
| 2.5.0 | 120 | 3/30/2026 |
| 2.2.18 | 125 | 3/8/2026 |
| 2.2.17 | 93 | 3/8/2026 |
| 2.2.16 | 99 | 3/8/2026 |
| 2.2.15 | 100 | 3/7/2026 |