Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Feature request: Targeted lifetime events through DI container #36382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Antaris opened this issue Sep 7, 2018 · 12 comments
Closed

Feature request: Targeted lifetime events through DI container #36382

Antaris opened this issue Sep 7, 2018 · 12 comments
Labels

Comments

@Antaris
Copy link

Antaris commented Sep 7, 2018

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

@davidfowl
Copy link
Member

The best thing to do might be to provide a new interface that gives control over each of the relevant pieces:

  • Before hosted services start
  • After hosted services start
  • Before hosted services stop
  • After hosted services stop

This interface would probably be a superset of IApplicationLifetime.

@Antaris
Copy link
Author

Antaris commented Sep 10, 2018

Would this be an interface I can provide an implementation (or multiple) through the DI container, maybe something like:

interface IApplicationLifetimeEventsHandler 
{
    Task OnApplicationStart();
    Task OnApplicationStopping();
    Task OnApplicationStopped();
    
    Task OnServicesStarting();
    Task OnServicesStarted();
    Task OnServicesStopping();
    Task OnServicesStopped();
}

If that is the case and we can provide implementations, that would be good. Although I would question having to implement a type where I want to intercept just a single lifetime event, and have to provide default implementations of the others:

class MyLifetimeEventsHandler : IApplicationLifetimeEventsHandler
{
    public Task OnApplicationStart() => Task.CompletedTask;
    public Task OnApplicationStopping() => Task.CompletedTask;
    public Task OnApplicationStopped() => Task.CompletedTask;
    
    public async Task OnServicesStarting()
    {
      // Do some work here
    }
    
    public Task OnServicesStarted() => Task.CompletedTask;
    public Task OnServicesStopping() => Task.CompletedTask;
    public Task OnServicesStopped() => Task.CompletedTask;
}

Would individual interfaces work better, as they are more focused:

interface IHostedServicesStartedEventHandler
{

}

That would obviously expand the number of resolves you'd have to do, too many moving parts? Default interface methods would work a charm here ;-)

Is WebHost being refactored to use Host for the 3.x timeframe? I think normalizing when IApplicationLifetime.NotifyStarted() is called - but then you're already looking into changes here, as part of dotnet/extensions#1544

Alternatively, could we perhaps extend event registration through to the builder?:

new HostBuilder()
  .WithEvents(
    onServicesStarted: async () => {
    
    }
  )
  .Build();

That is largely superficial, the real power could be allowing the builder to collect a set of Action (much like here), meaning by extending the builder with custom extension methods, you can mix in more functionality, but simplifying the API the consumer uses?

E.g. in my design:

new HostBuilder()
  .UseDiscoveredModules() // Registers lifetime actions
  .Build();

@linvi
Copy link

linvi commented Sep 12, 2018

I am trying to find an event that notify that and aspnet core application is now ready to server http requests.

I tried to use the IApplicationLifetime.ApplicationStarted but the event is raised before the application is ready to handle requests.

At the moment is there any way to perform that (hack or not)?

@Tratcher
Copy link
Member

@linvi ApplicationStarted is raised after the server can accept HTTP requests.
https://github.com/aspnet/Hosting/blob/c37c50964d1451c22d085803ec54ec697c7fb81a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs#L147-L153

Are you also dependent on the hosted services for request processing?

@linvi
Copy link

linvi commented Sep 14, 2018

Yes I am.

@davidfowl
Copy link
Member

I've been thinking about this a bit and I think the model I'd prefer is more optional interfaces that an IHostedService may implement to get notified about more events. These wouldn't be recognized through the DI container, the host would opportunistically cast the IHostedService to one of these lifetime event interfaces to fire the events.

@Antaris
Copy link
Author

Antaris commented Nov 11, 2018

Does this cover scenarios where I want to reason about stuff before any hosted services (any host) and the middleware pipeline (web host) are configured?

On 3.0, the work to configure the middleware pipeline for a webhost, when does this occur?

@davidfowl
Copy link
Member

We're not changing the WebHostBuilder in 3.0 so but adding a generic host compatible IHostedService that will boot the server (see aspnet/Hosting#1580). IStartupFilter is still the way to run code before the server starts but if you register hosted services before the call to ConfigureWebHost, you'll run first.

@aspnet-hello aspnet-hello transferred this issue from aspnet/Hosting Dec 18, 2018
@simkin2004
Copy link

simkin2004 commented Dec 26, 2019

When the design is created, please make sure to account for the race condition when Host calls _applicationLifetime.?.NotifyAsync() and both an IHostApplicationLifetime and an IHostedService have registered with IHostApplicationLifetime.ApplicationStarted. The IHostedService registration gets called before the IHostApplicationLifetime registration resulting in IHostedService's logging happening before the "Application has started" logging from ConsoleLifetime.

@simkin2004
Copy link

simkin2004 commented Dec 26, 2019

I incorrectly identified the issue as a race condition.
When reviewing the logging output, the IHostApplicationLifetime.NotifyAsync event is being fired in the reverse order of when the delegates were registered. This behavior causes the IHostServices (children) to be aware that the Application has started before the IHostApplicationLifetime (parent's lifetime) is aware.

public class SampleHostService : IHostedService
{
    private readonly ILogger<SampleHostService> _logger;
    private readonly IHostApplicationLifetime _hostLifetime;
    public SampleHostService(ILogger<SampleHostService> logger, IHostApplicationLifetime hostLifetime)
    {
        _hostLifetime = hostLifetime;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Start SampleServiceHost.");
        _hostLifetime.ApplicationStarted.Register(this.OnApplicationStarted);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stop SampleServiceHost.");
        return Task.CompletedTask;
    }

    private void OnApplicationStarted()
    {
        _logger.LogInformation("Do some work here and immediately stop.");
        _hostLifetime.StopApplication();
    }
}

This code results in the following log entries:

info: ConsoleApp1.SampleHostService[0]
Start SampleServiceHost.
info: ConsoleApp1.SampleHostService[0]
Do some work here and immediately stop.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
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: C:\ConsoleApp1\bin\Debug\netcoreapp3.0
info: ConsoleApp1.SampleHostService[0]
Stop SampleServiceHost.

@analogrelay analogrelay transferred this issue from dotnet/extensions May 13, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-Extensions-Hosting untriaged New issue has not been triaged by the area owner labels May 13, 2020
@analogrelay analogrelay added this to the Future milestone May 13, 2020
@ericstj ericstj removed the untriaged New issue has not been triaged by the area owner label Jun 22, 2020
Copy link
Contributor

Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process.

This process is part of our issue cleanup automation.

@dotnet-policy-service dotnet-policy-service bot added backlog-cleanup-candidate An inactive issue that has been marked for automated closure. no-recent-activity labels Apr 9, 2025
Copy link
Contributor

This issue will now be closed since it had been marked no-recent-activity but received no further activity in the past 14 days. It is still possible to reopen or comment on the issue, but please note that the issue will be locked if it remains inactive for another 30 days.

@dotnet-policy-service dotnet-policy-service bot removed this from the Future milestone Apr 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants