Search code examples
c#autofac

Autofac Web Api multi tenant not getting HTTPContext.Current


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?


Solution

  • 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...

    • Read in the various request-based parameters you're interested in.
    • Do the tenant identification and store the value using 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.