Indiko.Hosting.Abstractions 2.6.1

dotnet add package Indiko.Hosting.Abstractions --version 2.6.1
                    
NuGet\Install-Package Indiko.Hosting.Abstractions -Version 2.6.1
                    
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="Indiko.Hosting.Abstractions" Version="2.6.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Indiko.Hosting.Abstractions" Version="2.6.1" />
                    
Directory.Packages.props
<PackageReference Include="Indiko.Hosting.Abstractions" />
                    
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 Indiko.Hosting.Abstractions --version 2.6.1
                    
#r "nuget: Indiko.Hosting.Abstractions, 2.6.1"
                    
#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 Indiko.Hosting.Abstractions@2.6.1
                    
#: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=Indiko.Hosting.Abstractions&version=2.6.1
                    
Install as a Cake Addin
#tool nuget:?package=Indiko.Hosting.Abstractions&version=2.6.1
                    
Install as a Cake Tool

Indiko.Hosting.Abstractions

Core hosting abstractions for the Building Blocks framework. This package provides the foundation that every Indiko-based application is built on: the IBlock lifecycle contract, BlockBase<TOptions> base class, BaseHostBootstraper<T, TBaseStartup> orchestration, and the supporting utilities that operate before the DI container is built.

Table of Contents


Features

Feature Description
IBlock lifecycle interface Five-phase contract covering pre-DI initialization, host builder configuration, service registration, middleware pipeline, and pre-run async hooks
BlockBase<TOptions> Abstract base class that wires options binding, validation, and override hooks out of the box
Typed ConfigureBuilder overload Override ConfigureBuilder(IHostBuilder, TOptions) to receive already-resolved options without manual binding boilerplate
Fluent host builder actions Register Action<IHostBuilder> lambdas on the bootstrapper with .ConfigureHostBuilder(b => ...) — no subclassing required
Virtual ConfigureHostBuilder Subclass the bootstrapper and override ConfigureHostBuilder(IHostBuilder) for access to Configuration before the DI container is built
HostStartupOptions Base options class for startup classes; drives EnableForwardedHeaderOptions and ForceHttps
[BlockLoadOrder(int)] Attribute that controls the order in which blocks are discovered and invoked
BlockManager Static orchestrator that discovers, instantiates, and invokes all IBlock implementations in order
AssemblyLoader Scans loaded assemblies and the dependency graph for concrete IBlock types at startup
ConfigurationUtil Pre-DI helper that delegates to a configuration block or falls back to the standard appsettings.json / environment variable chain
LogUtil Pre-DI helper that delegates to a logging block or falls back to a simple console logger
HostBuilderExtension.UseStartup<T> Extension method that wires a startup class into IHostBuilder.ConfigureServices using expression-compiled constructor injection
IRequestMetadataService Contract for services that surface per-request tenant and user context

Installation

dotnet add package Indiko.Hosting.Abstractions

Quick Start

1. Write a block

using Indiko.Blocks.Common.Abstractions;
using Indiko.Blocks.Common.Abstractions.Attributes;
using Indiko.Blocks.Common.Abstractions.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Options class — section name in appsettings.json must match the class name: "CacheBlock"
public class CacheBlockOptions : BlockOptions
{
    public string ConnectionString { get; set; } = "localhost:6379";
    public int DefaultExpirySeconds { get; set; } = 300;
}

[BlockLoadOrder(10)]
public sealed class CacheBlock : BlockBase<CacheBlockOptions>
{
    public CacheBlock(IConfiguration configuration, ILogger logger)
        : base(configuration, logger) { }

    public override void ConfigureServices(IServiceCollection services)
    {
        base.ConfigureServices(services); // binds CacheBlockOptions into DI

        services.AddStackExchangeRedisCache(o =>
            o.Configuration = Options.ConnectionString);
    }
}

2. Write a startup class

