I'm maintaining a integration package that allows my users to integrate my library with ASP.NET Core. This package must be compatible with all versions of ASP.NET Core starting at 2.1. At application shutdown my integration package must be able to execute asynchronous cleanup, and unfortunately can't take a dependency on IAsyncDisposable through Microsoft.Bcl.AsyncInterfaces (see below).
The only way this, therefore, seems feasible is by registering an IHostedService implementation. It's StopAsync method is called at shutdown:
public sealed class ShutdownHostedService : IHostedService
{
public MyLibaryCleanupObject Obj;
public Task StartAsync(CancellationToken token) => Task.CompletedTask;
public Task StopAsync(CancellationToken token) => this.Obj.CleanupAsync();
}
services.AddSingleton<IHostedService>(new ShutdownHostedService { Obj = ... });
Application developers, however, can of course add their own IHostedService implementations, which might interact with my library. This is why it is important for my own IHostedService implementation to be called last. But here lies the problem.
With the introduction of ASP.NET Core 2.1 application developers can choose between using the new Microsoft.Extensions.Hosting.Host and the (now deprecated) Microsoft.AspNetCore.WebHost. With WebHost, at shutdown, IHostedService implementations are called in order of registration, whereas with Host, IHostedService implementations are called in opposite order of registration.
This is problematic for me, because my hosted service should be called last. As application developers might use my integration package in their existing ASP.NET Core application, they might still use WebHost, which is why it is important to support that scenario.
Question: What would be a reliable way to determine in what 'mode' the ASP.NET Core application runs, so I can decide to add my hosted service first or last?
Alternatively, to prevent falling into the XY-problem trap, I'm open to completely different solutions that solve my problem of implementing "asynchronous shutdown".
Note on IAsyncDisposable:
One solution that would come to mind (as Ian rightfully notes in the comments) is to add an IAsyncDisposable Singleton registration to the ServiceCollection. This would allow asynchronous cleanup at shutdown. Unfortunately, due to constraints (explained here) it's impossible for my integration package to take a dependency on Microsoft.Bcl.AsyncInterfaces and, therefore, not on IAsyncDisposable. This is an unfortunate situation that certainly complicates the matter. As a matter of fact, the reason for not being able to take a dependency on IAsyncDisposable is the reason I'm looking for alternative ways of implementing asynchronous shutdown code.
The solution I end up using is identical to the solution chosen by Microsoft's
Microsoft.Extensions.Hosting.Internal.Hostclass and doesn't involve usingIHostedServiceinstances. TheHostclass contains the followingDisposemethod:This might raise some eyebrows and might be seen as a bad practice, but don't forget that:
This is why Microsoft's
Hostclass can take this approach, and it means that it is a safe approach for my library to do as well.