Start a new LifetimeScope when a Masstransit consumer is triggered - c#

We have the following code to configure the recieveEndpoint:
private Action<IServiceBusReceiveEndpointConfigurator> GetReceiveEndpointConfigurator(IEnumerable<Type> consumerTypes)
{
return c =>
{
c.EnableDeadLetteringOnMessageExpiration = true;
c.SubscribeMessageTopics = false;
c.MaxDeliveryCount = 3;
c.EnableDeadLetteringOnMessageExpiration = true;
c.UseRetry(Retry.Exponential(3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(1)));
foreach (var consumerType in consumerTypes)
{
c.Consumer(consumerType, p => _componentContext.Resolve(p));
}
};
}
All of our consumers are autodiscovered through reflection once our application starts up. We have a DbContext that we want to use in many of our consumers. The problem we face is that the DbContext is disposed due to it being registered as InstancePerLifetimeScope. More details here:
AspNet Core Autofac disposing my DbContext even if its registered as SingleInstance
Two suggestions came from this post:
Register the DbContext as InstancePerDependency
Create a new Scope within the consumer to start a new LifetimeScope
The first suggestion wont work in our application as we have a UnitOfWork which triggers the SaveChangesAsync on the DbContext. The result would be that the Repository and the UnitOfWork will get two different instances of the DbContext and SaveChangesAsync will not persist our changes as the ChangeTracker has no changes in the UnitOfWork implementation, but these changes belongs to the instance in the Repository.
The second suggestion works perfectly. Within my Consumer I create a new LifetimeScope and resolves the components that I need:
public async Task Consume(ConsumeContext<RetailerCreatedEvent> context)
{
using (var scope = _serviceProvider.CreateScope())
{
var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork<MyDbContext>>();
}
}
It works, but it doesn't look that good.
Is there a way to start a new LifetimeScope before the Consumer triggers? Or should I rewrite my UnitOfWork-pattern to ensure that the same DbContext is being reused in the Repositories and the UnitOfWork?
Suggestions are much appreciated

You need to use the MassTransit.Autofac package to resolve your consumers, which will use the AutofacScopeProvider (part of the package) to create a lifetime scope and resolve your consumer.
The documentation shows the configuration, including how to automatically discover your consumers via scanning and add them to MassTransit.
Your consumers shouldn't have any container code in them using this package, they should just add DbContext as a constructor dependency and let Autofac do the work.

Related

Dependency injection with Unit of Work pattern