using Indiko.Hosting.Abstractions;
using Indiko.Hosting.Abstractions.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public class MyStartup : HostingStartup
{
    public MyStartup(IHostEnvironment environment, IConfiguration configuration)
        : base(environment, configuration) { }

    public override void ConfigureServices(IServiceCollection services)
    {
        base.ConfigureServices(services); // invokes all blocks' ConfigureServices
        services.AddSingleton<IMyService, MyService>();
    }
}

3. Run it

// Program.cs
using Indiko.Hosting.Abstractions;

class Program
{
    static async Task<int> Main(string[] args)
        => await MyBootstrapper.Instance.RunAsync<MyStartup>(args);
}

public sealed class MyBootstrapper : BaseHostBootstraper<MyBootstrapper, IHostingStartup>
{
    // Nothing else needed for the simplest case.
}

4. Add appsettings.json

{
  "ServiceName": "my-service",
  "CacheBlock": {
    "Enabled": true,
    "ConnectionString": "redis-host:6379",
    "DefaultExpirySeconds": 600
  },
  "HostStartupOptions": {
    "EnableForwardedHeaderOptions": true,
    "ForceHttps": false
  }
}

Block Lifecycle

Every IBlock implementation participates in five ordered phases. BlockManager drives all phases; blocks are sorted by [BlockLoadOrder] before any phase begins.

Application start
       │
       ▼
1. Initialize<TType>(object context)
   ─ Pre-DI. ConfigurationUtil uses IConfigurationBlock here.
   ─ LogUtil uses ILoggingBlock here.
   ─ Return value is the result type (e.g. IConfiguration, ILoggerFactory).
       │
       ▼
2. ConfigureBuilder(IHostBuilder)
   ─ Pre-DI. Modify the IHostBuilder before it is built.
   ─ Kestrel, Windows Service, custom DI containers go here.
       │
       ▼
  IHost is built — DI container is now available
       │
       ▼
3. ConfigureServices(IServiceCollection)
   ─ Register services, options, and middleware components.
   ─ BlockBase binds TOptions into the DI options pipeline here.
       │
       ▼
4. Configure(IApplicationBuilder, IServiceProvider, IHostEnvironment)
   ─ Configure the middleware pipeline.
   ─ Called from the startup class, typically driven by the hosting layer.
       │
       ▼
5. PreRunAsync(IServiceProvider)
   ─ Async hook. Seed data, warm caches, run migrations here.
   ─ Called after the host is built, before host.RunAsync().
       │
       ▼
  Host running

Phase responsibilities at a glance

Phase DI available Typical use
Initialize No Provide IConfiguration or ILoggerFactory before DI
ConfigureBuilder No Kestrel options, Windows Service, custom host configuration
ConfigureServices No (registration only) Register services, bind options, add middleware features
Configure Yes (via serviceProvider) Build middleware pipeline, use registered services
PreRunAsync Yes Migrations, cache warm-up, health pre-checks

BlockBase<TOptions>

BlockBase<TOptions> is the abstract base class all production blocks derive from. It handles options binding, validation, and override hooks automatically.

Constructor signature

Every concrete block must provide this constructor — BlockManager requires it:

public MyBlock(IConfiguration configuration, ILogger logger)
    : base(configuration, logger) { }

Options binding

TOptions must implement IBlockOptions (which defines bool Enabled { get; set; }). The base class:

  1. Calls GetModuleOptionsConfigSection() — returns configuration.GetSection(GetType().Name) by default.
  2. Calls config.Get<TOptions>() to bind the section.
  3. Calls OptionsConfigure(options) so you can apply in-code adjustments.
  4. Calls OptionsPostConfigure(options) which executes any stored OverrideModuleOptions action.

Options are refreshed in the constructor, in ConfigureBuilder, and in ConfigureServices.

Defining options

public class EmailBlockOptions : BlockOptions
{
    [Required]
    public string SmtpHost { get; set; }

    [Range(1, 65535)]
    public int SmtpPort { get; set; } = 587;

