---
title: SAIF.NetCore.ServiceExtensions architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.ServiceExtensions
reference_type: package
---

# SAIF.NetCore.ServiceExtensions architecture

`SAIF.NetCore.ServiceExtensions` is the top layer / composition root of the repository's three-layer model: it owns no domain logic of its own and instead provides `IServiceCollection`/`IApplicationBuilder`/`IWebHostBuilder`/`HostBuilder`/`IHostApplicationBuilder` extension methods, abstract ASP.NET Core startup base classes, console/Windows-service hosting bases, and OpenTelemetry composition helpers that wire together the foundation (`SAIF.NetCore.ServiceContracts`) and nine implementation-layer packages (ten internal package references in total) so that a consuming application only needs this single NuGet package to assemble a complete SAIF application.

## Responsibilities
- Compose dependency injection registrations for the services implemented in other SAIF packages (auth, identity, DB connectivity, environment/configuration, error handling, HTTP clients, logging, OIDC, ping/health, system hashing) through 14 public `IServiceCollection` extension methods on `IServiceCollectionExtensions`.
- Provide three ASP.NET Core startup base-class archetypes that encode the repository's standard application shapes: web UI with OIDC (`AppStartup` → `OidcMvcStartup`/`OidcRazorStartup`), internal/Core API with dual Negotiate+JWT authentication (`CoreAPIStartup`), and external/Edge API with JWT-only authentication (`EdgeAPIStartup`).
- Provide console host-builder configuration through `IHostBuilderExtensions.BuildConsoleApp<TConsole>`/`BuildSaifConsoleApp<TConsole>`, which add configuration sources, logging, and a singleton `IHostedService` descriptor for `TConsole` — any class implementing `IHostedService`, not only the optional `ConsoleBase`/`ConsoleAsyncBase` convenience base classes this library also supplies. Separately, provide `RegisterForWindowsService<T>`/`RegisterForWindowsService()` as `IServiceCollection` SUPPORT-service registrations (Windows event logger, Windows error handler, environment client, DB connection client) for a host/worker that the consuming application supplies and runs independently; these methods do not build or run a host and do not register the consumer's `IHostedService`.
- Supply cross-cutting middleware (`DynamicErrorHandler`, `DebugRequestHandler`, `LegacyBolAuthMiddleware`, `AspNetSessionIdMiddleware`) and an MVC `JwtControllerBase`/`ControllerBaseExtensions` helper surface for consistent HTTP error responses.
- Compose optional OpenTelemetry logging/metrics/tracing (`OpenTelemetryExtensions.ConfigureOpenTelemetry`) as an opt-in capability that no startup base class invokes automatically*.

## Component boundaries
- `IServiceCollectionExtensions` (`SE/IServiceCollectionExtensions.cs`): the primary composition surface — registers `IEnvironmentClient`/`SecureEnvironmentClient` (EnvironmentClient), `IDBConnectionClient`/`ConnectionClient` (DBConnectionClient), Edge/Core API auth services (AuthService), OIDC services (OIDC), the logging service (Logging), the error-handler pipeline (ErrorHandler, ServiceContracts `IErrorHandleable`), factory-activated middleware, and Windows-service registration (SystemHash for secure-setting hashing).
- `IAppBuilderExtensions`/`IWebHostBuilderExtensions`/`IHostBuilderExtensions` (`SE/IAppBuilderExtensions.cs`, `IWebHostBuilderExtensions.cs`, `IHostBuilderExtensions.cs`): pipeline- and host-builder-level composition — configuring the SAIF logger and error handling middleware, and configuring (not building or running) console hosts (`BuildConsoleApp`/`BuildSaifConsoleApp` return a configured `IHostBuilder`; the consumer calls `Build()`/`Run()` on it). Windows-service composition is a separate, `IServiceCollection`-only concern (`RegisterForWindowsService` in `IServiceCollectionExtensions`), not a host-builder responsibility.
- `OpenTelemetryExtensions`/`OpenTelemetryOptions` (`SE/OpenTelemetryExtensions.cs`, `OpenTelemetryOptions.cs`): a self-contained observability composition boundary built directly on the `OpenTelemetry.*` package family, independent of the other SAIF package registrations.
- `Startup` namespace (`SE/Startup/*.cs`): `AppStartup` (abstract web base, host-location-driven auth branching), `OidcMvcStartup`/`OidcRazorStartup` (web UI archetypes), `BaseApiStartup` (shared API base), `CoreAPIStartup` (internal API archetype), `EdgeAPIStartup` (external API archetype) — these classes are the boundary between this library's composition logic and an application's own `Startup`/`Program` type, which derives from one of them.
- `Middleware` namespace (`SE/Middleware/*.cs`): four `IMiddleware` implementations that this library both defines and registers (`DynamicErrorHandler`, `DebugRequestHandler`, `LegacyBolAuthMiddleware`, `AspNetSessionIdMiddleware`); `LegacyBolAuthMiddleware` is defined here, not in `SAIF.NetCore.LegacyAuth`.
- `ControllerBaseExtensions`/`JwtControllerBase`/`IConfigurationExtensions`/`IWindowsServiceErrorHandlerExtensions`: smaller, self-contained helper boundaries (HTTP error responses, JWT claim reads, configuration key validation, Windows-service error-handler fluent setup) that do not depend on the DI registration surface.