I'm trying to implement unit of work pattern for my repositories in C#/.NET. My plan is to give UoW as a parameter to repositories. Here's an example how it could be used:
using (var uow = new UnitOfWork())
{
var itemRepository = new ItemRepository(uow);
itemRepository.Add(new Item());
uow.Commit();
}
Also, for simple operations (when transactions are not needed) repositories should be able to be used without Unit of Work:
var itemRepository = new ItemRepository();
var item = itemRepository.Get(itemId);
UnitOfWork/Repository could get a database connection from ConnectionFactory. Connection factory receives connection options via dependency injection. However, here's my problem: How do repositories get reference to ConnectionFactory instance? Repositories are created manually, so they can't have dependencies injected via constructor. One option would be to have repository factories, which could have their dependencies injected. In that case usage could be like this:
using (var uow = new UnitOfWork())
{
var itemRepository = itemRepositoryFactory.Create(uow);
itemRepository.Add(new Item());
uow.Commit();
}
The drawback in that solution is that each repository will need its own factory, and there will be quite many. Are there any other solutions to circumvent this problem?
I would definitely register the UOW as a scoped dependency.
Scoped dependencies live for the lifetime of the container that creates them. Typically, frameworks would generate a child container from the parent container in order to execute some piece of work. For example, ASP.NET Core spawns a child container for a request and then disposes it when the request is finished. This would mean that the UOW instance that is getting injected is the same instance throughout the object graph for that request only.
You can also create your own scopes, if needed. I have done this twice for example:
A job scheduler, so that each job ran in it's own scope
A message handler, so that each message was processed in its own scope
This is how you would achieve this using Microsoft's DI framework:
var collection = new ServiceCollection();
collection.AddScoped<IUnitOfWork, UnitOfWork>();
var provider = collection.BuildServiceProvider();
var puow = provider.GetRequiredService<IUnitOfWork>();
for(int i = 0; i < 2; i++)
{
//Create the new scope
using var childContainer = provider.CreateScope();
//IUnitOfWork will be a singleton instance in this scope.
var c1uow = childContainer.ServiceProvider.GetRequiredService<IUnitOfWork>();
var c2uow = childContainer.ServiceProvider.GetRequiredService<IUnitOfWork>();
// This should true, since they come from the same scope.
var sameScope = c1uow == c2uow;
//With the requested IUnitOfWork from provider instead, it would be a different instance
//Therefore, this should be false
var diffScope = puow == c1uow;
}
This would allow you to simply inject IUnitOfWork into each repo, without having to create a factory for each repo.
This would work out of the box if you are writing an application in ASP.NET Core. Simply register the dependency as scope, as so
collection.AddScoped<IUnitOfWork, UnitOfWork>();
and then have it injected into the repos that you need. You don't have to worry about creating a new scope since the framework does that for you for each http request that the application receives.
I would really recommend reading Mark Seemann's book "Dependency Injection Principles, Practices, and Patterns". It really goes in depth about what dependency injection is and how it works. Not only that, I find him to be a great writer.

NET 5 and EF: how to use AddPooledDbContextFactory in liu of DbContext in services

