Skip to content

Feature request: Targeted lifetime events through DI container #36382

@Antaris

Description

@Antaris

Hi,

In the generic host, we consume a number of IHostedServices, and generally the pattern for handling lifetime events, is to hook into IApplicationLifetime.

A challenge with this approach, is where I need to set up state, or reason about configuration before hosted services are started. I can't get access to application lifetime without wrapping up access in a hosted service, which doesn't feel like it is the right design. Unless I am missing a trick?

I'm currently building on a design whereby I have a number of modules that need to be initialised, or features that do the same. So, in a simple term:

interface IModule
{
  Task InitAsync();
}

Now, I provide modules to the host (be it a WebHost or generic Host) through extension methods to the builder, e.g.

new HostBuilder()
  .UseDiscoveredModules()
  .RunConsoleAsync();

Or as a web host:

new WebHost.CreateDefaultBuilder()
  .UseDiscoveredModules()
  .UeStartup<Startup>()
  .Build()
  .Run();

Currently, in my webhost implementation, I initialise modules using an IStartupFilter that guarantees that this is done before any requests are processed.

It's not ideal as it feels like I'm fudging this initialisation around the creation of the middleware pipeline, but it works as an implementation detail

https://github.com/aspnet/Hosting/blob/master/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs#L140

Ideally, I don't want to implement an IHostedService to initialise these modules or features because there may be a scenario where hosted services need pre-conditions setup as part of module initialisation, and I can't rely on the order in which hosted services would be registered with the DI container.

On the Generic host side of the fence, I would have to hook into an IHostedService, because there is no comparable startup filter design (because there is no concept of startup and middleware). In terms of IApplicationLifetime itself, they are inconsistently intialised in WebHost vs Host:

https://github.com/aspnet/Hosting/blob/master/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs#L140
and https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting/Internal/Host.cs#L51

In WebHost the application lifetime is NotifyStarted() before hosted services are intialised, but NotifyStarted() is called after hosted services are initialised in Host (is there a reason for this, or an oversight?).

I feel there is potentially room for a specialised lifetime event type (or types) that can be registered with DI, for instance:

interface ILifetimeEvents
{
  Task OnStartAsync();
  Task OnStopAsync();
}

Or potentially:

interface IStartLifetimeEventHandler
{
  Task OnStartAsync();
}

interface IStopLifetimeEventHandler
{
  Task OnStopAsync();
}

Now, on an initial pass, these look very much like the implementation of IHostedService, but specifically they can be used to intercept code points before and after hosted services (or in the case of the WebHost, the middleware pipeline) are shut down.

So broadly the deisgn should:

  1. Create lifetime event handler types from the DI container
  2. Execute the OnStartAsync() method of the lifetime event handler
  3. Start hosted services
  4. Notify the IApplicationLifetime has started

And in shutdown,

  1. Notify the IApplicationLifetime is stopping
  2. Stop hosted services
  3. Execute OnStopAsync() method of lifetime event handler
  4. Notify the IApplicationLifetime is stopped

Or I guess this could all be squashed with an answer to ths question:

For the Generic Host, how can I hook into the startup of the host before the hosted services are initialised?

If there is any merit to anything here, I am happy to try a PR

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions