Health Check using DBContext that is still being configured - c#

I created a custom Health Check that calls an injected service, and that service uses a DbContext to query the DB to get some info. When I launched my application I get the following error:
An attempt was made to use the context while it is being configured. A
DbContext instance cannot be used inside OnConfiguring since it is
still being configured at this point. This can happen if a second
operation is started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
Is there a way to delay the health check until the DbContext is registered somewhere in the startup?
Below is my health check implementation.
public class HealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{
int userCount = dbService.GetUserCount(); // fails in the dbService here
if (userCount > 0)
return Task.FromResult(HealthCheckResult.Healthy("A healthy result."));
return Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, "An unhealthy result."));
}
}
This is how it is registered in the startup after my dbcontext is registered via AddDbContext
services.AddHealthChecks().AddCheck<HealthCheck>("user_health_check");

You may be able to get around this by adding a DbContextCheck with a custom query? (docs)
My understanding is that you can do something like this:
services.AddHealthChecks()
.AddDbContextCheck<YourDbContext>(customTestQuery:
(db, cancel) => Task.FromResult(db.Users.Any()));
With that said, you may have a concurrency problem here with how your DbContext is being used. Perhaps there's an async call being made that isn't awaited, or maybe there's something wrong with how your context lifetime is configured.
Without knowing the details of how you're registering and configuring your dbcontext or how it's being injected (or not) into what looks like a repository (DbService.GetUserCount()) I can point you toward some additional documentation about avoiding DbContext threading issues and hope it is useful.

Related

Using DbContextFactory (with a new DbContext per service method/operation) when working with a Complex Data Model