    public bool UseSsl { get; set; } = true;
}
{
  "EmailBlock": {
    "Enabled": true,
    "SmtpHost": "smtp.example.com",
    "SmtpPort": 587,
    "UseSsl": true
  }
}

OptionsConfigure — in-code defaults and adjustments

Override OptionsConfigure to apply logic that cannot live in JSON, for example deriving values or enforcing invariants:

protected override void OptionsConfigure(EmailBlockOptions options)
{
    if (string.IsNullOrWhiteSpace(options.SmtpHost))
        options.SmtpHost = "fallback-smtp.internal";

    // Enforce SSL in production regardless of config file
    if (Environment?.EnvironmentName == "Production")
        options.UseSsl = true;
}

OverrideModuleOptions — caller-side overrides

A caller can store an override action before the block runs. This is applied last, after OptionsConfigure, so it wins:

var block = new EmailBlock(configuration, logger);
block.OverrideModuleOptions(o => o.SmtpHost = "test-smtp.internal");

This is primarily used by test harnesses or integration scaffolding that needs to redirect block configuration without touching appsettings files.

GetModuleOptionsConfigSection — custom section name

By default the section name equals the class name. Override to change it:

protected override IConfiguration GetModuleOptionsConfigSection()
    => Configuration.GetSection("Email"); // reads "Email" instead of "EmailBlock"

Data Annotations validation

BlockBase.ConfigureServices calls optionsBuilder.ValidateDataAnnotations(). Any [Required], [Range], [MinLength], etc. attributes on TOptions are enforced automatically when the options are first resolved from DI.

Protected members available in blocks

Member Type Description
Configuration IConfiguration Full application configuration
Options TOptions Current options instance
Logger ILogger Logger created before DI via LogUtil
ServiceName string Value of "ServiceName" from configuration
Environment IHostEnvironment Startup environment populated from env vars

Flexible Host Builder Configuration

Three independent layers let you configure the IHostBuilder without forcing everything into a single override point.

Layer 1 — Typed ConfigureBuilder overload (inside a block)

Override the typed overload instead of the plain one to receive options that are already resolved, including any OverrideModuleOptions actions:

public sealed class KestrelBlock : BlockBase<KestrelBlockOptions>
{
    public KestrelBlock(IConfiguration configuration, ILogger logger)
        : base(configuration, logger) { }

    // Typed overload — options are already bound and post-configured
    protected override void ConfigureBuilder(IHostBuilder hostBuilder, KestrelBlockOptions options)
    {
        hostBuilder.ConfigureWebHost(web =>
            web.ConfigureKestrel(kestrel =>
            {
                kestrel.ListenAnyIP(options.HttpPort);
                if (options.HttpsPort > 0)
                    kestrel.ListenAnyIP(options.HttpsPort, l => l.UseHttps());
            }));
    }
}

The base ConfigureBuilder(IHostBuilder) implementation refreshes options then delegates to this overload, so you never need to call both.

Layer 2 — BaseHostBootstraper fluent actions and virtual override

Fluent — no subclassing needed

Chain .ConfigureHostBuilder(...) calls directly on the bootstrapper instance. All actions are applied after all block ConfigureBuilder calls and after the virtual override:

// Program.cs
await new MyBootstrapper()
    .ConfigureHostBuilder(b => b.UseWindowsService())
    .ConfigureHostBuilder(b => b.ConfigureWebHost(web =>
        web.ConfigureKestrel(o => o.ListenAnyIP(8080))))
    .RunAsync<MyStartup>(args);

Multiple calls accumulate in order:

var bootstrapper = new MyBootstrapper();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    bootstrapper.ConfigureHostBuilder(b => b.UseWindowsService());
else
    bootstrapper.ConfigureHostBuilder(b => b.UseSystemd());

bootstrapper.ConfigureHostBuilder(b => b.UseDefaultServiceProvider(o =>
    o.ValidateScopes = true));

return await bootstrapper.RunAsync<MyStartup>(args);
Virtual override — subclass with Configuration access

