Skip to content

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.

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.

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.
  • 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*.

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