Skip to main content

ο»Ώ# Hosting

🌍 Language: English | Español

Generic host for Delphi applications inspired by .NET’s Microsoft.Extensions.Hosting. Composes services, configuration, hosted services (background workers) and the application lifetime into a single, managed runtime.

Delphi 12+ License


Why use this?

  • πŸ—οΈ Unified startup β€” One place for DI, configuration, logging and background services
  • 🌍 Environment-aware β€” Built-in Development / Staging / Production / Testing modes
  • βš™οΈ Layered configuration β€” Host config, then app config, with environment variable overrides
  • πŸ”„ Hosted services β€” Register IHostedService implementations that start and stop with the host
  • πŸ›‘ Graceful shutdown β€” IHostApplicationLifetime signals Started, Stopping, Stopped
  • πŸ”Œ DI integration β€” IServiceProvider is the backbone; all services registered via IServiceCollection

Quick Start

uses
  Daf.Hosting,
  Daf.Extensions.Hosting,
  Daf.Extensions.DependencyInjection;

var Builder := THost.CreateBuilder;

Builder.Services.AddSingleton<IMyService, TMyService>;
Builder.Services.AddHostedService<TMyWorker>; // IHostedService

var Host := Builder.Build;
try
  Host.Start;
  Host.WaitForShutdown;
finally
  StopAndNil(Host);
end;

IHostedService

Any class registered as a hosted service must implement IHostedService:

type
  TMyWorker = class(TInterfacedObject, IHostedService)
  public
    procedure Start;
    procedure Stop;
  end;

procedure TMyWorker.Start;
begin
  // launched when Host.Start is called
end;

procedure TMyWorker.Stop;
begin
  // called on graceful shutdown
end;

Register it:

Services.AddHostedService<TMyWorker>;
// equivalent to:
Services.AddSingleton<IHostedService, TMyWorker>;

If a Stop raises, the rest of the shutdown still runs: the other services stop and the container is shut down. Host.Stop raises what failed at the end β€” the exception itself, or an EAggregateException holding every failure when several steps failed.


Environment

The environment is read from the environment host setting β€” --environment Staging on the command line, or the DAF_ENVIRONMENT variable β€” and defaults to Production:

Value IsDevelopment IsStaging IsProduction IsTesting
Development βœ…
Staging βœ…
Production βœ…
Testing βœ…

The questions compare the name without regard to case. Any other name is one more environment: QA loads appsettings.QA.json, and the four questions answer False.

var Env := Host.Services.GetRequiredService<IHostEnvironment>;
if Env.IsDevelopment then
  WriteLn('Running in development mode');
WriteLn(Env.ContentRootPath);

Host settings read at startup, as an argument or as a variable β€” the argument wins:

Key Variable Default Purpose
environment DAF_ENVIRONMENT Production Environment name
applicationName DAF_APPLICATIONNAME Executable name Application name
contentRoot DAF_CONTENTROOT Current directory Content root directory

When the current directory is the system one, as for a Windows service, the content root is the folder of the executable instead. Env.BinPath is always that folder.


Container validation

In the Development environment the host builds the container with ValidateOnBuild and ValidateScopes: a registration whose dependencies cannot be satisfied, or a Singleton that captures a Scoped, stops Build with every finding listed. In any other environment the container is built without validating.

ServiceProviderOptions on the builder reads the options Build will use, and assigning it fixes them whatever the environment:

Builder.ServiceProviderOptions := TServiceProviderOptions.None;               // never validate
Builder.ServiceProviderOptions := TServiceProviderOptions.Create(True, True); // always validate

The validation reaches the registrations made by class; a factory registration is left out. With ValidateScopes, a Scoped service asked of the root provider, or through the provider a Singleton received, is also rejected when it is resolved. Hosted services and startup tasks are Singletons, so what they need from a Scoped they resolve from a scope of their own, through an IServiceScopeFactory taken by constructor.


Application Lifetime

var Lifetime := Host.Services.GetRequiredService<IHostApplicationLifetime>;

Lifetime.ApplicationStarted.Register(procedure begin
  WriteLn('App started');
end);

Lifetime.ApplicationStopping.Register(procedure begin
  WriteLn('Stopping...');
end);

