Reputation: 4987
I'm trying to execute logic on start up of my web API and this code will also be async.
I've thought of IStartupFilter
but this is sync, which is a problem. StartUp.Configure()
is also not an option, sync.
I've thought of IHostedService
but this runs before the application is done loading and up for requests.
Any other ways of doing this ?
Upvotes: 2
Views: 2223
Reputation: 5522
Implement IStartupTask
, register StartupTaskRunner
and YourStartupTask
in DI:
services
.AddStartupTasksRunner()
.AddStartupTask<YourStartupTask>();
And here the code based on Andrew's Lock post:
public interface IAsyncStartupTask
{
Task OnStartupAsync(CancellationToken cancellationToken);
}
internal class StartupTasksRunner : IHostedService
{
private readonly IEnumerable<IAsyncStartupTask> _startupTasks;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly SemaphoreSlim _semaphore;
public StartupTasksRunner(IEnumerable<IAsyncStartupTask> startupTasks, IHostApplicationLifetime applicationLifetime)
{
_startupTasks = startupTasks;
_applicationLifetime = applicationLifetime;
_semaphore = new SemaphoreSlim(0);
applicationLifetime.ApplicationStarted.Register(() => _semaphore.Release());
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// wait for ApplicationStarted event to execute when app is listening to web requests
// await _semaphore.WaitAsync(cancellationToken);
foreach (var task in _startupTasks)
{
try
{
await task.OnStartupAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// stop the application if failing startup task if fatal
//_applicationLifetime.StopApplication();
throw;
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStartupTasksRunner(this IServiceCollection services)
{
return services.AddHostedService<StartupTasksRunner>();
}
public static IServiceCollection AddStartupTask<TStartupTask>(this IServiceCollection services)
where TStartupTask : class, IAsyncStartupTask
{
return services.AddSingleton<IAsyncStartupTask, TStartupTask>();
}
}
Notes
The app startup order is:
HostedService
'sApplicationStarted
eventIf you want to run your startup task before HostedService
s started you need to make sure StartupTasksRunner
registered before any other HostedService
(they are started in registration order).
If you want to run your startup task after all HostedService
s started but before application starts to receive web requests make sure you are registering StartupTasksRunner
after any other HostedService
.
If you want to run your startup task after Kestrel Server is started uncomment // await _semaphore.WaitAsync(cancellationToken);
line. Be aware that it may run concurrently with web requests handling.
Also be aware that exceptions thrown by IHostedService.StartAsync
method will be swallowed by framework. So if successful execution of startup tasks is necessary for your application uncomment // _applicationLifetime.StopApplication();
Upvotes: 1
Reputation: 32058
Something you probably didn't realize is that running the application has 2 parts, that are hidden by default when you call IHost.Run
/IHost.RunAsync
.
Basically, this:
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
Is equivalent to this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await host.WaitForShutdownAsync();
When StartAsync
returns, the application has already started and is ready to consume requests, so you probably want to do this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await PerformSomeWorkAfterStartupAsync();
await host.WaitForShutdownAsync();
Upvotes: 6