I know prior to Asp.Net 3.0 (or 3.1), to manually start a BackgroundService, we could derive it from IHostedService
instead and change the DI registration to:
services.AddSingleton<IHostedService, CustomHostedService>();
and then manually trigger the service start by injecting the service in the constructor, and calling StartAsync()
.
However, I can't seem to do that in Asp.Net Core 3.1. Having a look at the code for the StartAsync()
method, background services are started before the app startup is completed.
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
await _hostLifetime.WaitForStartAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);
}
// Fire IApplicationLifetime.Started
_applicationLifetime?.NotifyStarted();
_logger.Started();
}
What's the best way to manually trigger a background service to start?
Essentially, this is my setup:
Background Service
public MyBackgroundService(Func<string, IConsumer> consumerFactory)
{
_consumerFactory = consumerFactory ?? throw new ArgumentNullException(nameof(consumerFactory));
}
Consumer factory registration
services.AddSingleton<Func<string, IConsumer>>(provider => providerName => provider.ConfigureConsumer(environment, providerName));
private static IConsumer ConfigureConsumer(this IServiceProvider provider, IHostEnvironment environment, string provideName)
{
if (string.IsNullOrEmpty(provideName))
throw new ArgumentNullException(nameof(provideName));
var options = provider.GetRequiredService<IOptions<ConsumerConfig>>();
if (options?.Value == null)
throw new ArgumentNullException(nameof(options));
return environment.IsDevelopment()
? new Consumer(options, provider.GetTopics())
: new Consumer((IOptions<ConsumerConfig>)options.SetSecurityOptions(provider), provider.GetTopics());
}
private static IOptions<Config> SetSecurityOptions(this IOptions<Config> config, IServiceProvider provider)
{
var certService = provider.GetRequiredService<IVaultCertificateService>();
...
return config;
}
Essentially this certService properties are only set when a request comes in and executes a middleware. And since when ExecuteAsync()
in the background service tries to get an instance of consumer from the factory, is executed regardless, I dont have the IConsumer properly configured
Thanks
_ = app.ApplicationServices.GetService<IXyzService>();
inStartup.Configure()
to automatically start the background services. If this is not present the service is not resolved and thus not instantiated. We would need to send a request to one of the controllers where this service is injected, just as you describe. – SpottyAddSingleton
wasn't needed and was actually a bad idea, as it interferes with the background service lifecycle management - even after a service stops, it remains active in a zombie state. Worse, it introduces the entire scoped service problem - where is a singleton service going to get any scoped services it needs? – IndisputableStop
. That's why the doc samples don't use singletons. – Indisputable