.NET Generic Host

By Luke Latham

.NET apps configure and launch a host. The host is responsible for app startup and lifetime management. This topic covers the ASP.NET Core Generic Host (HostBuilder), which is useful for hosting apps that don't process HTTP requests. For coverage of the Web Host (WebHostBuilder), see the Web Host topic.

The goal of the Generic Host is to decouple the HTTP pipeline from the Web Host API to enable a wider array of host scenarios. Messaging, background tasks, and other non-HTTP workloads based on the Generic Host benefit from cross-cutting capabilities, such as configuration, dependency injection (DI), and logging.

The Generic Host is new in ASP.NET Core 2.1 and isn't suitable for web hosting scenarios. For web hosting scenarios, use the Web Host. The Generic Host is under development to replace the Web Host in a future release and act as the primary host API in both HTTP and non-HTTP scenarios.

View or download sample code (how to download)

When running the sample app in Visual Studio Code, use an external or integrated terminal. Don't run the sample in an internalConsole.

To set the console in Visual Studio Code:

  1. Open the .vscode/launch.json file.
  2. In the .NET Core Launch (console) configuration, locate the console entry. Set the value to either externalTerminal or integratedTerminal.

Introduction

The Generic Host library is available in the Microsoft.Extensions.Hosting namespace and provided by the Microsoft.Extensions.Hosting package. The Microsoft.Extensions.Hosting package is included in the Microsoft.AspNetCore.App metapackage (ASP.NET Core 2.1 or later).

IHostedService is the entry point to code execution. Each IHostedService implementation is executed in the order of service registration in ConfigureServices. StartAsync is called on each IHostedService when the host starts, and StopAsync is called in reverse registration order when the host shuts down gracefully.

Set up a host

IHostBuilder is the main component that libraries and apps use to initialize, build, and run the host:

public static async Task Main(string[] args)
{
    var host = new HostBuilder()
        .Build();

    await host.RunAsync();
}

Host configuration

HostBuilder relies on the following approaches to set the host configuration values:

  • Configuration builder
  • Extension method configuration

Configuration builder

Host builder configuration is created by calling ConfigureHostConfiguration on the IHostBuilder implementation. ConfigureHostConfiguration uses an IConfigurationBuilder to create an IConfiguration for the host. The configuration builder initializes the IHostingEnvironment for use in the app's build process.

Environment variable configuration isn't added by default. Call AddEnvironmentVariables on the host builder to configure the host from environment variables. AddEnvironmentVariables accepts an optional user-defined prefix. The sample app uses a prefix of PREFIX_. The prefix is removed when the environment variables are read. When the sample app's host is configured, the environment variable value for PREFIX_ENVIRONMENT becomes the host configuration value for the environment key.

During development when using Visual Studio or running an app with dotnet run, environment variables may be set in the Properties/launchSettings.json file. In Visual Studio Code, environment variables may be set in the .vscode/launch.json file during development. For more information, see Use multiple environments.

ConfigureHostConfiguration can be called multiple times with additive results. The host uses whichever option sets a value last.

hostsettings.json:

{
  "environment": "Development"
}

Example HostBuilder configuration using ConfigureHostConfiguration:

var host = new HostBuilder()
    .ConfigureHostConfiguration(configHost =>
    {
        configHost.SetBasePath(Directory.GetCurrentDirectory());
        configHost.AddJsonFile("hostsettings.json", optional: true);
        configHost.AddEnvironmentVariables(prefix: "PREFIX_");
        configHost.AddCommandLine(args);
    })

Note

The AddConfiguration extension method isn't currently capable of parsing a configuration section returned by GetSection (for example, .AddConfiguration(Configuration.GetSection("section")). The GetSection method filters the configuration keys to the section requested but leaves the section name on the keys (for example, section:environment). The AddConfiguration method expects the keys to match the HostBuilder keys (for example, environment). The presence of the section name on the keys prevents the section's values from configuring the host. This issue will be addressed in an upcoming release. For more information and workarounds, see Passing configuration section into WebHostBuilder.UseConfiguration uses full keys.

Extension method configuration

Extension methods are called on the IHostBuilder implementation to configure the content root and the environment.

Content Root

This setting determines where the host begins searching for content files.

Key: contentRoot
Type: string
Default: Defaults to the folder where the app assembly resides.
Set using: UseContentRoot
Environment variable: <PREFIX_>CONTENTROOT (<PREFIX_> is optional and user-defined)

If the path doesn't exist, the host fails to start.

var host = new HostBuilder()
    .UseContentRoot("c:\\<content-root>")

Environment

Sets the app's environment.

Key: environment
Type: string
Default: Production
Set using: UseEnvironment
Environment variable: <PREFIX_>ENVIRONMENT (<PREFIX_> is optional and user-defined)