When the host builder configuration depends on values read from IConfiguration, override ConfigureHostBuilder in a subclass. The Configuration property is available before the DI container is built:

public sealed class MyBootstrapper : BaseHostBootstraper<MyBootstrapper, IHostingStartup>
{
    protected override void ConfigureHostBuilder(IHostBuilder builder)
    {
        var port = Configuration.GetValue<int>("Kestrel:Port", 5000);
        var useHttps = Configuration.GetValue<bool>("Kestrel:UseHttps", false);

        builder.ConfigureWebHost(web =>
            web.ConfigureKestrel(kestrel =>
            {
                kestrel.ListenAnyIP(port);
                if (useHttps)
                    kestrel.ListenAnyIP(port + 1, l => l.UseHttps());
            }));
    }
}
Execution order within CreateHostBuilder
1. BlockManager.ConfigureBlock<IBlock>(x => x.ConfigureBuilder(builder), ...)
        (all blocks, sorted by [BlockLoadOrder])
2. ConfigureHostBuilder(builder)           ← virtual override
3. foreach action in _hostBuilderActions   ← fluent .ConfigureHostBuilder(...)

Layer 3 — HostStartupOptions (startup class flags as virtual properties)

See HostStartupOptions below. Flags that were previously abstract bool properties are now backed by options loaded from appsettings, while still supporting the traditional override pattern.


HostStartupOptions

HostStartupOptions is the base options class for startup behavior. It lives in the "HostStartupOptions" section of appsettings.json.

public class HostStartupOptions
{
    public bool EnableForwardedHeaderOptions { get; set; }
    public bool ForceHttps { get; set; }
}

Configuring from appsettings.json

{
  "HostStartupOptions": {
    "EnableForwardedHeaderOptions": true,
    "ForceHttps": false
  }
}

Traditional override pattern (still supported)

Concrete startup classes in higher-level packages (e.g., Indiko.Hosting.Web) expose these as virtual properties. Existing subclasses that use override continue to work unchanged:

public class MyStartup : WebStartup
{
    public MyStartup(IConfiguration config, IWebHostEnvironment env)
        : base(config, env) { }

    protected override bool EnableForwardedHeaderOptions => true;
    protected override bool ForceHttps => true;
}

Options-driven pattern (new)

When you do not override the properties, the base class reads them from HostStartupOptions bound from appsettings. This lets you control startup behavior purely from configuration without code changes:

{
  "HostStartupOptions": {
    "EnableForwardedHeaderOptions": true,
    "ForceHttps": true
  }
}

Extending HostStartupOptions

Derive a richer options class for your own startup hierarchy:

public class MyStartupOptions : HostStartupOptions
{
    public string[] AllowedOrigins { get; set; } = [];
    public bool EnableSwagger { get; set; }
}

BlockLoadOrder Attribute

[BlockLoadOrder(int order)] is applied to IBlock implementations to control the order in which BlockManager invokes them across all lifecycle phases.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BlockLoadOrderAttribute : Attribute
{
    public int Order { get; }
    public BlockLoadOrderAttribute(int order) => Order = order;
}

Ordering rules

  • Blocks without the attribute receive int.MaxValue and run last.
  • Lower numbers run earlier.
  • Blocks with equal order values run in undefined relative order.
  • Ordering is applied once at startup and applies to every lifecycle phase.

Common order conventions

Order Block type Reason
-1 Configuration blocks (e.g., AppSettingsConfigurationBlock) Must run first — all other blocks need IConfiguration
0 or omitted Most feature blocks Default; no ordering dependency
1 Logging blocks (e.g., SerilogBlock) After configuration, before everything else
10+ Application feature blocks After infrastructure is established
100+ Blocks with runtime dependencies on earlier blocks Explicit late ordering

Examples

// Configuration block — must run before all others
[BlockLoadOrder(-1)]
public sealed class AppSettingsConfigurationBlock : BlockBase, IConfigurationBlock
{
    public AppSettingsConfigurationBlock(IConfiguration cfg, ILogger log)
        : base(cfg, log) { }