I recently came across AddPooledDbContextFactory concept as part of my NET 5 self-education piece and am keen to implement it properly. However, I am not sure how to use it with generics that I generally use.
Example of my current setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbContext>(
(s, o) => o.UseNpgsql(Configuration.GetConnectionString("DatabaseConnection"))
.UseLoggerFactory(s.GetRequiredService<ILoggerFactory>()));
// other code //
}
my repository generic:
public class Repository<T> : IRepository<T> where T
{
private readonly TestDbContext _dbContext;
public Repository(TestDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Create(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
await _dbContext.SaveChangesAsync();
}
// other methods //
}
this is invoked in following manner as example:
public class WeatherForecastController : ControllerBase
{
private readonly IRepository<Test> testRepo;
public WeatherForecastController(IRepository<Test> testRepo)
{
this.testRepo= testRepo;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> GetAsync()
{
await testRepo.Create(new Test { Name = "Superman" });
// other code
}
}
I would like to convert this to use the new AddPooledDbContextFactory concept but cannot find enough documentation to figure out how to do this.
Atm only thing that comes to mind is using statements at each method but that doesn't make sense.
Any advice on this?
Documentation is not yet complete and is in progress, you track this issue
https://github.com/dotnet/EntityFramework.Docs/issues/2523
You can also a look at the tests for AddPooledDbContextFactory to see how to register DbContext with
https://github.com/dotnet/efcore/search?q=AddPooledDbContextFactory
for example to register DbContext:
services.AddPooledDbContextFactory<TContext>(ob =>
ob.UseSqlServer("ConnectionString").EnableServiceProviderCaching(false), poolSize: 32)
Then in your class, inject an IDbContextFactory<TContext> and use it like this:
using(var context = _factory.CreateDbContext())
{
var orders = await context.Orders.Where(o => o.Id > 3).ToListAsync();
}
According to this post:
Note that the DbContext instances created in this way are not managed
by the application's service provider and therefore must be disposed
by the application
You can also check out this post to see how to use IDbContextFactory:
https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0
#Aeseir your code looks good to me. You are following best practices and you don't need to change it.
You are using the Repository Pattern, so your Repository class has all of your query logic which helps create loosely coupled and maintainable code.
In your ConfigureServices, calling: services.AddDbContext<TestDbContext>() registers TestDbContext with Scoped service lifetime. This is the way that DbContext is designed to work, and it will also work well with ASP.NET controllers, since they have a Scoped lifetime as well.
You did not show your code for registering IRepository, but that service lifetime should be Scoped as well. Btw, you can tell BuildServiceProvider() to validate scope registrations:
builder.Services.BuildServiceProvider(validateScopes: true);
Since DbContext is designed to have a Scoped service lifetime, and since your IRepository and Controller services are Scoped as well, every request gets brand new:
Controller
IRepository
DbContext
Those services are used for the request and then Diposed. This is how ASP.NET is intended to work.
Apparently at some point, DbContext pooling has been introduced to improve performance. In this case, EF Core manages a pool of context instances for you and resets them after each request. This can improve performance, although in some situations, the benefit might be small. See MSDN documentation for more details.
I think for use with ASP.NET controllers (i.e. the code you posted above) all you need to do to take advantage of EF Core context pooling is call AddDbContextPool():
builder.Services.AddDbContextPool<ApplicationDbContext>(/* ... */);
However, if you needed to use DbContext in services registered with Singleton lifetime, then the pattern above would not work well. Because when a Scoped service gets used in a Singleton service, the Scoped service is essentially a Singleton. Each request would not get a new DbContext, nor a reset one from the pool. (See QUESTION below.)
In that case, you might want to use the DbContext factory pattern instead:
builder.Services.AddDbContextFactory<ApplicationDbContext>(/* ... */);
Or, if you want to use context pooling with a factory pattern:
builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(/* ... */);
The DbContextFactory can then be used in other services through constructor injection. For example:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
The injected factory can then be used to construct DbContext instances in the controller code. For example:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Keep in mind that when you call CreateDbContext(), context instances are not managed by the service provider and therefore must be disposed by the application. Hence you need to Dispose of them yourself, such as in the example above which does so with the using statement.
QUESTION
I am doing my best to understand this stuff and explain it, but I might be wrong, so please call out an inaccuracies in my post.
When using AddDbContextPool(), does the DbContext get registered as a Singleton or Scoped?
I found in MSDN documentation that it's effectively registered as a Singleton:
Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes). This means that special care must be taken when the context involves any state that may change between requests.
However, I have found that if AddDbContextPool() is used along with true for validatedScopes:
builder.Services.BuildServiceProvider(validateScopes: true)
When DbContext is consumed from another service which is registered as a Singleton, the following exception is thrown:
System.InvalidOperationException: 'Cannot consume scoped service 'ApplicationDbContext' from singleton 'IRepository'.'
Hence why I stated above that DbContext still gets Scoped service lifetime.

Unable to access services from DI on delegate in startup Configure?

I am trying to add some items to the piranha sitemap using the delegate method OnGenerateSitemaps.
In this method I am calling to a service that gets data from entity framework context and then caches it. Whenever I try to use this service in the delegate-method I get a error that the dbContext has already been disposed.
System.AggregateException: 'One or more errors occurred. (Cannot
access a disposed context instance. A common cause of this error is
disposing a context instance that was resolved from dependency
injection and then later trying to use the same context instance
elsewhere in your application. This may occur if you are calling
'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances.
I've tried making the service sync instead of async, I've tried awaiting the result and running the task sync, none of which works.
Any ideas on how to use my service in this delegate in Configure on startup?
services.AddScoped<ICachedSitemapService, CachedSitemapService>();
In startup I inject the service, which is scoped.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api, ICachedSitemapService cachedSitemapService)
{
App.Hooks.OnGenerateSitemap += (sitemap) =>
{
var items = await cachedSitemapService.GetCachedClinics().Result;
sitemap.AddRange(items);
return sitemap;
};
}
The service that is called is DbContext to get items:
private async Task<IEnumerable<SitemapItem>> GetSitemapClinics()
{
var spec = new ClinicSitemapSpecification();
//This throws error
var allClinics = await _clinicRepo.ListAsync(spec);
//code shortened for brevity, but returns a list.
}
I've tried below without any luck.
var items = await cachedSitemapService.GetCachedClinics().GetAwaiter().GetResult();
sitemap.AddRange(items);
We're (the Piranha team) planning on redesigning the hook system in version 10 (has to be a major version as it will break backward compatibility) to provide DI functionality in all hooks. However, in the meantime, the following should fix your issues.
using (var scope = app.ApplicationServices.CreateScope())
{
var service = scope.ServiceProvider.GetService< ICachedSitemapService>();
}
Since your service is registered to be scoped it can't be resolved directly from the root provider (like the error states). When resolving the service from a controller you are in the request scope, so the solution here is to just create a scope that will allow you to resolve it.

