0

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?

ThrowsException
  • 2,586
  • 20
  • 37
  • When/where are you resolving the object that's failing? (Please update the body of the question.) If it's not inside a request (e.g., the ASP.NET pipeline hasn't set HttpContext.Current yet) there's not much that can be done. That doc example is just an example and has more disclaimers around it than you can shake a stick at. You may have to do some modification for your personal needs to handle your own exceptional cases. – Travis Illig Jan 26 '17 at 17:31
  • [I updated the doc with _even more disclaimers_ to avoid confusion.](http://autofac.readthedocs.io/en/latest/advanced/multitenant.html) – Travis Illig Jan 26 '17 at 17:36
  • @TravisIllig thanks for the reply and the update to the documentation. However a big piece I didn't think to mention was that this is self hosted in a Windows Service. not through IIS. It seems HttpContext.Current will always be null so... bummer.... – ThrowsException Jan 26 '17 at 18:42

2 Answers2

2

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.

Community
  • 1
  • 1
Travis Illig
  • 23,195
  • 2
  • 62
  • 85
0

At the time of writing, and having .NET 6 in mind - there is Autofac.AspNetCore.Multitenant Nuget package required for Multitenant set up with Autofac, as described in Autofac documentation.

Samples are provided at this git repository specifying how it should be done. Basically IHttpContectAcessor is resolved in ConfigureMultitenantContainer method.

container.Resolve<IHttpContextAccessor>() 

and passed in the Strategy class, stored in a local variable and used to get the HttpContext when required.

HttpContextAccessor.HttpContext

There are samples for .NetCore 3-3.1 and 5-6.

The Nuget package and the samples:

Autofac.AspNetCore.Multitenant

fdhsdrdark
  • 144
  • 1
  • 13