## Interaction model
- Composition happens at application startup, not at request time: a consuming application's `Program`/`Startup` calls `IServiceCollectionExtensions` methods (e.g. `RegisterEdgeAPIAuthServices`, `RegisterOidcServices`, `RegisterDBConnectionClient`) during `ConfigureServices`/host building, which registers the concrete implementation types owned by AuthService, DBConnectionClient, EnvironmentClient, OIDC, SystemHash, and the other composed packages into the `IServiceCollection`.
- The three startup archetypes (`AppStartup`/`CoreAPIStartup`/`EdgeAPIStartup`) each call a fixed sequence of these registration and pipeline-configuration extension methods in their `ConfigureServices`/`Configure` overrides, so an application derives from one archetype and overrides only `ConfigureUI`/`ConfigureMvcBuilder` rather than re-assembling the DI graph itself.
- `AppStartup` branches its entire authentication composition on the `ASPNETCORE_HOST_LOCATION` environment variable at startup time: `EXTERNAL` selects OIDC cookie/OpenIdConnect authentication plus SecurityApi authorization, any other value selects Core API dual Negotiate/JWT authentication*.
- `IMiddleware` instances are factory-activated, so `IServiceCollectionExtensions`/`AppStartup` must register them in DI before `UseMiddleware<T>()` adds them to the pipeline in `IAppBuilderExtensions`/`Configure`; `DynamicErrorHandler` aggregates all registered `IErrorHandleable` implementations (ServiceContracts) and delegates to the single registered `IErrorHandler` plus any `IErrorHandlerHook` implementations.
- OpenTelemetry composition is independent of the startup archetypes: an application calls `ConfigureOpenTelemetry` itself (on `IHostBuilder` or `IHostApplicationBuilder`) if it wants OTel logging/metrics/tracing; enabling it replaces the application's existing logging providers with OTel's own*.
- Console applications bypass the ASP.NET Core startup archetypes entirely: a consumer supplies any `IHostedService` (optionally one of this library's `ConsoleBase`/`ConsoleAsyncBase` convenience bases) to `IHostBuilderExtensions.BuildConsoleApp<TConsole>`/`BuildSaifConsoleApp<TConsole>`. Both methods return a configured `IHostBuilder` — `BuildConsoleApp` adds configuration sources, a singleton `IHostedService → TConsole` descriptor, and console logging via `ConfigureAppConfiguration`/`ConfigureServices`/`ConfigureLogging`; `BuildSaifConsoleApp` additionally chains `RegisterSecureEnvironmentClient`, `RegisterDBConnectionClient`, and `IConsoleLogService` registrations onto that same builder. Neither method calls `Build()` or `Run()`: the consumer's own `Program` entry point calls `.Build().Run()` (or `.RunAsync()`) on the returned `IHostBuilder` itself, and that is when `TConsole`'s `IHostedService` is actually constructed and started. Windows services are a distinct pattern: the consumer builds and runs its own host/worker (for example a `BackgroundService`) independently, and calls `RegisterForWindowsService<T>`/`RegisterForWindowsService()` only to add Windows-specific SUPPORT services (Windows event logger, Windows error handler, environment client, DB connection client) to that host's `IServiceCollection` — this call neither builds a host nor registers the consumer's `IHostedService`, and does not require deriving from either console base class.

## Architecture diagram
The following diagram shows ServiceExtensions as the top-layer composition root sitting above the foundation and implementation-layer packages, composing them for the four application archetypes it supports.

```mermaid
flowchart TB
    subgraph Foundation["Foundation layer"]
        SC["SAIF.NetCore.ServiceContracts"]
    end

    subgraph Implementation["Implementation-layer packages composed by this library"]
        direction LR
        AUTH["AuthService"]
        DB["DBConnectionClient"]
        ENV["EnvironmentClient"]
        ERR["ErrorHandler"]
        HTTP["HttpClients"]
        LOG["Logging"]
        OIDC["OIDC"]
        PING["PingableService"]
        HASH["SystemHash"]
    end

    SE["SAIF.NetCore.ServiceExtensions<br>(composition root: IServiceCollectionExtensions,<br>startup bases, middleware, OTel)"]

    SE -.->|NuGet ref| SC
    SE -.->|NuGet ref| AUTH
    SE -.->|NuGet ref| DB
    SE -.->|NuGet ref| ENV
    SE -.->|NuGet ref| ERR
    SE -.->|NuGet ref| HTTP
    SE -.->|NuGet ref| LOG
    SE -.->|NuGet ref| OIDC
    SE -.->|NuGet ref| PING
    SE -.->|NuGet ref| HASH

    subgraph Apps["Application archetypes (consumer-derived)"]
        direction LR
        WEB["AppStartup →<br>OidcMvcStartup / OidcRazorStartup"]
        CORE["CoreAPIStartup<br>(dual Negotiate+JWT)"]
        EDGE["EdgeAPIStartup<br>(JWT only)"]
        CONSOLE["Console host<br>(consumer calls Build/Run; any IHostedService,<br>optionally ConsoleBase / ConsoleAsyncBase)"]
        WINSVC["Windows service host/worker<br>(consumer-supplied, independent)"]
    end

    WEB -->|derives from| SE
    CORE -->|derives from| SE
    EDGE -->|derives from| SE
    CONSOLE -->|configured via BuildConsoleApp /<br>BuildSaifConsoleApp — consumer builds/runs| SE
    WINSVC -->|adds support services via<br>RegisterForWindowsService| SE

    OTEL["OpenTelemetry.* packages<br>(opt-in, independent)"] --> SE
```

## Sequence diagram
The following diagram shows the startup-time composition sequence when an application derives from `EdgeAPIStartup`, split into its two distinct phases: `ConfigureServices` (DI registration, no HTTP pipeline yet) and `Configure` (HTTP request pipeline construction). This is a startup-time composition sequence, not a runtime request call chain.

```mermaid
sequenceDiagram
    participant App as Application Program/Startup
    participant Edge as EdgeAPIStartup
    participant ISCE as IServiceCollectionExtensions
    participant DI as IServiceCollection
    participant Pipe as IApplicationBuilder pipeline
    Note over App,DI: Phase 1 — ConfigureServices (registration only)
    App->>Edge: derive from EdgeAPIStartup
    Edge->>ISCE: RegisterEnvironmentClient()
    ISCE->>DI: register non-secure IEnvironmentClient + null ISystemHashService
    Edge->>DI: AddControllers() / ConfigureBuilder()
    Edge->>ISCE: RegisterLoggingService()
    ISCE->>DI: register ISaifLogService
    Edge->>ISCE: RegisterEdgeAPIAuthServices()
    ISCE->>DI: register AuthService JWT-only auth types
    Edge->>DI: RegisterFactoryActivatedMiddleware()
    Note over App,Pipe: Phase 2 — Configure (HTTP pipeline construction)
    Edge->>Pipe: ConfigureMiddleware(app): buffering, error handler, debug handler (before auth)
    Edge->>Pipe: UseAuthentication / UseHsts / UseHttpsRedirection / UseRouting / UseAuthorization
    Edge->>Pipe: UseEndpoints(MapControllers)
    Edge->>Pipe: ConfigureSaifLogger()
    Pipe-->>App: fully composed request pipeline
```

## Design constraints
- No project references between libraries in this repository; every cross-library dependency, including all ten this library composes, is a NuGet package reference, consistent with the repository-wide layering in [`Architecture.md`](../../../Architecture.md).
- This library directly references only 10 of the other 18 libraries in this repository (AuthService, DBConnectionClient, EnvironmentClient, ErrorHandler, HttpClients, Logging, OIDC, PingableService, ServiceContracts, SystemHash); `HashUtil`, `Common`, `Clients.UC4`, `LegacyAuth`, `NHibernate`, `NtlmApiAuth`, `Services.Messaging`, and `WindowsService` are not referenced and are not wired by this library, so `Architecture.md`'s description of ServiceExtensions pulling in "the full set" of SAIF services is only partially accurate*.
- `AppStartup`'s authentication branch is driven by a machine/process environment variable (`ASPNETCORE_HOST_LOCATION`) read at startup, making the composed authentication stack an environment-configuration concern rather than a compile-time one*.
- Enabling `ConfigureOpenTelemetry` replaces all existing logging providers with OTel's own, so console/other logging configured before the call is lost unless re-added by the application*.
- Several Windows-specific assumptions are present: `EnvironmentVariableTarget.Machine` environment-variable reads, the Negotiate/IIS authentication scheme, and the `APP_POOL_ID` environment variable used for the `iis.app_pool.name` OpenTelemetry resource attribute are all silently inert on non-Windows hosts*.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [Service registration](service-registration.md)
- [API reference](api/index.md)
- [SAIF.NetCore.ServiceContracts overview](../saif-netcore-servicecontracts/overview.md)
- [SAIF.NetCore.AuthService overview](../saif-netcore-authservice/overview.md)
- [SAIF.NetCore.OIDC overview](../saif-netcore-oidc/overview.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions.csproj`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs`, `IAppBuilderExtensions.cs`, `IWebHostBuilderExtensions.cs`, `IHostBuilderExtensions.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/OpenTelemetryExtensions.cs`, `OpenTelemetryOptions.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IConfigurationExtensions.cs`, `ControllerBaseExtensions.cs`, `IWindowsServiceErrorHandlerExtensions.cs`, `ConsoleAsyncBase.cs`, `ConsoleBase.cs`, `JwtControllerBase.cs`, `Enums.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/DynamicErrorHandler.cs`, `DebugRequestHandler.cs`, `LegacyBolAuthMiddleware.cs`, `AspNetSessionIdMiddleware.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs`, `OidcMvcStartup.cs`, `OidcRazorStartup.cs`, `BaseApiStartup.cs`, `CoreAPIStartup.cs`, `EdgeAPIStartup.cs`
- `Architecture.md`
