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

# SAIF.NetCore.WindowsService architecture

SAIF.NetCore.WindowsService is an implementation-layer library (per `Architecture.md`) that provides a small hosting skeleton — a program runner and a timed background-worker base class — for building SAIF Windows Services on the .NET Generic Host, and it depends only on the foundation layer (`SAIF.NetCore.ServiceContracts`), leaving logging, error handling, and DI registration to the consumer's composition root.

## Responsibilities
- Build and run the .NET Generic Host for a Windows Service, including configuration sources, `UseWindowsService()`, an optional startup delay, and error-handler/logger configuration hooks (`ServiceProgramRunnerBase`).
- Provide a fixed-delay background-task loop base class that runs a consumer-supplied `IServiceTaskRunner` repeatedly until cancellation (`ServiceWorker<T>`).
- Apply a fail-stop error policy: catch every exception from the task runner, report it through `IWindowsServiceErrorHandler`, and stop the host unless the exception type is explicitly shutdown-exempt.
- Track process-global host state (`ServiceProgramRunnerBase.ServiceHost`) so the worker's error-handling path can trigger a graceful host shutdown.

## Component boundaries
- `ServiceProgramRunnerBase`: owns the host lifecycle — building the `IHostBuilder`, applying the optional `DelayStart` startup delay, and invoking the `ConfigureErrorHandler`/`ConfigureLogger` hooks before the host runs.
- `ServiceWorker<T>` (`where T : IServiceTaskRunner`): owns the recurring task loop, per-run error handling, and the shutdown-exemption check; it is a `Microsoft.Extensions.Hosting.BackgroundService` subclass.
- Neither type has any abstract members; both are "abstract" only to prevent direct instantiation, so a consumer can derive with an empty override body*.
- All service interfaces these base classes depend on (`IServiceTaskRunner`, `IWindowsServiceErrorHandler`, `ISaifWindowsServiceLogService`, `ShutdownExemptExceptionType`) are defined in `SAIF.NetCore.ServiceContracts`, not in this library.
- This library ships **no** DI registration extension method. The companion registration, `RegisterForWindowsService`/`RegisterForWindowsService<T>`, is owned entirely by `SAIF.NetCore.ServiceExtensions`, and this library does not reference that package.