// Request a graceful stop from anywhere
Lifetime.StopApplication;

Using the application family

For a program with a main line, each variant of the application family β€” Daf.Cli, Daf.Web, Daf.VCL or Daf.FMX β€” wraps this pipeline and adds Run. The builder is usable the moment it is created: Services, Configuration and Environment answer as soon as they are read, so registrations happen where they are written. Build returns the application, and its Run gives the exit code.

uses Daf.Cli, Daf.CommandLine, Daf.Configuration.CommandLine;

var B := TCliApplication.CreateBuilder(CommandLineConfig.ProcessArgs);

B.Services.AddSingleton<IMyService, TMyService>;
B.Services.AddHostedService<TMyWorker>;

ExitCode := B.Build.Run;

See The application family for which entry point is yours and what the layer adds over the host.


Documentation

  • πŸ“– Usage Guide β€” configuration pipeline, scoped services, hosted services, lifetime hooks
  • πŸ“– The application family β€” which entry point is yours, what the layer adds over the host, and what it is in .NET terms
  • πŸ“– Startup composition β€” IStartupTask vs IHostedService, and the two phases of startup
  • πŸ“ HostQuickStart β€” compose, build, start and stop, with no application variant around it
  • πŸ“ StartupReport β€” where the startup record goes, and what a startup that does not complete shows
  • πŸ“ HostLifetime β€” the three lifetime notifications, and a copy of the IHost released in ApplicationStopped
  • πŸ“ ConsoleSample β€” configuration and the options pattern in one console program
  • πŸ“ HostedService β€” background workers with AddHostedService
  • πŸ“ PackageInit β€” an IStartupTask contributed from a package, without the .dpr naming it

Abstractions

Hosting.Abstractions

🌍 Language: English | Español

Core contracts for the DAF hosting system. This module defines interfaces and types only β€” no implementation. Reference it when writing hosted services, environment-aware components, or generic host extensions without depending on the concrete host.

Delphi 12+ License


What’s in this module

All types live in Daf.Extensions.Hosting.

Core interfaces

Interface Role
IHostApplicationBuilder The shared contract of the application family: Services, Configuration, Environment, Logging, Properties, ServiceProviderOptions
IStartupTask Runs once at startup with the provider already built: Run, and nothing to stop
IHost The running host: Services, Start, Stop, WaitForShutdown
IHostedService Background service contract: Start / Stop
IHostEnvironment Runtime environment: name, app name, paths, IsDevelopment, …
IHostBuilderContext Startup context, registered in the container: Environment, Configuration, Properties
IHostApplicationLifetime Lifetime signals: ApplicationStarted, ApplicationStopping, ApplicationStopped, StopApplication

Environment constants

TEnvironments.Development   // 'Development'
TEnvironments.Staging       // 'Staging'
TEnvironments.Production    // 'Production'
TEnvironments.Testing       // 'Testing'

Host settings

The keys of the host configuration that IHostEnvironment is derived from, named as in .NET’s HostDefaults. As environment variables they carry the DAF_ prefix: DAF_ENVIRONMENT.

THostDefaults.EnvironmentKey   // 'environment'
THostDefaults.ApplicationKey   // 'applicationName'
THostDefaults.ContentRootKey   // 'contentRoot'

Helper

IServiceCollectionHelper adds AddHostedService<T> and AddStartupTask<T> directly on IServiceCollection:

Services.AddHostedService<TMyWorker>;   // = AddSingleton<IHostedService, TMyWorker>
Services.AddStartupTask<TMySeeder>;     // = AddSingleton<IStartupTask, TMySeeder>

Only one record helper per type is in scope in Delphi, so a unit bringing a second one for IServiceCollection hides this one. The Hosting class facade is the way through, and it takes the implementation as a metaclass: Hosting.AddHostedService(Services, TMyWorker).


Dependencies

  • Daf.Extensions.DependencyInjection (DependencyInjection.Abstractions)
  • Daf.Extensions.Configuration (Configuration.Abstractions)
  • Daf.Extensions.Logging (Logging.Abstractions) β€” for the ILoggingBuilder the family contract exposes as a property
  • Daf.Commons

The implementation is in Hosting.