Cleaner way to choose LifetimeScope in a dependency chain with Autofac

I'm using a web app with Autofac injecting services into controllers. Those services are sometimes injected with other services, and repositories. Repositories are injected with DbContexts. These 3 layers (service, repository, context) are all registered with Autofac. My default lifetime for these is InstancePerLifetimeScope.
Unfortunately, I have some code in a specific controller that I want to execute in parallel threads. Since DbContext is not thread-safe, this means I need to give a factory method to each thread to resolve a Service in a per dependency lifetime scope, which in turn will need to resolve per dependency repositories and db contexts.
The options I am considering are to create a new lifetime scope per thread, or to use a separate registration using a named or keyed registration to resolve the per-dependency services.
The challenge with creating a new lifetime scope per thread is that I need access to some per-scope objects. Some objects would need to be inherited and to not have a new instance created in the new scope, but other objects (the non-thread-safe DbContexts) need to have new instances generated in the new scope. I have no idea how to control this behavior implicitly when creating my new lifetime scope.
The other method would be to use a registration key so that when I execute the factory method to resolve a service on each thread, it would resolve one in the per-dependency scope. This would work if the service had no dependencies, but since it depends on a bunch of repositories or services for which the default lifetime scope is set to InstancePerLifetimeScope, I have to write something like this:
builder.RegisterType<MyService>()
.As<IMyService>()
.Named<IMyService>(RegistrationKeys.PerDependency)
.WithParameter(new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(IMyRepository),
(pi, ctx) => ctx.ResolveNamed<IMyRepository>(RegistrationKeys.PerDependency))
).InstancePerDependency();
Since the repositories depend on the DbContext, each repository has to be registered separately using this registration name. And it needs to be configured to resolve the DbContext using the registration name. And the DbContext needs to be registered using the registration name.
With 10 services each using about 4-5 repositories, I wager the amount of boilerplate registration code I will have to write will be around 10-20 full pages. It's not going to be maintainable.
So my question is, is there a way to create a specific type of lifetime scope that will allow me to easily control which objects will have a new instance or which will be inherited from the parent lifetime scope that won't break the asp.net per-request lifetime scope?
Or is there a way I can register or resolve a service to explicitly resolve all of its dependencies in the same scope without relying on their default registrations and without having to hard code an entire second set of registrations for everything?
The challenge with creating a new lifetime scope per thread is that I need access to some per-scope objects. Some objects would need to be inherited and to not have a new instance created in the new scope, but other objects (the non-thread-safe DbContexts) need to have new instances generated in the new scope. I have no idea how to control this behavior implicitly when creating my new lifetime scope.
This is the challenge InstancePerRequest solve. You can create child scope and object scoped to Request will be shared amongst child scope. To do this, tagged lifetimescope and InstancePerMatchingLifetimeScope is used.
You can see InstancePerRequest and Tagging a lifetime scope in the official documentation.
Example :
builder.RegisterType<Service>().As<IService>().InstancePerMatchingLifetimeScope("KEY");
builder.RegisterType<DbContext>().InstancePerLifetimeScope();
// ...
using (ILifetimeScope scope = container.BeginLifetimeScope("KEY"))
{
scope.Resolve<IService>(); // Instance #1
using (ILifetimeScope childScope = scope.BeginLifetimeScope())
{
childScope.Resolve<DbContext>();
childScope.Resolve<IService>(); // shared instance (#1)
}
}
but that's mean you have to change all your InstancePerLifetimeScope to InstancePerMatchingLifetimeScope and can control the creation of the unit of work lifetime scope which can be quite difficult.
Another way of doing this is by using Owned<T> with Func<T>. You can get more information here : Owned instance
builder.RegisterType<Service>().As<IService>().InstancePerLifetimeScope();
builder.RegisterType<DbContext>().InstancePerLifetimeScope();
builder.RegisterType<Operation>().As<IOperation>().InstancePerLifetimeScope();
public class Operation : IOperation
{
public Operation(Func<Owned<DbContext>> contextFactory, IService service)
{
this._contextFactory = contextFactory;
this._service = service;
}
private readonly Func<Owned<DbContext>> _contextFactory;
private readonly IService _service;
public void Do()
{
using Owned<DbContext> context = this._contextFactory();
context.Value // => new instance
this._service // shared instance (#1)
}
}
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
scope.Resolve<IService>(); // Instance #1
IEnumerable<IOperation> operations = scope.Resolve<IEnumerable<IOperation>>();
operations.AsParallel()
.ForAll(operation => operation.Do());
}
The only downside of this solution is that your service will have dependency on Autofac but if you don't want it, it is quite easy to create your own abstraction over Owned
If you don't want to use Owned<T> or your own abstraction instead of trying to make DbContext a special case you can reverse the problem and manually share some dependency between your custom scope.
Something like :
using ILifetimeScope childScope = scope.BeginLifetimeScope(b => {
b.Register<XContext>(c => scope.Resolve<XContext>()).ExternallyOwned();
});
var operation = childScope.Resolve<IOperation>();
operation.Do();
This way IOperation would be resolved in a new scope but XContext will be from parent scope

