In this article, we will concentrate on how the Generic Host model hosts ASP.NET Core 3.x Web app and a Worker Service. We will first discuss the definition of a Host and its configuration. In the subsequent sections, we will dive into the implementation details from a higher level.
So what’s the deal with the Generic Host
With the separation of execution and initialisation, Generic Host provides us with a cleaner way to configure and start up our apps. By default, when you create an ASP.NET Core app now, your application will be hosted using the Generic Host model. If you create a new worker service app, it will be hosted the same way.
Not only that, but this model also provides you standardised configuration, DI, logging, and many more. You can even create a traditional console app, beef it up and make use of Generic Host.
💡 Follow along with the code from this repository
According to the official documentation, a Host is,
ASP.NET Core apps configure and launch a host. The host is responsible for app startup and lifetime management. At a minimum, the host configures a server and a request processing pipeline. The host can also set up logging, dependency injection, and configuration.
Let’s create a new .NET 3.1 WebAPI and a Worker Service project
dotnet new worker -n WorkerService
dotnet new sln
dotnet sln add WebApplication WorkerService
If you open up the solution in an IDE, you will see the following project structure.
They both have a Program.cs which takes care of setting up a host. In the case of the WebApplication project, it sets up a request processing pipeline defined in a Startup.cs and in the WorkerService project, sets a new hosted service which is an essentially an IHostedService.
In the WebApplication project, when you open up the Program.cs file, you will find the following boilerplate code has been added by the template:
public static IHostBuilder CreateHostBuilder(string args) =>
And, in the WorkerService project we have the following code:
public static IHostBuilder CreateHostBuilder(string args) =>
.ConfigureServices((hostContext, services) =>
Except for the ConfigureWebHostDefaults() and ConfigureServices(), everything else is the same.
If you look at the CreateHostBuilder method in the above code, it calls a CreateDefaultBuilder static method from Host coming from Microsoft.Extensions.Hosting namespace. It looks like that when we scaffold an ASP.NET Core app, it gives us a .NET Generic Host by default now. We used to have Web Host in ASP.NET Core 2.x, which was made deprecated since ASP.NET Core 3.0. For any future applications, it is recommended to use the .NET Generic Host.
This does a few things under the covers by wrapping,
Dependency Injection services
HTTP Server implementation (such as Kestrel)
In order to get an idea what the above methods do, I looked into the source code on Github.
We will start off with CreateDefaultBuilder method first.
// Initialize a new HostBuilder object
var builder = new HostBuilder();
// Specify the content root directory
// Host Configuration : Add environment variables starting with DOTNET_
// and add any command line args passed
builder.ConfigureHostConfiguration(config => ... );
// App Configuration : Add appsettings.json (depending on the env.) files
// and add user secrets if in development mode
builder.ConfigureAppConfiguration((hostingContext, config) => ... )
// Config logging
.ConfigureLogging((hostingContext, logging) => ... )
// Use default DI provider
.UseDefaultServiceProvider((context, options) => ... );
As you can see, it pretty much configures a HostBuilder object and returns it. There’s nothing really specific to web hosting in here. This is why it’s common to both HTTP and non-HTTP workloads.
Taking a step further, let’s look at how the web host gets configured. We will now look through ConfigureWebHostDefaults method.
return builder.ConfigureWebHost(webHostBuilder =>
Remember that ConfigureWebHostDefaults is used only for HTTP workloads and let’s see what we get as the default web host configuration.
// Configure static web assets if in Development mode
builder.ConfigureAppConfiguration((ctx, cb) => ... );
// Configure Kestrel
builder.UseKestrel((builderContext, options) => ... )
// Configure the default services
.ConfigureServices((hostingContext, services) => ... )
// Configure IIS for Windows
So far, we have seen that both approaches use the same Generic Host paradigm in the two projects. If you are interested in customising the default configuration, head over to Microsoft Docs’ official documentation.
Finally, how does it all run?
Now comes the interesting part.
In both cases, after the configuration sections, we finally call the Run() on IHost object implemented in HostingAbstractionsHostExtensions. This will run the app and block the calling thread until the host is shut down. This is enabled by WaitForShutdownAsync which is called at the beginning of the start-up process, which can be triggered by Ctrl+C/SIGTERM or SIGINIT.
Let’s look at how both web hosts and worker services run.
For a worker service, remember how we registered our Worker class by passing it into ConfigureServices method. This Worker class extends BackgroundService which in turn implements IHostedService. IHostedService provides 2 methods, namely, StartAsync and StopAsync. So when we run our host, it must be retrieving our Worker service and invoking these methods.
In the Host.cs there’s a separate StartAsync method and we can find the following lines inside it.
foreach (var hostedService in _hostedServices)
// Fire IHostedService.Start
So our guess was correct. It certainly invokes the StartAsync method of BackgroundService, that calls ExecuteAsync method in which we have ultimately implemented in our Worker class.
For a web host, there’s a little bit of abstraction on top of this before it hits the above section. A summary of how it reaches this as follows;
In Program.cs, configure a new webhost builder object in ConfigureWebHostDefaults
Register Startup class
GenericHostBuilderExtensions.ConfigureWebHostDefaults method gets called
GenericHostWebHostBuilderExtensions.ConfigureWebHost gets called
Register a GenericWebHostService service
var webhostBuilder = new GenericWebHostBuilder(builder);
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
So what is a GenericWebHostService ? It’s an IHostedService 🤩. Rest of the story is as above as we looked at in the worker service scenario. Because of this nicely decoupled initialisation we are able to run both ASP.NET Core and Worker services on the Generic Host.
To summarise, we looked at what makes the Generic Host generic and dug deeper into the implementation details in .NET Github repo. We also looked at what makes an ASP.NET Core web application and a worker service different, configuration-wise. This post became a bit longer than I initially I thought it would be 😅 Nevertheless, hope you picked up a thing or two.