I have a method where I need a new scope (must be a new scope, the scenario here doesn't need it, but when this works I'll use the logic elsewhere where it needs to be a separate scope), for this I use the IServiceScopeFactory (I think that's the right one). I then get the services I need out of the new scope and I expect them to still work in a scoped way. But dependencies in those services act like transient services. I always get a new one in the constructor.
Example:
public class EntryController : IEntryController
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IRequestContext _requestContext;
public EntryController(IServiceScopeFactory scopeFactory, IRequestContext requestContext)
{
_scopeFactory = scopeFactory;
_requestContext = requestContext;
}
public async Task GetEntries(int userId)
{
using var scope = _scopeFactory.CreateScope();
var requestContext = scope.ServiceProvider.GetRequiredService<IRequestContext>();
var manager = scope.ServiceProvider.GetRequiredService<IEntryManager>();
var test = requestContext // Completely new IRequestContext
requestContext = _requestContext;
var test1 = requestContext // test1 is the same as _requestContext, which is good
return await manager.GetAll();
}
}
public class EntryManager : IEntryManager
{
private readonly IEntryResource _entryResource;
private readonly IRequestContext _requestContext;
public EntryManager (IEntryResource entryResource, IRequestContext requestContext)
{
_entryResource = entryResource;
_requestContext = requestContext;
}
public async Task GetAll()
{
var test = _requestContext; // Completely new IRequestContext, which is bad
return await _entryResource.GetAll();
}
}
public class EntryResource : IEntryResource
{
private readonly IRequestContext _requestContext;
public EntryManager (IRequestContext requestContext)
{
_requestContext = requestContext;
}
public async Task GetAll()
{
var test = _requestContext; // Completely new IRequestContext, which is bad
// here is some code for the db query where I need info stored in the IRequestContext
return _dbContext.Entries.ToListAsync();
}
}
I understand why I get a new requestContext in the new scope, but when I update the values I would expect those to be available inside the whole scope through dependency injection. When I run the code without a new scope everything works fine. All services are added as scoped services in the startup.
services.AddScoped<IRequestContext, RequestContext>();
services.AddScoped<IEntryManager,EntryManager>();
services.AddScoped<IEntryResource, EntryResource>();
I found the issue:
requestContext = _requestContext;
This didn't work because requestContext
is no longer the same object as it was before this line and it will no longer be the same as the one injected to the other services within the scope.
If I simply assign new values to its properties it does work. this is why this works:
requestContext.Id = _requestContext.Id
That way it still is the same object and the new value of Id
can be retrieved everywhere in the new scope inside the requestContext
.
With a custom extension I can assign all properties.
public static IRequestContext CopyRC(this IRequestContext dest, IRequestContext source)
{
var props = typeof(RequestContext).GetProperties();
foreach(var prop in props)
{
var value = prop.GetValue(source);
prop.SetValue(dest, value);
}
return dest;
}
With this
requestContext = _requestContext;
now becomes
requestContext.CopyRC(_requestContext);
Thanks to Stephen Cleary for helping me look in the right direction with his working snippet.