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
}
Related
I have a tricky requirement where I need create copy of a service that has been created via Constructor DI in my Azure Function
public MyFunction(IMyService myService,
IServiceProvider serviceProvider,
ServiceCollectionContainer serviceCollectionContainer)
{
_myService = tmToolsService;
_serviceProvider = serviceProvider;
_serviceCollectionContainer = serviceCollectionContainer;
}
[FunctionName("diagnostic-orchestration")]
public async Task DiagnosticOrchestrationAsync(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
}
This service has a lot of dependencies so I dont really want go down the manual Activator.CreateInstance route
I have tried 2 different approaches
Approach 1
I have ServiceCollectionContainer. This is filled in Configure of the startup and simply holds the services
public override void Configure(IFunctionsHostBuilder builder)
{
base.Configure(builder);
var services = builder.Services;
services.AddSingleton(s => new ServiceCollectionContainer(services));
}
In my function I call
var provider = _serviceCollectionContainer.ServiceCollection.BuildServiceProvider();
if (provider.GetService<IMyService>() is IMyService myService)
{
await myService.MyMathodAsync();
}
This throws the error
System.InvalidOperationException: 'Unable to resolve service for type
'Microsoft.Azure.WebJobs.Script.IEnvironment' while attempting to activate
'Microsoft.Azure.WebJobs.Script.Configuration.ScriptJobHostOptionsSetup'.'
I believe this could be because although the service collection looks fine (276 registered services) I have seen references online that say that Configure may be unreliable
Approach 2
The second approach is the more conventional one, I just tried to use the service provider injected without making any changes
if (_serviceProvider.GetService<IMyService>() is IMyService myService)
{
await myService.MyMathodAsync();
}
But if I use this approach I get the error
'Scope disposed{no name} is disposed and scoped instances are disposed and no longer availab
How can I fix this?
I have large date range of data that I am processing. I need to split my date range and use my service to process each date range. My service has repositories. Each repository has a DbContext. Having each segment of dates run in the context of its own service allows me to run the processing in parallel without having DbContext queries being run in parallel which causes issues with Ef Core
This processing is running inside a durable function
I don't know if this holds true for Azure Functions and moreover I am not experienced with durable ones, though as it seems that the main goal is to run parallel queries via ef core through your IMyService then you could in the constructor:
public MyFunction(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
And then in the function call, assuming you have an IEnumerable "yourSegments" of the things you want to process in parallel:
var tasks = yourSegments.Select(async segment =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var IMyService = scope.ServiceProvider.GetRequiredService<IMyService>();
await IMyService.MyMathodAsync(segment);
}
});
await Task.WhenAll(tasks);
I got this from a nice blog post that explains "Since we project our parameters to multiple tasks, each will have it's own scope which can resolve it's own DbContext instance."
You can create a 1:1 copy by using this extension method.
It is a large function, to large for SO, so I've put a pastebin here.
https://pastebin.com/1dKu01w9
Just call _myService.DeepCopyByExpressionTree(); within your constructor.
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 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.
I want to seed some data to my sotre context from the app entry point.
My question is why should i create scope on main method (within program.cs).
Instead of consuming service directly ?
What is the rational behind this best practice ?
Create scope
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var serv = scope.ServiceProvider.GetRequiredService<StoreContext>();
// ..... do stuff
}
host.Run();
}
Consume directly
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var serv = host.Services.GetRequiredService<StoreContext>();
// do some code here
host.Run();
}
If you've registered your context with a Scoped lifetime (which is the default) then you will get an exception if you try to retrieve it from the root ServiceProvider. You will need to create a IServiceScope first and then request the services from there. This ensures that any dependencies that are also scoped (for example services dependant on the current request in a web app) can be resolved correctly as well.
To be clear: If you request from the root, there is no scope. You need a scope to request a scoped service; without one an exception will be thrown.
From the documentation under the heading "Call services from Main":
Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. This approach is useful to access a scoped service at startup to run initialization tasks
Retrieving "directly" is reserved for singletons. If you register your context with a lifetime of Singleton then retrieving it from the root service provider will work just fine.
// register
services.AddDbContext<StoreContext>(ServiceLifetime.Singleton);
// use
var context = host.Services.GetRequiredService<StoreContext>();
Note that for contexts in a web application this should be avoided. For a console application where there's no concurrent use of the context this approach is fine.
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;