Should EF6 DbContext be injected as Scoped or Transient if transactions need to be executed both in batches and asynchronously individually?

About 2 years ago, we made the change from ADO.net over to Entity Framework 6. Initially, we simply instantiated our DbContexts where we needed them. However, at some point we started down the path of prepping for implementing Dependency Injection in the solution. As such, our DbContexts were injected into our MVC controller constructors, and then the necessary logic classes were instantiated directly using the DbContexts. For awhile, this worked great as we had certain IRepository implementations that allowed us to manipulate dozens of entities across multiple repositories, and save them all with a single SaveChanges call.
However, over time, we've started to adapt a more purist DI approach where all our new classes are being injected (rather than instantiated). As a side-effect, we've started moving away from repositories and towards using EF as just a core repository across our solution. This has lead to us building modules in our application that perform their unit of work and save their changes. So rather than having dozens of repositories being used and accessed to perform an operation, we simply use the DbContext.
Initially, this worked out alright as we were injecting our DbContexts as scoped, and the functionality was unchanged. However, with the move towards more self-contained, self-saving modules, we've encountered concurrency errors with our new functionality. We managed to solve the concurrency issues by switching the DI configuration for our DbContexts over to transient. This presented each self-contained module with a new DbContext and they were able to execute and save without caring what the other modules were doing.
However, switching the DbContexts over to transient had the unfortunate side-effect of making it impossible to switch our legacy modules over to our DI container as they relied on a singular shared DbContext across all of their injected dependencies.
So my main conundrum is whether we should make our DbContexts Scoped or Transient. And if we do settle on scoped, how do we write our new modules so that they can execute in a parallel way? And if we settle on transient, how can we preserve the functionality in our dozens of legacy classes that are still developed and used?
Scoped
Pros
Single DbContext per request. No worries about entities being tracked in different contexts, and saves can be done wholesale.
Legacy Code does not need any major changes to be switched to DI.
Cons
Unrelated tasks can't execute concurrently using the same context.
Developers must constantly be aware of the state of the current context. They need to be wary of any side-effects from other classes utilizing the same context.
System.NotSupportedException: 'A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.' thrown during concurrent operations.
Transient
Pros
New DbContext per class. No worries about locking context while performing most operations on the context.
Modules become self-contained and you don't need to worry about side-effects from other classes.
Cons
Receiving an entity from one context and attempting to use it in a different context instance can cause errors.
No ability to perform batch operations across multiple different classes sharing the same context.
Here is a demo algorithm to force a concurrency error for a scoped context. It presents a possible use-case for the transient injection.
// Logic Class
public class DemoEmrSaver
{
private readonly DbContext_dbContext;
public DemoEmrSaver(DbContext dbContext)
{
_dbContext = dbContext;
}
public Task CreateEmrs(int number)
{
Contract.Assert(number > 0);
for (var i = 0; i < number; i++)
CreateEmr();
return _dbContext.SaveChangesAsync();
}
private void CreateEmr()
{
var emr = new EMR
{
Name = Guid.NewGuid().ToString()
};
_dbContext.EMRs.Add(emr);
}
}
// In a controller
public async Task<IActionResult> TestAsync()
{
// in reality, this would be two different services.
var emrSaver1 = new DemoEmrSaver(_dbContext);
var emrSaver2 = new DemoEmrSaver(_dbContext);
await Task.WhenAll(emrSaver1.CreateEmrs(5), emrSaver2.CreateEmrs(5));
return Json(true);
}
And here is a demo of how the older services often functioned
public class DemoEmrSaver
{
private readonly DbContext _dbContext;
public DemoEmrSaver(DbContext dbContext)
{
_dbContext = dbContext;
}
public void CreateEmrs(int number)
{
Contract.Assert(number > 0);
for (var i = 0; i < number; i++)
CreateEmr();
}
private void CreateEmr()
{
var emr = new EMR
{
Name = Guid.NewGuid().ToString()
};
_dbContext.EMRs.Add(emr);
}
}
// controller action
public async Task<IActionResult> TestAsync()
{
var emrSaver1 = new DemoEmrSaver(_dbContext);
var emrSaver2 = new DemoEmrSaver(_dbContext);
emrSaver1.CreateEmrs(5);
emrSaver2.CreateEmrs(5);
await _catcContext.SaveChangesAsync();
return Json(true);
}
Is there some sort of middle ground that won't require massive overhauls to the old code, but that still enables my new modules to be defined and utilized in a simple way (e.g. avoiding having to pass a Func of some sort into each constructor to get a new instance, and avoid having to specifically a request a fresh DbContext everywhere I need one?
Also probably important, I'm using the .Net Core DI Container from the Microsoft.Extensions.DependencyInjection namespace.
Why not to use an artificial scopes were you have this difficulties?
For example, we have some background services in our codebase, when they are used inside a normal AspNet core web app, as you say, the context are bounded to the requests, but for our console apps, we do not have the concept of scoped, so we have to define it ourselves.
To create an artificial scope, simply inject an IServiceScopeFactory, then, everything inside will utilize the new, separated context.
public class SchedulerService
{
private readonly IServiceScopeFactory _scopeService;
public SchedulerService(IServiceScopeFactory scopeService)
{
_scopeService = scopeService;
}
public void EnqueueOrder(Guid? recurrentId)
{
// Everything you ask here will be created as if was a new scope,
// like a request in aspnet core web apps
using (var scope = _scopeService.CreateScope())
{
var recurrencyService = scope.ServiceProvider.GetRequiredService<IRecurrencyService>();
// This service, and their injected services (like the context)
// will be created as if was the same scope
recurrencyService.ProcessScheduledOrder(recurrentId);
}
}
}
This way you can control the lifetime of the scoped services, helping you to share the same context inside that block.
I would recommend to create just one service this way, and then inside the service program everything as normal, this way your code will be keep clean and easier to read, so, do like the example:
using (var scope = _scopeService.CreateScope())
{
var recurrencyService = scope.ServiceProvider.GetRequiredService<IRecurrencyService>();
// In this service you can do everything and is
// contained in the same service
recurrencyService.ProcessScheduledOrder(recurrentId);
}
Please do not add complex code inside the using, something like
using (var scope = _scopeService.CreateScope())
{
var recurrencyService = scope.ServiceProvider.GetRequiredService<IRecurrencyService>();
var otherService= scope.ServiceProvider.GetRequiredService<OtherService>();
var moreServices = scope.ServiceProvider.GetRequiredService<MoreServices>();
var something = recurrencyService.SomeCall();
var pleaseDoNotMakeComplexLogicInsideTheUsing = otherService.OtherMethod(something);
...
}
EDIT
My fear with this approach is that it's applying a Service Locator
pattern, and I've often seen that dismissed as an anti-pattern where
DI is concerned
An anti-pattern would be to use this as normal work, but I am suggesting to introduce it in just one part, there are limits and constraints to what DI can do and can help you with your problems.
For example, property injection (no constructor injection) are also a code smell, but it is not banned or deleted of the framework, because in some cases is the only solution, or the most simple, and keeping things simple is more important than keep all the good practices (even best practices are not white or black, sometimes you will have to do trade-offs between follow one or other principle).
My solution should be in one part of your program, not for everything, that is why I recommend to create just one service, and from there make all the services, you can not use constructor injection to break the scoped life cycle, so IServiceScopeFactory exists just for that.
And sure, it is not for general use, but to help with lifecycle problems like you have.
If you are worried about calling GetService<SomeClass> you can create an abstraction to keep your code clean, for example, I created this general service:
public class ScopedExecutor
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ILogger<ScopedExecutor> _logger;
public ScopedExecutor(
IServiceScopeFactory serviceScopeFactory,
ILogger<ScopedExecutor> logger)
{
_serviceScopeFactory = serviceScopeFactory;
_logger = logger;
}
public async Task<T> ScopedAction<T>(Func<IServiceProvider, Task<T>> action)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
return await action(scope.ServiceProvider);
}
}
public async Task ScopedAction(Func<IServiceProvider, Task> action)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
await action(scope.ServiceProvider);
}
}
}
Then i have this extra layer (you could make this in the same class as the previous)
public class ScopedExecutorService<TService>
{
private readonly ScopedExecutor _scopedExecutor;
public ScopedExecutorService(
ScopedExecutor scopedExecutor)
{
_scopedExecutor = scopedExecutor;
}
public Task<T> ScopedActionService<T>(Func<TService, Task<T>> action)
{
return _scopedExecutor.ScopedAction(serviceProvider =>
action(
serviceProvider
.GetRequiredService<TService>()
)
);
}
}
Now, where you need your services as a separated context, you can use it something like this
public class IvrRetrieveBillHistoryListFinancingGrpcImpl : IvrRetrieveBillHistoryListFinancingService.IvrRetrieveBillHistoryListFinancingServiceBase
{
private readonly GrpcExecutorService<IvrRetrieveBillHistoryListFinancingHttpClient> _grpcExecutorService;
public IvrRetrieveBillHistoryListFinancingGrpcImpl(GrpcExecutorService<IvrRetrieveBillHistoryListFinancingHttpClient> grpcExecutorService)
{
_grpcExecutorService = grpcExecutorService;
}
public override async Task<RetrieveBillHistoryListFinancingResponse> RetrieveBillHistoryListFinancing(RetrieveBillHistoryListFinancingRequest retrieveBillHistoryListFinancingRequest, ServerCallContext context)
{
return await _grpcExecutorService
.ScopedLoggingExceptionHttpActionService(async ivrRetrieveBillHistoryListFinancingHttpClient =>
await ivrRetrieveBillHistoryListFinancingHttpClient
.RetrieveBillHistoryListFinancing(retrieveBillHistoryListFinancingRequest)
);
}
}
As you see, no service.GetService is called in the business code, just in one place in our toolkit

Categories