Skip to content

Console host bootstrap

This operation composes a generic .NET host for console applications — configuration precedence, console logging, and (for the SAIF variant) environment client/DB connectivity — then runs the hosted console service through its Initialize/Execute/Finish lifecycle.

Field Value
Operation ID OP-18
Trigger A console application calls BuildConsoleApp<TConsole>() or BuildSaifConsoleApp<TConsole>(), then runs the built host.
Inputs TConsole — the hosted console type (implements IHostedService, typically via ConsoleBase/ConsoleAsyncBase); command-line args.
Outputs A configured IHost whose IHostedService executes the console application's logic; the process exit code.
Primary components IHostBuilderExtensions.BuildConsoleApp, .BuildSaifConsoleApp; ConsoleBase; ConsoleAsyncBase
Entry point src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IHostBuilderExtensions.cs (L24–63)

Steps

  1. BuildConsoleApp: configuration sources added in order — appsettings.json (optional), environment variables, command line (if args supplied) — later sources override earlier ones (BR-SE-20) Register Singleton IHostedService → TConsole; configure logging with AddConfiguration("Logging") and AddConsole.
  2. BuildSaifConsoleApp additionally delegates to Register secure environment client via RegisterSecureEnvironmentClient(typeof(TConsole)) (BR-SE-03), delegates to Register DB connection client via RegisterDBConnectionClient() (BR-SE-04), and registers Singleton IConsoleLogService → ConsoleLogService.

Origin: ConsoleLogService's console-logging behavior is implemented in Configure console logger and Write console log entry (SAIF.NetCore.Logging); this step only registers the singleton type — construction and logging calls happen later, at first resolution and at each log call. 3. ConsoleAsyncBase's constructor captures StartTime = DateTime.Now and writes a header via IConsoleLogService; Dispose writes a footer and disposes the log service. - EndTime is a get-only property that returns DateTime.Now on every read rather than a timestamp captured once at run completion; RunningTime (EndTime - StartTime) is therefore the elapsed time since construction at the moment it is read, not a value frozen at a prior completion event. 4. ConsoleBase.StartAsync returns Task.Run(() => Run(), cancellationToken), scheduling Run on the thread pool with the cancellation token forwarded to Task.Run itself (not observed inside Run). Run calls Initialize → Execute → Finish sequentially with no try/finally, then sets Environment.ExitCode = ExitCode (BR-SE-21). ConsoleBase.Stop() is an empty no-op; it does not write the footer — footer logging and log-service disposal happen only via ConsoleAsyncBase.Dispose().

Process flow

This diagram shows host construction and the Run() lifecycle through assignment of Environment.ExitCode; it does not depict process termination, which is a separate host-lifetime concern outside this operation's scope.

flowchart TD
    Start([BuildConsoleApp / BuildSaifConsoleApp called]) --> S1[Add configuration sources in precedence order]
    S1 --> S2[Register IHostedService and console logging]
    S2 --> S3{BuildSaifConsoleApp variant?}
    S3 -->|Yes| S3a["Register secure environment client +<br>DB connection client + IConsoleLogService"]
    S3 -->|No| S4[Host built]
    S3a --> S4
    S4 --> S5[StartAsync runs Initialize then Execute then Finish]
    S5 --> S6["Environment.ExitCode = ExitCode<br>(host lifetime/process exit are separate)"]
    S6 --> EndOk([Run completes with exit status assigned])

Failure and edge-case behavior

  • ConsoleAsyncBase.EndTime recomputes DateTime.Now on every property read instead of returning a captured completion time; RunningTime read from WriteFooter() during Dispose() reflects the elapsed time up to that read, which is effectively the time of disposal, not a separately captured "run ended" instant.
  • Run()'s three steps are not wrapped in try/finally: if Initialize() or Execute() throws, Finish() does not run and Environment.ExitCode is never assigned from ExitCode — the exception instead propagates out through the Task returned by StartAsync. The final process exit code reflects ConsoleBase.ExitCode only when Run() completes all three steps without an unhandled exception.

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/IHostBuilderExtensions.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/ConsoleBase.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/ConsoleAsyncBase.cs