The environment can be set to any value. Framework-defined values include Development, Staging, and Production. Values aren't case sensitive.

var host = new HostBuilder()
    .UseEnvironment(EnvironmentName.Development)

ConfigureAppConfiguration

App builder configuration is created by calling ConfigureAppConfiguration on the IHostBuilder implementation. ConfigureAppConfiguration uses an IConfigurationBuilder to create an IConfiguration for the app. ConfigureAppConfiguration can be called multiple times with additive results. The app uses whichever option sets a value last. The configuration created by ConfigureAppConfiguration is available at HostBuilderContext.Configuration for subsequent operations and in Services.

Example app configuration using ConfigureAppConfiguration:

var host = new HostBuilder()
    .ConfigureAppConfiguration((hostContext, configApp) =>
    {
        configHost.SetBasePath(Directory.GetCurrentDirectory());
        configApp.AddJsonFile("appsettings.json", optional: true);
        configApp.AddJsonFile(
            $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", 
            optional: true);
        configHost.AddEnvironmentVariables(prefix: "PREFIX_");
        configApp.AddCommandLine(args);
    })

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

appsettings.Production.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Note

The AddConfiguration extension method isn't currently capable of parsing a configuration section returned by GetSection (for example, .AddConfiguration(Configuration.GetSection("section")). The GetSection method filters the configuration keys to the section requested but leaves the section name on the keys (for example, section:Logging:LogLevel:Default). The AddConfiguration method expects an exact match to configuration keys (for example, Logging:LogLevel:Default). The presence of the section name on the keys prevents the section's values from configuring the app. This issue will be addressed in an upcoming release. For more information and workarounds, see Passing configuration section into WebHostBuilder.UseConfiguration uses full keys.

ConfigureServices

ConfigureServices adds services to the app's dependency injection container. ConfigureServices can be called multiple times with additive results.

A hosted service is a class with background task logic that implements the IHostedService interface. For more information, see the Background tasks with hosted services topic.

The sample app uses the AddHostedService extension method to add a service for lifetime events, LifetimeEventsHostedService, and a timed background task, TimedHostedService, to the app:

var host = new HostBuilder()
    .ConfigureServices((hostContext, services) =>
    {
        services.AddLogging();
        services.AddHostedService<LifetimeEventsHostedService>();
        services.AddHostedService<TimedHostedService>();
    })

ConfigureLogging

ConfigureLogging adds a delegate for configuring the provided ILoggingBuilder. ConfigureLogging may be called multiple times with additive results.

var host = new HostBuilder()
    .ConfigureLogging((hostContext, configLogging) =>
    {
        configLogging.AddConsole();
        configLogging.AddDebug();
    })

UseConsoleLifetime

UseConsoleLifetime listens for Ctrl+C/SIGINT or SIGTERM and calls StopApplication to start the shutdown process. UseConsoleLifetime unblocks extensions such as RunAsync and WaitForShutdownAsync. ConsoleLifetime is pre-registered as the default lifetime implementation. The last lifetime registered is used.

var host = new HostBuilder()
    .UseConsoleLifetime()

Container configuration

To support plugging in other containers, the host can accept an IServiceProviderFactory. Providing a factory isn't part of the DI container registration but is instead a host intrinsic used to create the concrete DI container. UseServiceProviderFactory(IServiceProviderFactory<TContainerBuilder>) overrides the default factory used to create the app's service provider.

Custom container configuration is managed by the ConfigureContainer method. ConfigureContainer provides a strongly-typed experience for configuring the container on top of the underlying host API. ConfigureContainer can be called multiple times with additive results.

Create a service container for the app:

namespace GenericHostSample
{
    internal class ServiceContainer
    {
    }
}

Provide a service container factory:

using System;
using Microsoft.Extensions.DependencyInjection;

namespace GenericHostSample
{
    internal class ServiceContainerFactory : IServiceProviderFactory<MyContainer>
    {
        public ServiceContainer CreateBuilder(IServiceCollection services)
        {
            return new ServiceContainer();
        }

        public IServiceProvider CreateServiceProvider(ServiceContainer containerBuilder)
        {
            throw new NotImplementedException();
        }
    }
}

Use the factory and configure the custom service container for the app:

var host = new HostBuilder()
    .UseServiceProviderFactory<ServiceContainer>(new SerivceContainerFactory())
    .ConfigureContainer<ServiceContainer>((hostContext, container) =>
    {
    })

Extensibility

Host extensibility is performed with extension methods on IHostBuilder. The following example shows how an extension method extends an IHostBuilder implementation with RabbitMQ. The extension method (elsewhere in the app) registers a RabbitMQ IHostedService:

// UseRabbitMq is an extension method that sets up RabbitMQ to handle incoming
// messages.
var host = new HostBuilder()
    .UseRabbitMq<MyMessageHandler>()
    .Build();

