I have a scoped service (lets it be UserContext, that contain user IP). It service I try to inject in another services (let's call them ProfileManager and LogerProvider).
In my controller at startup, I added them like so:
service
.AddTransient(ILogerProvider, LogerProvider)()
.AddSingleton<IProfileManager, ProfileManager)()
.AddScoped<IUserContext, UserContext>()
Class LogerProvider contain UserContext inject:
class LogerProvider: ILogerProvider
{
private readonly IUserContext _userContext;
public LogerProvider(IUserContext userContext)
{
_userContext = userContext;
}
}
Class ProfileManager contain LogerProvider inject:
class ProfileManager: IProfileManager
{
private readonly ILogerProvider _logerProvider;
public ProfileManager(ILogerProvider logerProvider)
{
_logerProvider = logerProvider;
}
}
And when i try to run my program i got error:
(Inner Exception #1) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: IProfileManager Lifetime: Singleton ImplementationType: IProfileManager': Cannot consume scoped service 'IUserContext' from singleton 'IProfileManager'.
I found that i can just change lifetime of ProfileManager just make it transient. But i need to use this service like singleton. So a question: how i can realize dependency injection saved lifetime of services how i typed at the beginning of the text?
You need to manually create a scope and consume it, to do so you'll need to inject the IServiceProvider into your singleton service and then call IServiceCollection.CreateScope
public class ProfileManager : IProfileManager
{
private readonly IServiceProvider _services;
public ProfileManager(IServiceProvider services)
{
_services = services;
}
public void DoSomething()
{
using (var scope = _services.CreateScope())
{
var logger = scope.ServiceProvider.GetRequiredService<ILogerProvider>();
}
}
}
Sidenote: You may want to rethink the lifetimes and scopes of your services if you find yourself repeating this pattern again and again
I would strongly advise against storing any sort of user context information in a singleton class, for security reasons. If you do it wrong, users will be able to see each others' data.
If your logger needs user context information, have the caller pass it in and store it only as a local variable for the duration of the method call. Never store it as a member variable.
class LogerProvider: ILogerProvider
{
public void LogMessage(IUserContext userContext, string message)
{
//Write to logs, including user context information
}
}
This will also prevent anyone from trying to log anything when there is no user context, as it will be obvious to them that they don't have one of the necessary arguments. If your logger has a dependency on user context then its methods should not be called when there isn't one.
If you don't want to burden the caller with the extra argument, consider using the ThreadPrincipal to store your user context.
To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory into your singleton service (the IServiceScopeFactory is itself a singleton, which is why this works). The IServiceScopeFactory has a CreateScope method, which is used for creating new scope instances.
public class MySingletonService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MySingletonService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Execute()
{
using var scope = _serviceScopeFactory.CreateScope()
var myScopedService = scope.ServiceProvider.GetService<IMyScopedService>();
myScopedService.DoSomething();
}
}
The created scope has its own IServiceProvider, which you can access to resolve your scoped services.
It is important to make sure that the scope only exists for as long as is necessary, and that it is properly disposed of once you have finished with it. This is to avoid any issues of captive dependencies. Therefore, I would recommend:
Only define the scope within the method that you intend to use it. It might be tempting to assign it to a field for reuse elsewhere in the singleton service, but again this will lead to captive dependencies.
Wrap the scope in a using statement. This will ensure that the scope is properly disposed of once you have finished with it.
In short, IServiceProvider.CreateScope() and IServiceScopeFactory.CreateScope() are identical (in non-scoped context even instances of IServiceProvider and IServiceScopeFactory are identical).
But here is a little difference between these abstractions
IServiceProvider's lifetime can be Scoped. But IServiceScopeFactory's lifetime is always Singleton.
Related
How should I inject (using .NET Core's built-in dependency injection library, MS.DI) a DbContext instance into a Singleton? In my specific case the singleton is an IHostedService?
What have I tried
I currently have my IHostedService class take a MainContext (deriving from DbContext) instance in the constructor.
When I run the application I get:
Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
So I tried to make the DbContextOptions transient by specifying:
services.AddDbContext<MainContext>(options =>
options.UseSqlite("Data Source=development.db"),
ServiceLifetime.Transient);
in my Startup class.
But the error remains the same, even though, according to this solved Github issue the DbContextOptions passed should have the same lifetime specified in the AddDbContext call.
I can't make the database context a singleton otherwise concurrent calls to it would yield concurrency exceptions (due to the fact that the database context is not guaranteed to be thread safe).
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>();
…
}
}
…
}
You can add create Scope in constructor as below:
public ServiceBusQueueListner(ILogger<ServiceBusQueueListner> logger, IServiceProvider serviceProvider, IConfiguration configuration)
{
_logger = logger;
_reportProcessor = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IReportProcessor>();
_configuration = configuration;
}
Do add
using Microsoft.Extensions.DependencyInjection;
I'm using .net core 3.1 with this configuration :
public interface IFoo
{
public void Work();
}
public class Foo : IFoo
{
readonly string MyGuid;
public Foo()
{
MyGuid = Guid.NewGuid().ToString();
}
public void Work() { Console.WriteLine(MyGuid); }
}
And this is the configuration :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IFoo, Foo>();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IFoo foo, IServiceProvider myServiceProvider)
{
Console.WriteLine("Via ApplicationServices");
app.ApplicationServices.GetService<IFoo>().Work();
Console.WriteLine("Via IServiceProvider");
myServiceProvider.GetService<IFoo>().Work();
Console.WriteLine("Via IFoo injection");
foo.Work();
}
Result are :
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----different
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb
More , in the controller action , If I use IserviceProvider :
[HttpGet]
public IActionResult Get([FromServices] IServiceProvider serviceProvider)
{
serviceProvider.GetService<IFoo>().Work();
}
I see another different Guid : 45d95a9d-4169-40a0-9eae-e29e85a3cc19.
Question:
Why does the IServiceProvider and Injection of IFoo in the Configure method yield the same Guid , while the controller action and app.ApplicationServices.GetService yield different ones?
There are 4 different guids in this example
It's a scoped service. It supposed to be the same Guid.
TL;DR;
IFoo foo is resolved using myServiceProvider.GetService<IFoo>() before both get passed into Configure method. So you're resolving same IFoo instance from the same myServiceProvider instance for the 2nd time.
app.ApplicationServices is special root service provider of the application. Parent of myServiceProvider. Thus it resolves different IFoo.
**Default behavior of app.ApplicationServices should be to throw exception when you try to resolve scoped service.
LONGER EXPLANATION
If you have three dummy classes:
class Singleton { }
class Scoped { }
class Transient { }
And you register them as such in IServiceConfiguration:
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Singleton>();
services.AddScoped<Scoped>();
services.AddTransient<Transient>();
}
Now, you create your "root" IServiceProvider from IServiceCollection - in command-line app, it would look like this:
ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();
ROOT SERVICE PROVIDER BEHAVIOR (equivalent to app.ApplicationServices):
If you now test root:
root.GetService<Singleton>(); - every time it's called returns same object instance.
root.GetService<Scoped>(); - every time it's called returns same object instance.
root.GetService<Transient>(); every time it's called returns new object instance.
CHILD-SCOPE SERVICE PROVIDER BEHAVIOR (eg: IServiceProvider in Configure method):
If you now create child scope and use it's own IServiceProvider:
IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;
sp1.GetService<Singleton>(); - every time it's called returns same object instance whichroot.GetService<Singleton>(); returns. Singleton is the same instance no matter from which scope you call it. It is resolved climbing the hierarchy of scopes back to the root service provider (scopeless one).
sp1.GetService<Scoped>(); - every time it's called returns same object instance, but not the same instance that root returns. Object instance is cached on the current scope. Every scope creates/caches it's own scoped instance.
sp1.GetService<Transient>(); every time it's called returns new object instance, same behavior like for the root.
root scope is "special" only because it has no parent scope, so resolving scoped or singleton service from the root technically does the same thing - object instance returned is cached in the root itself.
This also explains why you cannot resolve service from IServiceCollection directly. IServiceCollection does not have hierarchy of scopes and caching infrastructure which IServiceProvider has. It just contains list of ServiceDescriptor. In addition, it would be unclear in which scope service instance should be cached.
ASP.NET Core
For ASP.NET Core root IServiceProvider is app.ApplicationServices. Configure method receives first child-scope created from the root - application-scope. For every HTTP request application-scope creates child-scope which is used to resolve all services and is itself injected in controllers and views of that HTTP request. It is also used to inject all other types in a controller constructor or a view.
IFoo resolution
So, your foo from Configure method is resolved using myServiceProvider, then they both get used as input parameters for Configure. Framework does something like this:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
var appScope = root.CreateScope();
IFoo foo = appScope.ServiceProvider.GetService<IFoo>();
ConfigureServices(foo, appScope.ServiceProvider);
When you call sp.GetService<IFoo>() inside of Configure method it is identical to appScope.ServiceProvider.GetService<IFoo>(); that was already called from the outside. root.GetService<IFoo>() creates different IFoo instance, as it should.
More ASP.NET Core:
To prevent developers making mistake trying to resolve scoped service from the app.ApplicationServices and not realizing that it is application scoped (global), instead of being scoped to HTTP request, by default, ASP.NET Core creates root ServiceProvider using BuildServiceProvider overload:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
validateScopes: true to perform check verifying that scoped services never gets resolved from root provider; otherwise false.
However, this might depend on compatibility mode you're using. I'm guessing that's the reason why it allows you to resolve scoped service via app.ApplicationServices.
Update answer:
For being able to provide you with the same instance, the DI engine will need to know the current scope.
The Configure method in the Startup class is called initially by the ASP.NET Core engine, not by a request from the user which is how a scoped service is meant to be used.
Now if you try this example instead:
IServiceScope scope = app.ApplicationServices.CreateScope();
scope.ServiceProvider.GetService<IFoo>().Work();
As long as you use the same ServiceProvider from the same IServiceScope instance, you will have access to the same IFoo object and will get the same GUID.
Old answer:
A scoped service is meant to be living for a short period (scope) and get removed when they are needed anymore.
From Dependency injection in .NET
a scoped lifetime indicates that services are created once per client request (connection)
furthermore,
Do not resolve a scoped service from a singleton and be careful not to do so indirectly, for example, through a transient service.
app.ApplicationServices.GetService will try to provide you a Singleton which is not what the service is registered as.
What you see is the expected behavior. If you need the service to have the same instance for the whole period of your application's lifetime, you will have to register it as a Singleton.
Read more about the singleton lifetime and change your code from services.AddScoped<IFoo, Foo>() to services.AddSingleton<IFoo, Foo>() if you need the same value everywhere.
The IApplicationBuilder "app" is created and passed to the Configure method.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-5.0#the-configure-method
IApplicationBuilder is available to the Configure method, but it isn't registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.
On the other hand the IServiceProvider "myServiceProvider" is actually activated/created and injected:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#services-injected-into-startup
Services can be injected into the Startup constructor and the Startup.Configure method.
Any service registered with the DI container can be injected into the Startup.Configure method
So we are in two different scopes, so the guid, as expected, is different.
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <---- created in scope "A"
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb <-- created in scope "B"
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb < -- created in scope "B"
When you are in your controller each request will have it's brand new scope, a different scope than the ones discussed above:
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
It seems to me that you expected that also the guid in the controller to be the same, you should be aware that for each request an instance of your controller is created and a DI chain starts in a fresh scope for each request.
I have defined my DbContext in the Startup.cs
services.AddDbContext<GretaDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
I want to access the DbContext in my Dialogs in order to Add and Modify the entities in my database. The problem is my Dialogs are a Singleton service while the DbContext is Scoped so i can't DI it through the constructor.
I've read somewhere I should create a Controller obtaining the DbContext through the IServiceProvider and call Controller functions from the Dialogs but I don't know how to accomplish that.
What is the best way to be able to use the DbContext inside the Dialogs?
You can request a scoped service within a singleton by creating scope and then retrieving the service from an IServiceProvider that is injected into the singleton:
public class DialogsSingleton
{
private readonly IServiceProvider services;
public DialogsSingleton(IServiceProvider services)
{
this.services = services;
}
public void DomeSomethingRequiringDbContext()
{
using (var scope = this.services.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<GretaDBContext>();
// Use the scoped service
}
}
}
You should probably create a business logic layer(BLL). You can inject your DbContext into this layer. From there you should be able to inject you BLL into a controller and have your Dialogs reach out. Your controller typically shouldn't contain logic. You may be able to access your BLL from within your dialogs rather than a separate controller.
My textbook shows an example to build identity services, below is the code:
//startup.cs
public void Configure(IApplicationBuilder app) {
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
//try to seed an admin account for the first time the app runs
AppIdentityDbContext.CreateAdminAccount(app.ApplicationServices, Configuration).Wait();
}
//AppIdentityDbContext.cs
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options) { }
public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
{
UserManager<AppUser> userManager = serviceProvider.GetRequiredService<UserManager<AppUser>>();
RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string username = configuration["Data:AdminUser:Name"];
string email = configuration["Data:AdminUser:Email"];
string password = configuration["Data:AdminUser:Password"];
string role = configuration["Data:AdminUser:Role"];
if (await userManager.FindByNameAsync(username) == null)
{
if (await roleManager.FindByNameAsync(role) == null)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
AppUser user = new AppUser
{
UserName = username,
Email = email
};
IdentityResult result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, role);
}
}
}
}
and then the textbook says:
Because I am accessing a scoped service via the IApplicationBuilder.ApplicationServices provider,
I must also disable the dependency injection scope validation feature in the Program class, as shown below:
//Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options => options.ValidateScopes = false)
.Build();
I have a basic understanding in DI, but I'm really confused with this example, below are my questions:
Q1- accessing a scoped service via the IApplicationBuilder.ApplicationServices provider
what does it mean? what services it tries to access? why it is scoped not transient or singleton?
Q2- why we have to disable the dependency injection scope validation, what does scope validation try to achieve?
In order to understand what is going on, you will first have to understand the difference between the dependency injection lifetimes:
Transient: A new instance gets created for every dependency that gets resolved.
Singleton: A single shared instance is used whenever the service gets resolved.
Scoped: A single instance is shared whenever the service gets resolved within a single scope (or request). A subsequent request will mean that a new instance will be created again.
A database context holds a connection to the database. That’s why you usually don’t want it to be a singleton, so that you don’t keep a single connection open for the whole lifetime of your application. So you would want to make it transient. But then, if you needed to access the database multiple times while serving a single request, you would be opening the database connection multiple times within a short duration. So the compromise is to make it a scoped dependency by default: That way you don’t keep the connection open for a long time, but you also can still reuse the connection for a short duration.
Now, let’s think about what happens when a singleton service depends on a non-singleton service: The singleton service gets created just once, so its dependencies are also only resolved once. That means that any dependency it has is now effectively shared throughout the lifetime of that service—which is the lifetime of the application. So by depending on non-singleton services, you effectively make those services quasi-singleton.
That’s why there is a protection in play (during development), that protects you from making this mistake: The scope validation will check that you are not depending on scoped services outside of scopes, e.g. within singleton services. That way, you are not escaping the desired lifetime of that scoped service.
When you now run AppIdentityDbContext.CreateAdminAccount within the Configure method, you are running this outside of a scope. So you are basically within “singleton land”. Any dependency you now create will be kept around. Since you resolve UserManager<AppUser> and RoleManager<IdentityRole> which both depend on the scoped database context, you are now escaping the database context’s configured scoped lifetime.
In order to fix this, you should create a short-lived scope in which you can then access scoped services (since you are within a scope) that will be properly cleaned up when the scope terminates:
public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
{
// get service scope factory (you could also pass this instead of the service provider)
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
// create a scope
using (var scope = serviceScopeFactory.CreateScope())
{
// resolve the services *within that scope*
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// do stuff
}
// scope is terminated after the using ends, and all scoped dependencies will be cleaned up
}
How should I inject (using .NET Core's built-in dependency injection library, MS.DI) a DbContext instance into a Singleton? In my specific case the singleton is an IHostedService?
What have I tried
I currently have my IHostedService class take a MainContext (deriving from DbContext) instance in the constructor.
When I run the application I get:
Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
So I tried to make the DbContextOptions transient by specifying:
services.AddDbContext<MainContext>(options =>
options.UseSqlite("Data Source=development.db"),
ServiceLifetime.Transient);
in my Startup class.
But the error remains the same, even though, according to this solved Github issue the DbContextOptions passed should have the same lifetime specified in the AddDbContext call.
I can't make the database context a singleton otherwise concurrent calls to it would yield concurrency exceptions (due to the fact that the database context is not guaranteed to be thread safe).
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>();
…
}
}
…
}
You can add create Scope in constructor as below:
public ServiceBusQueueListner(ILogger<ServiceBusQueueListner> logger, IServiceProvider serviceProvider, IConfiguration configuration)
{
_logger = logger;
_reportProcessor = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IReportProcessor>();
_configuration = configuration;
}
Do add
using Microsoft.Extensions.DependencyInjection;