Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 20 – Command-line apps, hosting, DI, configuration and telemetry

Generic Host and dependency injection

Compose a C# console app on the .NET Generic Host: register services for dependency injection, choose their lifetimes and catch lifetime mistakes at start-up.

  • Intermediate
  • 15 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free intro, Pro: 4 of 15 minutes free
Free: The host builds every object your app needs. With a pass: the other 6 sections and 3 interview answers, about 11 minutes.

What you will learn

  • Compose a console app on the Generic Host with Host.CreateApplicationBuilder, and start and stop it in order
  • Choose singleton, scoped or transient for a service, and create a scope for each unit of work
  • Detect a captive dependency with ValidateScopes and ValidateOnBuild before the app handles its first message
  • Resolve one of several implementations by key, or all of them as IEnumerable<T>, through constructor injection

Before you start

What you’ll learn · Before you start

What you will learn

  • Compose a console app on the Generic Host with Host.CreateApplicationBuilder, and start and stop it in order
  • Choose singleton, scoped or transient for a service, and create a scope for each unit of work
  • Detect a captive dependency with ValidateScopes and ValidateOnBuild before the app handles its first message
  • Resolve one of several implementations by key, or all of them as IEnumerable<T>, through constructor injection

Before you start

On this page

A class that makes its own dependencies with new decides in code which clock, store and sender it uses, so a test cannot replace them and every change to them reaches into the class. The .NET Generic Host: The host of Microsoft.Extensions.Hosting: one object that owns an app's services, configuration, logging and lifetime, and starts and stops its hosted services. Host.CreateApplicationBuilder sets one up. turns that around: in this lesson’s first program, one call gives a console app configuration, console logging, an orderly start and stop, and a service container that builds every object by dependency injection: Giving an object the objects it depends on, usually as constructor parameters, instead of letting it create them. In .NET a service container reads the registrations and builds each object with its dependencies..

You will compose an app on the Generic Host, give each service the lifetime it needs, catch a lifetime mistake before the app handles its first message, and choose between implementations by key.

The host builds every object your app needs

The Generic Host, from the Microsoft.Extensions.Hosting package, is one object that owns an app’s services, its configuration, its logging and its lifetime. Dependency injection is how it builds the services: a class lists what it needs as constructor parameters, here with a primary constructor, and the container passes them in. The program greeting.cs registers two services, a greeter and a hosted service: A class that implements IHostedService, often by deriving from BackgroundService, registered with AddHostedService. The host starts it after the app is built and stops it when the app shuts down. that the host starts and stops:

greeting/greeting.cs C# · greeting/greeting.cs
#:package Microsoft.Extensions.Hosting@10.0.12
#:property PublishAot=false
// A console app on the Generic Host: one call sets up configuration, logging and a service container; the greeting
// service gets what it needs through its constructor. Everything is written through the logger, so lines keep their order.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IGreeter, Greeter>();
builder.Services.AddHostedService<GreetingService>();

using (IHost host = builder.Build())
{
    await host.StartAsync();   // starts every hosted service, then reports that the app has started
    await host.StopAsync();    // what Run does after Ctrl+C or SIGTERM: stops the services in reverse order
}                              // disposing the host disposes its services and flushes the console logger
Console.WriteLine("host stopped and disposed");

interface IGreeter { string Greet(string city); }

sealed class Greeter : IGreeter
{
    public string Greet(string city) => $"Namaste, {city}!";
}

sealed class GreetingService(IGreeter greeter, IHostEnvironment environment, ILogger<GreetingService> logger) : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("{Greeting} (environment {Environment})", greeter.Greet("Pune"), environment.EnvironmentName);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Greeting service stopped");
        return Task.CompletedTask;
    }
}
The greeting app, in Production and in Development Python · greeting_session.py
from session import run

# network: true. greeting.cs names Microsoft.Extensions.Hosting 10.0.12 in its #:package line, and dotnet run
# restores it, with the packages it depends on, from nuget.org into the session's own, empty home folder. The second
# run sets DOTNET_ENVIRONMENT, the variable the host reads for the environment's name.
run("""
cd greeting
dotnet run greeting.cs
DOTNET_ENVIRONMENT=Development dotnet run greeting.cs
""", network=True)

Output

$ cd greeting
$ dotnet run greeting.cs
info: GreetingService[0]
      Namaste, Pune! (environment Production)
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: greeting
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: GreetingService[0]
      Greeting service stopped
host stopped and disposed
$ DOTNET_ENVIRONMENT=Development dotnet run greeting.cs
info: GreetingService[0]
      Namaste, Pune! (environment Development)
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: greeting
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: GreetingService[0]
      Greeting service stopped
host stopped and disposed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 greeting_session.py

The class GreetingService never created an IGreeter, an environment or a logger: it asked for them and got them. Because every dependency arrives through the constructor, a unit test can create the service with a fake greeter and no host at all, and a change of greeter never touches the class. The container also calls Dispose on the disposable objects it created, when the host is disposed, so a service never disposes what it was given. The host reported Production because nothing set DOTNET_ENVIRONMENT, and the same program reported Development when the variable said so. Run does what this program did by hand: it calls StartAsync, waits for Ctrl+C or SIGTERM, calls StopAsync and disposes the host, as the using block did here. Step through the run:

Interactive · stage by stage The life of a host, from builder to disposal Step through the greeting run, from the first call to the last line it printed.
The life of a host, from builder to disposal, step 7 of 7. Showing CreateApplicationBuilder, Register services, Build, StartAsync, Running, StopAsync, Dispose. In focus: Dispose.Ctrl+C or SIGTERMCreateApplicationBuilderconfiguration and loggingRegister servicesbuilder.ServicesBuildthe container is madeStartAsynchosted services startRunninguntil a stop is askedStopAsyncservices stop in reverseDisposeobjects and logs released

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. The builder. Host.CreateApplicationBuilder(args) sets up configuration from files, environment variables and the command line, console logging, and the environment name, which is Production unless DOTNET_ENVIRONMENT says otherwise.
  2. Two registrations. The program adds two entries to builder.Services, IGreeter with its class Greeter, and GreetingService as a hosted service, which the host starts and stops for you.
  3. The container. builder.Build() turns the registrations into a container. Nothing has been created yet; each object is made when something first asks for it.
  4. The hosted service starts. StartAsync needs a GreetingService. Its constructor asks for an IGreeter, the environment and a logger, and the container supplies all three. The service logs Namaste, Pune! with the environment.
  5. Started. With every hosted service started, the host logs Application started, the environment and the content root. A program that calls Run would now wait for Ctrl+C or SIGTERM; this one asks for the stop at once.
  6. The stop. StopAsync, which Run calls after a stop signal, logs that the application is shutting down and stops the hosted services in reverse order, so the greeting service logs that it stopped.
  7. Disposal. Disposing the host disposes the objects the container made and flushes the console logger. Only then does the program print its last line.

Every registration also says how long its object lives. A singleton is created once and shared by the whole app. A scoped service is created once per scope, such as one message, and disposed when the scope ends; code opens a scope with IServiceScopeFactory.CreateScope(). A transient service is created every time something asks for it. A captive dependency is a long-lived service that holds a shorter-lived one, and the host’s ValidateScopes and ValidateOnBuild checks report it when the host is built. Keyed services register several implementations of one interface under keys such as sms and email, and a constructor asks for one with [FromKeyedServices]. A parameter of type IEnumerable<T> receives every registration of T. An open generic registration serves every closed type of a generic interface, and a factory registration builds an object in code when a constructor is not enough.

Checkpoint

Check: The Generic Host and dependency injection

  1. Question 1 of 2 In greeting.cs, what creates the IGreeter that GreetingService uses?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The container, when it builds GreetingService and sees an IGreeter parameter in its constructor

    Dependency injection means a class declares what it needs and the container provides it. When the host started GreetingService, the container saw three constructor parameters and supplied a Greeter, the environment and a logger.

  2. Question 2 of 2 greeting.cs calls StartAsync and then StopAsync inside a using block. What does host.Run() do instead?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It calls StartAsync, waits for Ctrl+C or SIGTERM, calls StopAsync and disposes the host

    A worker or a console app that should keep running calls Run. It starts the hosted services, waits for Ctrl+C, SIGTERM or StopApplication, stops them in reverse order and disposes the host, so the recorded lines would be the same, with the wait in between.

Three lifetimes decide when objects are made and disposed Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • A captive dependency passes silently until validation refuses it
  • A key picks one implementation, a list takes them all
  • Practise: register a notification app
  • Interview questions
    • A message consumer is registered as a singleton and takes the app's data context, which is registered as scoped. In production, changes from one message sometimes appear in the next, and threading errors show up under load. What is happening, how do you fix it, and how do you keep it from coming back?
    • A checkout pays each order by card, bank transfer or wallet, chosen at run time from the order, and new payment methods are added every few months. How would you register and resolve the payment services, and what should happen when an order names a method that is not registered?
    • A reporting class takes IServiceProvider in its constructor and calls GetRequiredService in five methods. Tests need a full container, and a missing registration showed up only when a rarely used report ran in production. How would you refactor it, and which uses of IServiceProvider would you keep?
  • Key takeaways

1 diagram · 8 runnable examples (C#, Python) · 3 interview answers · about 27 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

captive dependency
A service with a long lifetime that holds one with a shorter lifetime, such as a singleton that takes a scoped service, so the shorter-lived object lives far longer than it should.
dependency injection
Giving an object the objects it depends on, usually as constructor parameters, instead of letting it create them. In .NET a service container reads the registrations and builds each object with its dependencies.
Generic Host
The host of Microsoft.Extensions.Hosting: one object that owns an app's services, configuration, logging and lifetime, and starts and stops its hosted services. Host.CreateApplicationBuilder sets one up.
hosted service
A class that implements IHostedService, often by deriving from BackgroundService, registered with AddHostedService. The host starts it after the app is built and stops it when the app shuts down.
keyed service
A registration with a key, such as "sms" or "email", so one interface can have several implementations that code asks for by key, with [FromKeyedServices] or GetRequiredKeyedService.
service lifetime
How long the container keeps a registered service: singleton, one for the whole app; scoped, one per scope; or transient, a new one every time it is asked for.
service scope
An IServiceScope, made with IServiceScopeFactory.CreateScope for one unit of work such as a message or a request. Scoped services are made once inside it and disposed when it is disposed.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.