I'm developing a multi-tenant application in .NET and I've run into an issue where I need to access a scoped service, ITenantProvider, from within a custom middleware, TenantMiddleware. However, when I try to run my application, I receive the following error:
Cannot resolve scoped service 'MyNameSpace.ITenantProvider' from root provider.
Here is the middleware:
public class TenantMiddleware
{
private readonly RequestDelegate _next;
private readonly ITenantProvider _tenantProvider;
public TenantMiddleware(ITenantProvider tenantProvider, RequestDelegate next)
{
_tenantProvider = tenantProvider;
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Logic to set tenant ID
await _next(context);
}
}
Startup config:
services.AddScoped<ITenantProvider, TenantProviderImplementation>();
builder.Services.AddTransient<IDBService, DBService>();
app.UseMiddleware<TenantMiddleware>();
The goal of the tenant ITenantProvider is to be set with a tenant Id (as a scoped) and the to be consumed by other services while it is injected as scoped to a transient db services.
public interface ITenantProvider
{
public Guid GetTenantId();
public void SetTenantId(Guid tenantId);
}
The DBService has the tenantId provider as a dependancy:
DbService(ITenantProvider tenant) { ... }
The most imprtant thing that the method DbLogic will be able to get from the injected ITenantProvider
the tenantId via the method GetTenantId()
that is correspondent with the one-time-called method (via the middleware) of SetTenantId(Guid tenantId)
DbLogic()
{
Guid tenantId = _tenantProvider.GetTenantId();
}
I understand that the issue is related to trying to inject a scoped service (ITenantProvider
) into a middleware that is a singleton, but I'm not sure how to correctly structure this to avoid the error.
You can resolve it from the context in the Invoke
call (remove the ctor parameter):
public async Task Invoke(HttpContext context)
{
var tenantProvider = context.RequestServices.GetRequiredService<ITenantProvider>();
// Logic to set tenant ID
await _next(context);
}
Or inject it directly to the method parameters:
public async Task Invoke(HttpContext context, ITenantProvider provider)
{
// Logic to set tenant ID
await _next(context);
}