Update: The threading issues were caused as a result of ApplicationDbContext being registered as Scoped, and services being
registered as Transient. Registering my ApplicationDbContext as
transient has fixed the threading issue. However: I do not want to lose the Unit of Work and Change Tracking funcionality. Instead I now keep the ApplicationDbContext as Scoped, and fix the issue by using Semaphore to prevent simultaneous calls, as explained in my answer here: https://stackoverflow.com/a/68486531/13678817
My Blazor Server project uses EF Core, with a complex database model (some entities having 5+ levels of child entities).
When the user navigates to a new component from the nav menu, the relevant entities are loaded in OnInitializedAsync (where I inject a service for each entity type). Each service is registered as Transient in startup. The loaded entities are manipulated in this component, and in its child/nested components.
However, this approach resulted in threading issues (different threads concurrently using the same instance of DbContext) when the user would navigate between components while the previous component's services are still loading entities (...a second operation has started...).
Following is the simplified original code causing this error.
Component 1:
#page : "/bases"
#using....
#inject IBasesService basesService
#inject IPeopleService peopleService
<h1>...//Code omitted for brevity
#code{
List<Bases> bases;
List<Person> people;
protected override async Task OnInitializedAsync()
{
bases = await basesService.GetBasesAndRelatedEntities();
people = await peopleService.GetPeopleAndRelatedEntities();
}
Component 2:
#page : "/people"
#using....
#inject IBasesService basesService
#inject IPeopleService peopleService
<h1>...//Code omitted for brevity
#code{
List<Person> people;
protected override async Task OnInitializedAsync()
{
people = await peopleService.GetPeopleAndRelatedEntities();
}
Furthermore, all services have this structure, and are registered in startup as transient:
My BasesService:
public interface IBasesService
{
Task<List<Base>> Get();
Task<List<Base>> GetBasesAndRelatedEntities();
Task<Base> Get(Guid id);
Task<Base> Add(Base Base);
Task<Base> Update(Base Base);
Task<Base> Delete(Guid id);
void DetachEntity(Base Base);
}
public class BasesService : IBasesService
{
private readonly ApplicationDbContext _context;
public BasesService(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<Base>> GetBasesAndRelatedEntities()
{
return await _context.Bases.Include(a => a.People).ToListAsync();
}
//...code ommitted for brevity
DbContext is registered as follows:
services.AddDbContextFactory<ApplicationDbContext>(b =>
b.UseSqlServer(
Configuration.GetConnectionString("MyDbConnection"), sqlServerOptionsAction: sqlOptions =>
{ //Updated according to https://dev-squared.com/2018/07/03/tips-for-improving-entity-framework-core-performance-with-azure-sql-databases/
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null);
}
));
Now, my user can switch between /bases and /people by using the nav menu. If they do this quickly, then the other component's await peopleService.GetPeopleAndRelatedEntities(); gets called before the previous component's await peopleService.GetPeopleAndRelatedEntities(); has finished, and this causes an error as follows:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (9ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [**Sensitive DB statement ommitted**]
fail: Microsoft.EntityFrameworkCore.Query[10100]
An exception occurred while iterating over the results of a query for context type '[**ommitted**].Data.ApplicationDbContext'.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
dbug: Microsoft.Azure.SignalR.Connections.Client.Internal.WebSocketsTransport[12]
Message received. Type: Binary, size: 422, EndOfMessage: True.
dbug: Microsoft.Azure.SignalR.ServiceConnection[16]
Received 422 bytes from service 468a12a0...
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
Unhandled exception rendering component: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at [**path to service ommitted**]...cs:line 35
at [**path to component ommitted**].razor:line 77
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit 'cvOyWXdG_oikG_YJe2ehrsHsI3VQDJw2U8YIySmroTM'.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at [**path to service ommitted**].cs:line 35
at [**path to component ommitted**].razor:line 77
I read through everything Stack Overflow and MS docs has available regarding this topic, and adapted my project according to the recommended approach of using DbContextFactory:
Blazor concurrency problem using Entity Framework Core
https://stackoverflow.com/a/58047471/13678817
Let's say I have the following DB model:
Each AlphaObject, has many BetaObjects, which has many CharlieObjects. EachCharlieObject has 1 BetaObject, and each BetaObject has 1 AlphaObject.
I adapted all services to create a new DbContext with DbContextFactory, before each operation:
public AlphaObjectsService(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<List<AlphaObject>> GetAlphaObjectAndRelatedEntities()
{
using (var _context = _contextFactory.CreateDbContext())
return await _context.AlphaObjects.Include(a => a.BetaObjects).ThenInclude(b => b.CharlieObjects).ToListAsync();
}
Where before, I would load a List<AlphaObject> alphaObjects, and include all related BetaObject entities (and in turn their related CharlieObject entities) in the service, I can then later load a list of List<BetaObject> betaObjects, and without explicitly including their related AlphaObject or CharlieObjects, it would already be loaded if it was loaded before.
Now, when working with a 'DbContext per Operation' - many of my related entities are null if I don't load them again explicitly. I am also worried about manipulating entities & their related entities, and updating all these changes, without the normal lifetime of a DbContext with Change Tracking. EF Core documentation states that the normal lifetime of a DbContext should be:
Create the DbContext instance
Track some entities
Make some changes to the entities
Call SaveChanges to update the database
Dispose the DbContext instance.
In order to solve my threading (concurrent access of the same DbContext) errors, but continue to manipulate my entities in the manner that EF Core was intended to be used:
Should I extend the lifetime of my DbContext to the lifetime of my main component in which the entities are loaded from the database?
In this way, after loading one entity, with all its related entities, I don't need to load all already loaded entities for another entity type. I would also have other benefits such as Change Tracking for the lifetime of the component, and all changes to tracked entities will be saved when calling context.SaveChangesAsync(). However, I will need to manually dispose each context created with DbContextFacoty.
As per MS Docs, it seems I would have to access the database directly from the component in order to implement IDisposable (I will need to create the Context directly in my component, and not in a service - as with the MSDocs sample app: https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0). Is it really optimal/recommended to create and access the DbContext directly from within components? Otherwise, instead of implementing IDisposable, would using OwningComponentBase have the exact same capability to dispose the context after the component's lifetime ends, except I can use my existing services with this?
Can I continue to dispose my new DbContext after each service operation - using (var _context = _contextFactory.CreateDbContext())?
Then, must I simply ensure that each time I load a different entity type, I should also load all the required related entities again? I.e. return await context.AlphaObject.Include(a => a.BetaObject).ThenInclude(b => b.CharlieObject).ToListAsync();, and when loading a list of CharlieObject, again I should explicitly include BetaObject and AlphaObject? Will I be able to still make changes to AlphaObject and it's related Beta- and CharlieObjects throughout my child components, and then when finished with all the changes, will making context.Entry(AlphaObject).State = EntityState.Modified & calling context.SaveChangesAsync() also update changes that were made to the BetaObjects and CharlieObjects that are related to the AlphaObject? Or would one need to change the state of each entity to EntityState.Modified?
In short, I would love to understand the correct way to ensure related entities are loaded (and manipulated & updated) properly when working outside a single DbContext lifetime, as this seems to be the recommended approach. In the meantime I will go ahead an adapt my project to use new context per service operation, and continue to 'learn-as-I-go-along'. I will update this question as I learn more.

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.

Entity Framework Core - disposed when trying to run 2nd Query

I have an issue with a database context being disposed. I have set up the databases like the below in the Configure services method. The code has been simplified to hopefully make it easier to read.
public void ConfigureServices(IServicesCollection services)
{
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database1")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database2")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database3")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database4")));
Services.AddScoped<IQueryService1, ConcreteService1>();
Services.AddScoped<IQueryService1, ConcreteService2>();
Services.AddScoped<IQueryService1, ConcreteService3>();
Services.AddScoped<IQueryService1, ConcreteService4>();
}
Now in one of the controllers I inject the relevant services that are required.
[Produces("application/json")]
[Route("api/[controller]/[action]
public class DigitalFinesController : Controller
{
private readonly IQueryService1 _Service1;
public DigitalFinesController(IConfiguration configuration, IQueryServices1 QueryService1)
{
_Service1 = QueryService1;
}
[Authorize]
[HttpPost]
[ActionName("SubmitFine")]
[ProducesResponseType(200)]
[ProducesResponseType(401)]
public async Task<IActionResult> SubmitFine([FromBody] Models.DigitalFine fine)
{
//This is a simple version of my issue
var vehicles = _Service1.Vehicles.FirstOrDefault(p=> p.vrm == "OX7 DFG");
if(vehicle == null)
{
return BadRequest("Vehicle is missing");
}
var fleet = _Service1.Fleets.FirstOrDefault(p=> p.Code = "MyCode");
}
}
And once I get to the second query I get the following exception
System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
And I am stumped to understand why this is happening. Can anyone please give me a pointer to fix this?
Many thanks
Simon
I think it may have to do with how you're registering it. Try registering it with AddSingleton instead of AddScoped
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes-and-registration-options
Scoped
Scoped lifetime services are created once per request.
Warning
When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don't inject via constructor injection because it forces the service to behave like a singleton. For more information, see ASP.NET Core Middleware.
Singleton
Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance. If the app requires singleton behaviour, allowing the service container to manage the service's lifetime is recommended. Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.
Warning
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
.AddScoped will dispose after lifetime of the request, try changing to singleton or transient:
Please see documentation:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
At this section:
In the sample app, the IMyDependency service is registered with the
concrete type MyDependency. The registration scopes the service
lifetime to the lifetime of a single request. Service lifetimes are
described later in this topic.
Warning
When using a scoped service in a middleware, inject the service into
the Invoke or InvokeAsync method. Don't inject via constructor
injection because it forces the service to behave like a singleton.
For more information, see ASP.NET Core Middleware.

How to transfer DI requestscope to an other thread?

Context:
I am using DI in my Web application. (I am using NInject, but hopefully this should not matter)
Some places constructor injection is not possible, for example in my custom log4net database logger (that's not me, who instantiates my custom logger instead the log4net framework). So I am using my DI container there in service locator DP mode, and asking an instance resolve explicitly in the logger code.
Note this is just a sample, in many other cases I had to use NInject as service locator DP instead of constructor injection.
Now the problem:
I have an IAuditContextProvider which serves current request's audit data, like IP etc. The question arises how I configure my DI container to instantiate a concrete provider. So far I've used a request scope (singleton by request) what is supported out of box by NInject.
However recently I faced the fact I had to start a background processing initiated by a request. This is done by
// This is 'outside' it's actually a request serving method running in the request context, btw it is an MVC action method,
// Pseudo code:
var auditProvider = Locator.Resolve<IAuditProvider>()
Task.Run(() =>
{
// I would like to get the very same resolved auditProvider instance here as outside.
// Please note: outer local variables are not solution, because of implicit calls here inside, for example if there is a logging statement here, then the service locator in the custom logger must resolve the very same instance as outside
// Some how I must 'stamp' this thread to be the 'same' as the outside
// request thread in point of view of my custom scope resolver (see below)
}
Note: Configuring the DI container a wide scoped singleton are not solution because of multiple requests are server parallel, and they can not use a common auditProvider.
OK, I thought this is what for custom (resolving) scopes are for. Here is the pseudo code how I am configuring my DI container:
kernel
.Bind(typeof(IAuditContextProvider))
.To(typeof(WebAuditContextProvider)).InScope(dummy =>
{
// Here I have to return a very same object/number/id when in
// 'outside' the request thread, and inside the worker thread.
// This way I hopefully get the very same instance when resolving.
// To be short: I have no idea how?
});
I don't think there is a good answer for your question within the current bounds.
I do have an alternative suggestion - just perform the work synchronously in another process. This would require a form of inter-process communication (IPC) but shouldn't be too difficult.
A simple but effective form of IPC is just writing a record to a database table (acting like a queue) and having a windows service/daemon polling for new records to "process". In this example, you would put a record in the table with the contextual information (user id, etc) and the service would utilize this context to perform the work synchronously, but the workflow would be asynchronous to the Web UI.
This also has a nice side benefit: You can start to build monitoring, retry logic, etc into the service. These things are much harder to do reliably within an ASP.NET model.
You could forgo the database queue completely by using something like message queues/buses/events, but the basic concept is the same.
Update:
Did you try to use closures in C#? Like this:
var auditProvider = Locator.Resolve<IAuditProvider>()
Task.Run(() =>
{
// with closure you'll get that very variable you need:
auditProvider.SomeMethod();
}
You should read whole article about closures by John Skeet and how they can help you together with TPL.
Other useful information:
Such DI is being called as Ambient Context in famous book Dependency Injection by M. Seeman:
A truly universal CROSS-CUTTING CONCERN can potentially pollute a large part of the API for an application if you have to pass an instance around to every collaborator. An alternative is to define a context that’s available to anyone who needs it and that can be ignored by everyone else.
The AMBIENT CONTEXT is available to any consumer via a static property
or method. A consuming class might use it like this:
public string GetMessage() { return SomeContext.Current.SomeValue; }
In this case, the context has a static Current property that a consumer can access. This property may be truly static, or may be associated with the currently executing thread. To be useful in DI scenarios, the context itself must be an ABSTRACTION and it must be possible to modify the context from the outside—in the previous example, this means that the Current property must be writable. The context itself might be implemented as shown in the following listing.
The context is an abstract class, which allows us to replace one context with another implementation at runtime.
public abstract class SomeContext
{
public static SomeContext Current
{
get
{
// Get current context from TLS
var ctx = Thread.GetData(Thread.GetNamedDataSlot("SomeContext")) as SomeContext;
if (ctx == null)
{
ctx = SomeContext.Default;
Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), ctx);
}
return ctx;
}
set
{
Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), value);
}
}
public static SomeContext Default = new DefaultContext();
public abstract string SomeValue { get; }
}
TLS here stands for Thread Local Storage, which can be useful idea for you here.
Also I suggest you to read about OperationContext class, which can be helpful for you if you want to pass some context for your Task, something like this:
// save current context before task start
var operationContext = OperationContext.Current;
Task.Run(() =>
{
// set current operation context inside your Task with closure
OperationContext.Current = operationContext;
// Your work here
}

Categories