    public override TType Initialize<TType>(object context = default)
    {
        // Returns IConfiguration instance
        return new AppsettingsConfigurationBuilder().Build() as TType;
    }
}

// Logging block — after configuration
[BlockLoadOrder(1)]
public sealed class SerilogBlock : BlockBase<SerilogBlockOptions>
{
    public SerilogBlock(IConfiguration cfg, ILogger log) : base(cfg, log) { }

    public override void ConfigureBuilder(IHostBuilder hostBuilder, SerilogBlockOptions options)
    {
        hostBuilder.UseSerilog((ctx, cfg) => cfg.ReadFrom.Configuration(ctx.Configuration));
    }
}

// Feature block — standard order
[BlockLoadOrder(10)]
public sealed class CacheBlock : BlockBase<CacheBlockOptions>
{
    public CacheBlock(IConfiguration cfg, ILogger log) : base(cfg, log) { }
    // ...
}

// No attribute — runs after all explicitly ordered blocks
public sealed class SwaggerBlock : BlockBase<SwaggerBlockOptions>
{
    public SwaggerBlock(IConfiguration cfg, ILogger log) : base(cfg, log) { }
    // ...
}

Pre-DI Utilities

These utilities operate before the DI container is built and are used internally by BaseHostBootstraper and HostingStartup. They can also be used directly in bootstrapping code.

ConfigurationUtil

Builds an IConfiguration instance. It first checks whether a block implementing IConfigurationBlock exists and delegates to it. If no configuration block is found, it falls back to the standard chain:

appsettings.json
appsettings.{DOTNET_ENVIRONMENT}.json     (or ASPNETCORE_ENVIRONMENT)
appsettings.{MachineName}.json
Environment variables
Command-line arguments
// Used internally by BaseHostBootstraper.Configuration property:
IConfiguration configuration = ConfigurationUtil.CreateConfiguration();

LogUtil

Creates an ILogger<TTarget>. Delegates to an ILoggingBlock if one is registered. Falls back to a simple console logger (Debug level in DEBUG builds, Information in Release):

ILogger<MyBootstrapper> logger = LogUtil.CreateLogger<MyBootstrapper>(configuration);

AssemblyLoader and BlockManager

AssemblyLoader (internal)

Scans AppDomain.CurrentDomain.GetAssemblies() and the DependencyContext graph to locate all concrete, non-abstract types that implement IBlock. Results are cached after the first scan. Ordering by [BlockLoadOrder] is applied at scan time.

Custom assembly filters can be registered on BlockManager.ModuleCompatibilityCheck to restrict which assemblies are scanned:

// Only scan assemblies whose names start with "MyCompany."
BlockManager.ModuleCompatibilityCheck.Add(
    t => t.Assembly?.GetName().Name?.StartsWith("MyCompany.") == true);

BlockManager

Static class that drives the lifecycle. Two overloads:

ConfigureBlock<TModule>(Action<IBlock>, IConfiguration, ILogger) — invokes an action on every discovered IBlock. Used for ConfigureBuilder, ConfigureServices, and PreRunAsync phases.

ConfigureBlock<TModule, TResult>(Func<TModule, TResult>, IConfiguration, ILogger) — invokes a function on the first IBlock that satisfies TModule and returns its result. Used for Initialize (e.g., to retrieve IConfiguration from a configuration block).

Block instances are created fresh for each invocation via expression-compiled constructors, which avoids reflection overhead in the hot path.


Best Practices

One block per concern

Each block should own exactly one infrastructure concern: caching, storage, messaging, authentication. Blocks that do too much become hard to test and reorder.

Always call base.ConfigureServices

BlockBase.ConfigureServices binds and validates TOptions. Skipping the call means your options are never registered into the DI options pipeline:

public override void ConfigureServices(IServiceCollection services)
{
    base.ConfigureServices(services); // must come first
    services.AddSingleton<IMyService, MyService>();
}

Use [BlockLoadOrder] intentionally

