How to consume a Scoped service from a Singleton?
A good way to use services inside of hosted services is to create a scope when needed. This allows to use services / db contexts etc. with the lifetime configuration they are set up with. Not creating a scope could in theory lead to creating singleton DbContexts and improper context reusing (EF Core 2.0 with DbContext pools).
To do this, inject an IServiceScopeFactory
and use it to create a scope when needed. Then resolve any dependencies you need from this scope. This also allows you to register custom services as scoped dependencies should you want to move logic out of the hosted service and use the hosted service only to trigger some work (e.g. regularly trigger a task - this would regularly create scopes, create the task service in this scope which also gets a db context injected).
public class MyHostedService : IHostedService
{
private readonly IServiceScopeFactory scopeFactory;
public MyHostedService(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public void DoWork()
{
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
…
}
}
…
}
DbContext dependency injection is conflicting with IHostedService when Application is starting
You can not inject scoped/transiet services to your HostedService
, instead you will have to inject IServiceProvider
and then create a scope like this:
public class TaskLeituraService : BackgroundService
{
private readonly ILogger<TaskLeituraService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<TaskLeituraService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = Services.CreateScope())
{
var _leituraService=
scope.ServiceProvider
.GetRequiredService<LeituraService>();
while (!stoppingToken.IsCancellationRequested)
{
stoppingToken.Register(() => _logger.LogDebug("LeituraTaskService esta parando"));
await ExecuteTaskLeitura(_leituraService);
}
}
await Task.CompletedTask;
}
private async Task ExecuteTaskLeitura(LeituraService _leituraService)
{
try
{
_logger.LogDebug($"{DateTime.Now} - Enviando comando leitura das fotocélulas");
await _leituraService.ExecuteTaskLeituraFotocelulaAsync();
_logger.LogDebug($"{DateTime.Now} - Finalizando comando leitura das fotocélulas");
await Task.Delay(TimeSpan.FromMinutes(5));
}
catch (Exception e)
{
throw e;
}
}
}
Create DBContext outside of DependencyInjection
For resolving a Scoped Service
from a Singleton Service
, you could create the scoped service from IServiceProvider
.
Here is the demo code:
public class DbHostedService : IHostedService
{
private readonly ILogger _logger;
public DbHostedService(IServiceProvider services,
ILogger<DbHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var context =
scope.ServiceProvider
.GetRequiredService<ApplicationDbContext>();
var user = context.Users.LastOrDefault();
_logger.LogInformation(user?.UserName);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
Reference: Consuming a scoped service in a background task
.net core dependency injection to hosted service
The reason you're not allowed to do this is because MyHostedService has a singleton lifetime, which is a longer lifetime than scoped. The basic assumption here is that a service that is registered as scoped should not be kept alive indefinitely, this could easily happen if a singleton service keeps a reference to a scoped service.
I think the solution you're looking for is to inject IServiceProvider into MyHostedService, use it to create a new scope and new XService instance whenever you need it.
That is, replace
_xService.Foo();
with
using(var scope = _serviceProvider.CreateScope()) {
var xService = scope.ServiceProvider.GetService<IXService>();
xService.Foo();
}
An alternative, of course, would be to simply register XService as a singleton, by just replacing the call to AddScoped with AddSingleton, but I would not recommend it.
Edit: I must admit to not reading your link before posting my response. However, I still think this is the most elegant solution.
access context in a hosted service
A Hosted Service is a singleton, which means that only one instance of that class exists for the life of the application.
A Context is scoped, meaning it's designed to have a very short lifespan (only for a specific "scope", like a single HTTP request). It's not good at staying alive indefinitely (there are db connections involved, which you can't guarantee will stay open for the life of the application, for example).
If you inject a Context into another class, the Context will exist for the life of the instance of that class. For a singleton class, that's the life of the application. So this is why you get the exception you do. .NET Core is telling you: "This isn't going to work the way you think it's going to work"
The solution is here: https://stackoverflow.com/a/48368934/1202807
In short, inject a IServiceScopeFactory
, which gives you the power to ask the DI engine to give you a scoped class when needed, then it's up to you to keep it around only as long as you need it.
private readonly IServiceScopeFactory _scopeFactory;
public TimedHostedService(ILogger<TimedHostedService> logger, IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
Then you get your context like this:
using (var scope = scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<Context>();
//do what you need
}//scope (and context) gets destroyed here
Old answer (which is wrong here, but applies to other types of classes):
Just put it in your constructor and it'll get injected by dependency injection:
public TimedHostedService(ILogger<TimedHostedService> logger, Context context)
{
_logger = logger;
_context = context;
}
It's the services.AddDbContext()
lines that makes them available for dependency injection. Just pick type you want, since you've defined two:
services.AddDbContext<Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LaprDB")));
services.AddDbContext<ContextUsers>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));
Access DbContext service from background task
In that case you can only rely on the servicelocater-pattern which is an anti-pattern. Also the DbContext must be instance-per-dependency (transient) rather than scoped.
My suggestion is to inject IServiceScopeFactory
which is a singleton and the beginn a scope in your background-worker that does the deed.
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<DbContext>();
// now do your work
}
There is no other. These things are not part of the mvc pipeline so they cannot be resolved on the fly. I would also suggest not to directly access the dbcontext somewhere. Wrap things in a repository and then use that repository or even wrap that repository in a so called service class. So your logic is encapsulated and your background-worker just executes things when receiving a message.
Not directly related, but ASP.NET-Core has background-worker built-in with IHostedService.
Edit 2018-07-30: As Steven suggested below, it might not always be an anti-pattern when manually resolving / initiating classes. At least not when the compositionroot is taking care of it. Here is a link he provided, where it is explained quite good.
Edit 2022-07-07:
Using the latest c# syntax and creating an async-scope is now also possible.
await using var scope = _serviceProviderFactory.CreateAsyncScope();
var context = scope.ServiceProvider.GetRequiredService<DbContext>();
// now do your work
Alternatively you can also directly inject IServiceProvider
and do the very same thing.
How to inject a reference to a specific IHostedService implementation?
None of the complexity in the other answers is needed as of .net core 3.1. If you don't need to get a concrete reference to your class from another class, simply call:
services.AddHostedService<MyHostedServiceType>();
If you must have a concrete reference, do the following:
services.AddSingleton<IHostedService, MyHostedServiceType>();
Related Topics
Sorting a List Using Lambda/Linq to Objects
Setting Canvas Properties in an Itemscontrol Datatemplate
Passing a Value from One Form to Another Form
Collection<T> Versus List<T> What Should You Use on Your Interfaces
Display Image from Database in Asp MVC
Correct, Idiomatic Way to Use Custom Editor Templates with Ienumerable Models in ASP.NET MVC
Deserialize JSON Array Stream One Item at a Time
Serialize Dictionary as Array (Of Key Value Pairs)
Exact Time Measurement for Performance Testing
C# Image Resizing to Different Size While Preserving Aspect Ratio
How to Delete Cookies from Windows.Form
How to Fix "Referenced Assembly Does Not Have a Strong Name" Error
How to Format a Number into a String with Leading Zeros