## Interaction model
- A consumer writes `class Program : ServiceProgramRunnerBase`, overrides `CreateHostBuilder` to call `base.CreateHostBuilder(args).ConfigureServices` with a lambda that registers its `ServiceWorker<T>` subclass with `AddHostedService<>` and calls `SAIF.NetCore.ServiceExtensions.RegisterForWindowsService`, then calls `new Program().Run(args)` from `Main`*.
- `Run` builds the host once (`Create`), applies the optional `DelayStart` delay, invokes `Configure` (which calls the consumer's `ConfigureErrorHandler` hook and then `ConfigureLogger`), and finally blocks on `ServiceHost.Run()`.
- `ConfigureLogger` resolves `ISaifWindowsServiceLogService`, `IEnvironmentClient`, and `IHttpJsonClient` from the container with `GetRequiredService` and calls `logger.Configure(envClient, httpClient)`; this library does not implement any of those three services itself.
- `ServiceWorker<T>.ExecuteAsync` runs `T.Run(stoppingToken)`, then waits `TaskDelayMilliseconds`, repeating until cancellation; any exception from the runner is caught, reported via `IWindowsServiceErrorHandler.HandleError`, and — unless shutdown-exempt — triggers `ServiceHost.StopAsync(ShutdownTimeoutSeconds)` through the static `ServiceProgramRunnerBase.ServiceHost` reference.
- `IWindowsServiceErrorHandler`, `ISaifWindowsServiceLogService`, `IEnvironmentClient`, and `IHttpJsonClient` are all resolved from the DI container; none are referenced as compile-time package dependencies of this library — in practice a consumer satisfies them by referencing `SAIF.NetCore.ServiceExtensions` and calling `RegisterForWindowsService`, though nothing in this library enforces that specific call, and an equivalent manual registration of the same four contracts would satisfy the runtime requirement just as well*.

## Architecture diagram
The following diagram shows this library's single NuGet dependency on `SAIF.NetCore.ServiceContracts`, the runtime services the consumer's composition root must supply through `SAIF.NetCore.ServiceExtensions`, and the resulting hosted-service process.

```mermaid
flowchart TD
    SC["SAIF.NetCore.ServiceContracts<br>(IServiceTaskRunner, IWindowsServiceErrorHandler,<br>ISaifWindowsServiceLogService)"]
    WS["SAIF.NetCore.WindowsService<br>ServiceProgramRunnerBase / ServiceWorker&lt;T&gt;"]
    SE["SAIF.NetCore.ServiceExtensions<br>RegisterForWindowsService"]
    EH["SAIF.NetCore.ErrorHandler<br>(IWindowsServiceErrorHandler impl)"]
    LOG["SAIF.NetCore.Logging<br>(ISaifWindowsServiceLogService impl)"]
    ENV["SAIF.NetCore.EnvironmentClient<br>(IEnvironmentClient impl)"]
    HTTP["SAIF.NetCore.HttpClients<br>(IHttpJsonClient impl)"]
    APP["Consuming Windows Service app<br>(Program : ServiceProgramRunnerBase)"]

    WS -.->|NuGet ref| SC
    APP -.->|NuGet ref| WS
    APP -.->|NuGet ref| SE
    SE -.->|NuGet ref| EH
    SE -.->|NuGet ref| LOG
    SE -.->|NuGet ref| ENV
    SE -.->|NuGet ref| HTTP
    APP -->|RegisterForWindowsService| SE
    WS -->|DI-resolved at host composition root| EH
    WS -->|DI-resolved at host composition root| LOG
    WS -->|DI-resolved at host composition root| ENV
    WS -->|DI-resolved at host composition root| HTTP
```

## Sequence diagram
The following diagram shows the primary startup and task-loop sequence, including that every task error is reported through the error handler and that only a non-exempt error triggers the host-wide stop.

```mermaid
sequenceDiagram
    participant Program as "Program (consumer)"
    participant Runner as ServiceProgramRunnerBase
    participant Worker as "ServiceWorker&lt;T&gt;"
    participant TaskRunner as "IServiceTaskRunner"
    participant ErrorHandler as "IWindowsServiceErrorHandler"
    participant Host as "IHost (ServiceHost)"

    Program->>Runner: Run(args)
    Runner->>Runner: Create(args) -> CreateHostBuilder, DelayStartup
    Runner->>Runner: Configure(host) -> ConfigureErrorHandler, ConfigureLogger
    Runner->>Host: ServiceHost.Run()
    Host->>Worker: ExecuteAsync(stoppingToken)
    loop until cancellation
        Worker->>TaskRunner: Run(stoppingToken)
        alt task throws
            TaskRunner-->>Worker: Exception
            Worker->>ErrorHandler: HandleError(message, exception)
            alt exception is non-exempt
                Worker->>Host: ServiceHost.StopAsync(ShutdownTimeoutSeconds)
            else exception is shutdown-exempt
                Worker->>Worker: Task.Delay(TaskDelayMilliseconds)
            end
        else success
            TaskRunner-->>Worker: Completed
            Worker->>Worker: Task.Delay(TaskDelayMilliseconds)
        end
    end
```

## Design constraints
- The library takes **no NuGet dependency on any other SAIF implementation library** — only on `SAIF.NetCore.ServiceContracts` — consistent with the repo-wide rule that implementation libraries depend solely on the foundation layer.
- `ServiceProgramRunnerBase.ServiceHost` is a static, process-global reference; only one host per process is supported, and a `ServiceWorker<T>` hosted by anything other than `ServiceProgramRunnerBase` will throw a `NullReferenceException` on its first non-exempt error*.
- `CreateHostBuilder` appends `AddJsonFile("appsettings.json", optional: true)` and `AddEnvironmentVariables()` **after** the sources `Host.CreateDefaultBuilder` already registers, which reverses the expected configuration-source precedence (later sources win), so `appsettings.json` can override environment-specific settings and command-line arguments*.
- `DelayStartup` blocks the main thread synchronously (`Thread.Sleep`) before the host starts; a `DelayStart` value longer than the Windows SCM start timeout would likely make the service fail to start*.
- `ServiceWorker<T>.ExecuteAsync` is a fixed-**delay** loop (delay after each run, including the first), not a fixed-rate loop, despite the `TaskDelayMilliseconds` XML doc describing it as an initial-run delay.
- This library has no in-repo consumer; it is referenced only by its own pipeline files and by the repo's architecture/legacy-repository documentation, so its composition pattern is otherwise unverified against a real consumer in this repository.
- There is no test project for this library.

## 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)
- [Service registration](service-registration.md)
- [API reference](api/index.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.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService.csproj`
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs`
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs`
- Architecture.md
