Reputation: 9439
In an ASP.NET web service I have my ordinary dependencies, e.g.
services.AddTransient<IEmailService, EmailService>();
services.AddTransient<ICustomerCreationService, CustomerCreationService>();
services.AddTransient<ICustomerValidationService, CustomerValidationService>();
...
But then we have just a few that are special, that only in a certain context do we want an exception to the above, so I have to do the following. In this example, I want to use an EmailService instantiated differently than usual whenever I need a new IEmailService in the context of a CustomerService, rather than using the "default" instance registered above, so I need to do the following :
services.AddTransient<ICustomerService>(sp =>
ActivatorUtilities.CreateInstance<CustomerService>(sp,
ActivatorUtilities.CreateInstance<ICustomerValidationService>(sp,
ActivatorUtilities.CreateInstance<ICustomerCreationService>(sp,
// this is the only override we actually need (don't use scoped IEmailService)
ActivatorUtilities.CreateInstance<IEmailService>(sp => new EmailService(specialConfigurationString)
)
)
)
);
Now here ICustomerValidationService doesn't need its own activation; I just want it to use the ordinary dependency injection registered above. Same for ICustomerCreationService. But when I want a dependency of a dependency of a dependency to be special, like IEmailService here, I think I have to specify arguments for the whole tree using ActivatorUtilities, right? Is there a more elegant way? I'd love to be above to say something like this, but I don't see any way. Have I missed it?
services.AddTransient<CustomerService>(sp =>
ActivatorUtilities.CreateInstanceAtAnyLevel<EmailService>(sp) // I wish...
);
Upvotes: 0
Views: 97
Reputation: 141565
Because a background service doesn't have a scope
Then it should create one. If you are actually using background hosted services then the usual approach is to create one per iteration and resolve the dependency (docs). Something along these lines:
public class MyBackgroundService(IServiceProvider services) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (...)
{
using var scope = services.CreateScope();
var customerService = scope.ServiceProvider.GetRequiredService<ICustomerService>();
await customerService.DoWork(stoppingToken);
}
}
}
If you have something different the same approach can be used:
services.AddTransient<BackgroundService>(sp => sp.CreateScope().ServiceProvider.GetRequiredService<ICustomerService>());
Note that this implementation has at least one potential big problem that it will not dispose correctly owned dependencies (if any are disposable, like EF Core context). This can be workaround by making the registered dependency disposable and capturing scope in it or making wrapper (see for example this answer, basically following factory approach).
P.S.
Note that the check for a scope (by default is applied only for development environments and can be disabled if needed) is there for a reason. If you are creating a scoped service outside of the scope you can end up with so called captive dependency which can have negative effects on the app (for example in case of EF context - degrading performance and memory-leakish behaviour).
Upvotes: 0
Reputation: 1
I'm not sure, but maybe this can help you. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-8.0&tabs=visual-studio
public sealed class BackgroundService: IHostedService, IDisposable
{
public BackgroundService(IServiceScopeFactory serviceScopeFactory)
{
serviceScope = serviceScopeFactory.CreateScope();
}
public Task StartAsync(CancellationToken cancellationToken)
{
var services = serviceScope.ServiceProvider;
var emailService = services.GetRequiredService<IEmailService>();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
serviceScope.Dispose();
}
}
Upvotes: 0