Only apply the attribute when your block has a genuine dependency on another block's output. Assigning arbitrary low numbers creates hidden ordering contracts. The general guidance:

  • Configuration blocks: -1
  • Logging blocks: 1
  • Everything else: omit the attribute unless you have a specific reason

Prefer the typed ConfigureBuilder overload

Overriding ConfigureBuilder(IHostBuilder, TOptions) is safer than overriding ConfigureBuilder(IHostBuilder) directly, because options are already refreshed and post-configured before the typed overload is called.

Keep PreRunAsync idempotent

The host may restart in some environments. Operations in PreRunAsync (migrations, seeding) should be safe to run more than once.

Validate options eagerly

Add [Required] and [Range] attributes to your options class. BlockBase already calls ValidateDataAnnotations(). To additionally validate at startup rather than on first use, call ValidateOnStart() explicitly in ConfigureServices:

public override void ConfigureServices(IServiceCollection services)
{
    base.ConfigureServices(services);
    services.AddOptions<MyBlockOptions>()
            .ValidateOnStart();
}

Use OverrideModuleOptions only in tests

OverrideModuleOptions bypasses appsettings entirely. In production code, prefer environment-specific appsettings files. Reserve OverrideModuleOptions for integration test fixtures:

// In an integration test
var block = new EmailBlock(testConfiguration, NullLogger.Instance);
block.OverrideModuleOptions(o =>
{
    o.SmtpHost = "localhost";
    o.SmtpPort = 2525;
});

Do not use Instance singleton when you need fluent actions

BaseBootstrapper.Instance returns a lazy singleton. Fluent .ConfigureHostBuilder(...) calls mutate the instance state. For testability or when you need independent bootstrapper instances per test, use new MyBootstrapper() directly:

// Good for tests — fresh instance, no shared state
await new MyBootstrapper()
    .ConfigureHostBuilder(b => b.ConfigureAppConfiguration(
        cfg => cfg.AddInMemoryCollection(testSettings)))
    .RunAsync<MyStartup>(args);

Package Role
Indiko.Hosting.Web Web API hosting built on this package
Indiko.Hosting.Mvc MVC hosting built on this package
Indiko.Hosting.BlazorServer Blazor Server hosting built on this package
Indiko.Hosting.Gateway API Gateway hosting built on this package
Indiko.Blocks.Common.Abstractions IBlock, BlockBase<TOptions>, [BlockLoadOrder]
Indiko.Blocks.Common.Management BlockManager, AssemblyLoader
Indiko.Blocks.Configuration.AppSettings AppSettings configuration block ([BlockLoadOrder(-1)])
Indiko.Blocks.Configuration.Consul Consul configuration block
Indiko.Common.Runtime.Abstractions BaseBootstrapper, IBaseStartup

Target Framework

.NET 10


License

MIT — see LICENSE in the repository root.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Indiko.Hosting.Abstractions:

Package Downloads
Indiko.Hosting.BlazorServer

Building Blocks Hosting Blazor Server

Indiko.Hosting.Web

Building Blocks Hosting Web

Indiko.Hosting.Gateway

Building Blocks Hosting Gateway

Indiko.Hosting.Mvc

Building Blocks Hosting MVC

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.6.1 36 4/18/2026
2.6.0 39 4/17/2026
2.5.1 106 4/14/2026
2.5.0 132 3/30/2026
2.2.18 145 3/8/2026
2.2.17 111 3/8/2026
2.2.16 125 3/8/2026
2.2.15 116 3/7/2026
2.2.13 111 3/7/2026
2.2.12 116 3/7/2026
2.2.10 116 3/6/2026
2.2.9 114 3/6/2026
2.2.8 117 3/6/2026
2.2.7 118 3/6/2026
2.2.5 117 3/6/2026
2.2.3 118 3/6/2026
2.2.2 111 3/6/2026
2.2.1 112 3/6/2026
2.2.0 115 3/6/2026
2.1.4 125 3/2/2026
Loading failed