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
IServiceTaskRunnerrepeatedly 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 theIHostBuilder, applying the optionalDelayStartstartup delay, and invoking theConfigureErrorHandler/ConfigureLoggerhooks 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 aMicrosoft.Extensions.Hosting.BackgroundServicesubclass.- 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 inSAIF.NetCore.ServiceContracts, not in this library. - This library ships no DI registration extension method. The companion registration,
RegisterForWindowsService/RegisterForWindowsService<T>, is owned entirely bySAIF.NetCore.ServiceExtensions, and this library does not reference that package.
Interaction model¶
- A consumer writes
class Program : ServiceProgramRunnerBase, overridesCreateHostBuilderto callbase.CreateHostBuilder(args).ConfigureServiceswith a lambda that registers itsServiceWorker<T>subclass withAddHostedService<>and callsSAIF.NetCore.ServiceExtensions.RegisterForWindowsService, then callsnew Program().Run(args)fromMain*. Runbuilds the host once (Create), applies the optionalDelayStartdelay, invokesConfigure(which calls the consumer'sConfigureErrorHandlerhook and thenConfigureLogger), and finally blocks onServiceHost.Run().ConfigureLoggerresolvesISaifWindowsServiceLogService,IEnvironmentClient, andIHttpJsonClientfrom the container withGetRequiredServiceand callslogger.Configure(envClient, httpClient); this library does not implement any of those three services itself.ServiceWorker<T>.ExecuteAsyncrunsT.Run(stoppingToken), then waitsTaskDelayMilliseconds, repeating until cancellation; any exception from the runner is caught, reported viaIWindowsServiceErrorHandler.HandleError, and — unless shutdown-exempt — triggersServiceHost.StopAsync(ShutdownTimeoutSeconds)through the staticServiceProgramRunnerBase.ServiceHostreference.IWindowsServiceErrorHandler,ISaifWindowsServiceLogService,IEnvironmentClient, andIHttpJsonClientare all resolved from the DI container; none are referenced as compile-time package dependencies of this library — in practice a consumer satisfies them by referencingSAIF.NetCore.ServiceExtensionsand callingRegisterForWindowsService, 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.
flowchart TD
SC["SAIF.NetCore.ServiceContracts<br>(IServiceTaskRunner, IWindowsServiceErrorHandler,<br>ISaifWindowsServiceLogService)"]
WS["SAIF.NetCore.WindowsService<br>ServiceProgramRunnerBase / ServiceWorker<T>"]
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.
sequenceDiagram
participant Program as "Program (consumer)"
participant Runner as ServiceProgramRunnerBase
participant Worker as "ServiceWorker<T>"
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.ServiceHostis a static, process-global reference; only one host per process is supported, and aServiceWorker<T>hosted by anything other thanServiceProgramRunnerBasewill throw aNullReferenceExceptionon its first non-exempt error*.CreateHostBuilderappendsAddJsonFile("appsettings.json", optional: true)andAddEnvironmentVariables()after the sourcesHost.CreateDefaultBuilderalready registers, which reverses the expected configuration-source precedence (later sources win), soappsettings.jsoncan override environment-specific settings and command-line arguments*.DelayStartupblocks the main thread synchronously (Thread.Sleep) before the host starts; aDelayStartvalue longer than the Windows SCM start timeout would likely make the service fail to start*.ServiceWorker<T>.ExecuteAsyncis a fixed-delay loop (delay after each run, including the first), not a fixed-rate loop, despite theTaskDelayMillisecondsXML 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
- Overview
- Dependencies
- Configuration
- Operations and processes
- Business rules
- Service registration
- API reference
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.csprojsrc/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cssrc/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs- Architecture.md