This is the fourth post in the series: Exploring ASP.NET Core 3.0 .

In this post I describe how a small change in the ASP.NET Core 3.0 WebHost makes it easier to run asynchronous tasks on app startup using IHostedService .

Running asynchronous tasks on app startup.

In a previous series I showed various ways you could run asynchronous tasks on app startup. There are many reasons you might want to do this - running database migrations, validating strongly-typed configuration, or populating a cache, for example.

Unfortunately, in 2.x it wasn't possible to use any of the built-in ASP.NET Core primitives to achieve this:

IStartupFilter has a synchronous API, so would require doing sync over async.

has a synchronous API, so would require doing sync over async. IApplicationLifetime has a synchronous API and raises the ApplicationStarted event after the server starts handling requests.

has a synchronous API and raises the event after the server starts handling requests. IHostedService has an asynchronous API, but is executed after the server is started and starts handling requests.

Instead, I proposed two possible solutions:

With ASP.NET Core 3.0, a small change in the WebHost code makes a big difference - we no longer need these solutions, and can use IHostedService without the previous concerns!

A small change makes all the difference

In ASP.NET Core 2.x you can run background services by implementing IHostedService . These are started shortly after the app starts handing requests (i.e. after the Kestrel web server is started), and are stopped when the app shuts down.

In ASP.NET Core 3.0 IHostedService still serves the same purpose - running background tasks. But thanks to a small change in WebHost you can now also use it for automatically running async tasks on app startup.

The change in question is these lines from the WebHost in ASP.NET Core 2.x:

public class WebHost { public virtual async Task StartAsync ( CancellationToken cancellationToken = default ) { await Server . StartAsync ( hostingApp , cancellationToken ) . ConfigureAwait ( false ) ; _applicationLifetime ? . NotifyStarted ( ) ; await _hostedServiceExecutor . StartAsync ( cancellationToken ) . ConfigureAwait ( false ) ; } }

In ASP.NET Core 3.0, these have been changed to this:

public class WebHost { public virtual async Task StartAsync ( CancellationToken cancellationToken = default ) { await _hostedServiceExecutor . StartAsync ( cancellationToken ) . ConfigureAwait ( false ) ; await Server . StartAsync ( hostingApp , cancellationToken ) . ConfigureAwait ( false ) ; _applicationLifetime ? . NotifyStarted ( ) ; } }

As you can see, IHostedService.Start is now executed before Server.StartAsync . This change means you can now use IHostedService to run async tasks.

This assumes that you want to delay your app handling requests until after the async task has completed. If that's not the case, you may want to use the Health Check approach from the last post in my series.

Using an IHostedService to run async tasks on app startup

Implementing an IHostedService as an "app startup" task is not difficult. The interface consists of just two methods:

public interface IHostedService { Task StartAsync ( CancellationToken cancellationToken ) ; Task StopAsync ( CancellationToken cancellationToken ) ; }

Any code you want to be run just before receiving requests should be placed in the StartAsync method. The StopAsync method can be ignored for this use case.

For example, the following startup task runs EF Core migrations asynchronously on app startup:

public class MigratorHostedService : IHostedService { private readonly IServiceProvider _serviceProvider ; public MigratorHostedService ( IServiceProvider serviceProvider ) { _serviceProvider = serviceProvider ; } public async Task StartAsync ( CancellationToken cancellationToken ) { using ( var scope = _serviceProvider . CreateScope ( ) ) { var myDbContext = scope . ServiceProvider . GetRequiredService < MyDbContext > ( ) ; await myDbContext . Database . MigrateAsync ( ) ; } } public Task StopAsync ( CancellationToken cancellationToken ) = > Task . CompletedTask ; }

To add the task to the dependency injection container, and have it run just before your app starts receiving requests, use the AddHostedService<> extension method:

public class Startup { public void ConfigureServices ( IServiceCollection services ) { services . AddHostedService < MigratorHostedService > ( ) ; } public void Configure ( IApplicationBuilder ) { } }

The services will be executed at startup in the same order they are added to the DI container, i.e. services added later in ConfigureServices will be executed later on startup.

Summary