await host.StartAsync();

Manage the host

The IHost implementation is responsible for starting and stopping the IHostedService implementations that are registered in the service container.

Run

Run runs the app and blocks the calling thread until the host is shut down:

public class Program
{
    public void Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        host.Run();
    }
}

RunAsync

RunAsync runs the app and returns a Task that completes when the cancellation token or shutdown is triggered:

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        await host.RunAsync();
    }
}

RunConsoleAsync

RunConsoleAsync enables console support, builds and starts the host, and waits for Ctrl+C/SIGINT or SIGTERM to shut down.

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder();

        await hostBuilder.RunConsoleAsync();
    }
}

Start and StopAsync

Start starts the host synchronously.

StopAsync(TimeSpan) attempts to stop the host within the provided timeout.

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        using (host)
        {
            host.Start();

            await host.StopAsync(TimeSpan.FromSeconds(5));
        }
    }
}

StartAsync and StopAsync

StartAsync starts the app.

StopAsync stops the app.

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        using (host)
        {
            await host.StartAsync();

            await host.StopAsync();
        }
    }
}

WaitForShutdown

WaitForShutdown is triggered via the IHostLifetime, such as ConsoleLifetime (listens for Ctrl+C/SIGINT or SIGTERM). WaitForShutdown calls StopAsync.

public class Program
{
    public void Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        using (host)
        {
            host.Start();

            host.WaitForShutdown();
        }
    }
}

WaitForShutdownAsync

WaitForShutdownAsync returns a Task that completes when shutdown is triggered via the given token and calls StopAsync.

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = new HostBuilder()
            .Build();

        using (host)
        {
            await host.StartAsync();

            await host.WaitForShutdownAsync();
        }

    }
}

External control

External control of the host can be achieved using methods that can be called externally:

public class Program
{
    private IHost _host;

    public Program()
    {
        _host = new HostBuilder()
            .Build();
    }

    public async Task StartAsync()
    {
        _host.StartAsync();
    }

    public async Task StopAsync()
    {
        using (_host)
        {
            await _host.StopAsync(TimeSpan.FromSeconds(5));
        }
    }
}

IHostLifetime.WaitForStartAsync is called at the start of StartAsync, which waits until it's complete before continuing. This can be used to delay startup until signaled by an external event.

IHostingEnvironment interface

IHostingEnvironment provides information about the app's hosting environment. Use constructor injection to obtain the IHostingEnvironment in order to use its properties and extension methods:

public class MyClass
{
    private readonly IHostingEnvironment _env;

    public MyClass(IHostingEnvironment env)
    {
        _env = env;
    }

    public void DoSomething()
    {
        var environmentName = _env.EnvironmentName;
    }
}

For more information, see Use multiple environments.

IApplicationLifetime interface

IApplicationLifetime allows for post-startup and shutdown activities, including graceful shutdown requests. Three properties on the interface are cancellation tokens used to register Action methods that define startup and shutdown events.

Cancellation Token Triggered when…
ApplicationStarted The host has fully started.
ApplicationStopped The host is completing a graceful shutdown. All requests should be processed. Shutdown blocks until this event completes.
ApplicationStopping The host is performing a graceful shutdown. Requests may still be processing. Shutdown blocks until this event completes.

Constructor-inject the IApplicationLifetime service into any class. The sample app uses contructor injection into a LifetimeEventsHostedService class (an IHostedService implementation) to register the events.

LifetimeEventsHostedService.cs:

internal class LifetimeEventsHostedService : IHostedService
{
    private readonly ILogger _logger;
    private readonly IApplicationLifetime _appLifetime;

    public LifetimeEventsHostedService(
        ILogger<LifetimeEventsHostedService> logger, IApplicationLifetime appLifetime)
    {
        _logger = logger;
        _appLifetime = appLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _appLifetime.ApplicationStarted.Register(OnStarted);
        _appLifetime.ApplicationStopping.Register(OnStopping);
        _appLifetime.ApplicationStopped.Register(OnStopped);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    private void OnStarted()
    {
        _logger.LogInformation("OnStarted has been called.");

        // Perform post-startup activities here
    }

    private void OnStopping()
    {
        _logger.LogInformation("OnStopping has been called.");

        // Perform on-stopping activities here
    }

    private void OnStopped()
    {
        _logger.LogInformation("OnStopped has been called.");

        // Perform post-stopped activities here
    }
}

StopApplication requests termination of the app. The following class uses StopApplication to gracefully shut down an app when the class's Shutdown method is called:

public class MyClass
{
    private readonly IApplicationLifetime _appLifetime;

    public MyClass(IApplicationLifetime appLifetime)
    {
        _appLifetime = appLifetime;
    }

    public void Shutdown()
    {
        _appLifetime.StopApplication();
    }
}

Additional resources