I having a hard time getting the Autofac.Extras.Multitenancy package to work to choose between two implementations of an interface.
I'm trying to connect to separate databases based on a tenant Id but my ITenantIdentificationStrategy
that I took from Autofac's example isn't getting HttpContext.Current
set so I can't get anything from the HTTP call to make a decision. I have two configurations like this.
var mtc = new MultitenantContainer(tenantIdStrategy, builder.Build());
mtc.ConfigureTenant(Guid.Parse("00000000-0000-0000-0000-000000000001"), b => b.RegisterInstance<DatabaseConnection>(connectionOne).As<IDatabaseConnection>().SingleInstance());
mtc.ConfigureTenant(Guid.Parse("00000000-0000-0000-0000-000000000002"), b => b.RegisterInstance<DatabaseConnection>(connectionTwo).As<IDatabaseConnection>().SingleInstance());
var resolver = new AutofacWebApiDependencyResolver(mtc);
config.DependencyResolver = resolver;
appBuilder.UseWebApi(config);
So I have two database connections I instantiate and I want those instances to be used for each tenant respectively
My tenant Id is pretty simple
public class RequestParameterStrategy : ITenantIdentificationStrategy
{
public bool TryIdentifyTenant(out object tenantId)
{
tenantId = null;
try
{
var context = HttpContext.Current;
if (context != null && context.Request != null)
{
tenantId = HttpContext.Current.Request.RequestContext.RouteData.Values["tenantId"];
}
}
catch (HttpException)
{
// Happens at app startup in IIS 7.0
}
return tenantId != null;
}
}
I added a simple controller to test this that should be within the ASP.NET pipeline.
public IHttpActionResult Get(IMongoDBAdapter adapter, string memberId= null) {
return Ok()
}
This is the example right out of Autofac's website. but HttpContext.Current is always null and tenant Id is not set and then it complains that my controller's aren't getting a DatabaseConnection
EDIT I think my problem might be that this project is not hosted in IIS. It's a self hosted windows service and even in the controller HttpContext.Current is null. Seems like this idea may not be possible with self hosted services?
You are correct - you won't get HttpContext
in a self hosted environment
You could try writing a custom DelegatingHandler
that sits first in your self-hosted request pipeline and sets a context variable in a way similar to the way ASP.NET Core has an HttpContextAccessor
Which is to say, your DelegatingHandler
would have to...
CallContext.LogicalSetData
Then your tenant ID strategy would read the value using CallContext.LogicalGetData
.
Look at how ASP.NET Core gets/sets HttpContext.Current
doing a very, very similar thing since ASP.NET Core also doesn't support HttpContext.Current
.
That's actually potentially some tricky code to get correct so I will put a very, very rough sample here but do not just copy/paste this and assume it's going to be 100% perfect. I'm not doing extensive testing on it, I'm just trying to give you an idea.
Here's what the handler might look like:
public class TenantIdentityHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var id = request.Headers.GetValues("X-Tenant-ID").FirstOrDefault();
CallContext.LogicalSetData("TenantIdentification" + AppDomain.CurrentDomain.Id, new ObjectHandle(id));
return await base.SendAsync(request, cancellationToken);
}
}
Here's what your ID strategy might look like:
public class CallContextStrategy : ITenantIdentificationStrategy
{
public bool TryIdentifyTenant(out object tenantId)
{
var handle = CallContext.LogicalGetData("TenantIdentification" + AppDomain.CurrentDomain.Id) as ObjectHandle;
var tenantId = handle?.Unwrap() as string;
return tenantId != null;
}
}
Again, I haven't tried this out so you may have to tweak it, but hopefully it will get you started.