Re: Multitenancy - injecting EF Code First context based on user login

1,094 views
Skip to first unread message

Travis Illig

unread,
Apr 24, 2013, 1:55:23 PM4/24/13
to aut...@googlegroups.com
It does sound like you have a chicken/egg problem - if you need the tenant info before the user logs in but the tenant is determined by the user login, that's a problem.

What I've found works for me:
  • Make the tenant ID strategy aware of authenticated vs. anonymous context.
  • When authenticated, the tenant ID can come from a user-related property (a claim, a role, etc.).
  • When anonymous, the tenant ID needs to come from somewhere user-agnostic. For a web app, the Host header works nicely, or the domain name of the current request. That means you can have tenant1.yourapp.com or tenant2.yourapp.com and it can figure things out from there.
You also mentioned you have an EF context that's getting injected... somewhere... at app startup. Might need more information on that - I'm not an EF user.

However, if I were to treat it just like any other dependency, I'd say this: application startup inherently has to run as a tenant-agnostic thing. You can't really do anything that is tenant-specific at app startup (unless you're registering tenants, or iterating through all of your tenants running configuration for each, etc.). For example, you couldn't somehow fire up an MVC controller that needs to run in a tenant-specific way or has tenant overrides because, as you noticed, you don't have a way to determine which tenant is coming in.

If you're trying something that requires tenant-specific behavior during app startup, that's a design problem you'll need to address. If you're trying to "warm start" database connections, for example, you'd need to foreach over the set of tenant IDs and do that. I don't have specific guidance on how to do that; it'd be very app-specific. Most likely this would be something you need to do outside of the Autofac container. There's nothing built-in currently that provides such a feature. You might try looking at Startable components, but I don't think that'll work at the tenant level, just the application container level.

Hope that helps,
-T

On Wednesday, April 24, 2013 8:08:53 AM UTC-7, zam6ak wrote:
Hi

I am trying to figure out how to use multitenancy with Autofac based on following scenario

  • 1 app deployment : Many tenant databases
  • ASP.NET MVC + Web API (single app)
  • MS SQL (database per tenant)
  • tenant in this case is an Enterprise (db isolation) which has many Companies (table/row isolation)
  • Centralized authentication - decentralized authorization (
  •     Authentication - LDAP user which "belongs to" Enterprise (has Enterprise Id claim/group)
  •     Authorization - roles defined in the tenant's database 

What I would like to do is to be able to inject Entity Framework context with different connection string (different database) based on user login.
After user logs in, his Enterprise Id will determine which databse EF context should be used....

But I think I am having "chicken before egg" problem....
When container is built (on Application Startup) user has not logged in yet (there is not even HttpContext yet, let alone forms auth claims...) so tenant id strategy fails....
I would like to "delay" the injection of db context after the login

Any ideas on how to approach this?

Thanks
Z...


Travis Illig

unread,
Apr 25, 2013, 12:46:29 PM4/25/13
to aut...@googlegroups.com
I'm not recommending using Host headers specifically, but you can use them in IIS with only one app deployment. Multiple host names pointing to a single IIS app instance. It works great, I do it all the time.

The point is, though, that app startup has to be tenant agnostic, as you've seen.

Maybe you can do something with the application initialization module to warm start your connections? There are a few options, but, unfortunately, it will mean some app design and code on your part, not something Autofac can solve "for you" as it were.

Good luck,
-T

On Wednesday, April 24, 2013 11:39:11 AM UTC-7, zam6ak wrote:
Travis thanks for the reply....

The only dependency regarding tenants I have is database connection which is "wrapped" by EF code first context.
Usually this comes from web.config via connection strings property...

Having host headers does not solve my problem because I really do not want to deploy the same app multiple times.
If that was the case, I could just include appropriate connection string and have no need for multitenant autofact plugin (I guess)
But with 300+ tenants, having single app deployment is a must...They can share app instance (which I can scale) while having DB isolation....
So security based tenant id (role, claim)  is the only thing I can think of to insure right user connects to the right db....

I could start 300+ contexts on startup and put them in dictionary, and then pull out the correct one after user logs in but I am not sure that is scallable/performant.
It would also mean re-deploying the app every time new tenant comes on board (editing web.config to add new conn string)...

Tricky....

Alex Meyer-Gleaves

unread,
Apr 26, 2013, 9:26:23 AM4/26/13
to aut...@googlegroups.com
Hi zam6ak,

I haven’t used EF for a long time but I imagine that you would want your context to follow the Unit of Work pattern. Having multiple contexts floating around would not be good for scalability and I doubt they are thread safe for access by concurrent requests.

If the only part of your architecture that you want to be tenant specific is the EF context I would wrap that in an InstancePerHttpRequest service and dynamically build the connection string based on something in the incoming request. You can use just the one connection string in your configuration file and utilize the appropriate connection string builder to alter the tenant specific portion (such as Initial Catalog).

The following code snippet should at least give you a feel for the concept:

builder.Register(c => new TenantSpecificContext(GetConnectionString())).InstancePerHttpRequest();

static string GetConnectionString()
{
var defaultConnectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
var connectionString = new SqlConnectionStringBuilder(defaultConnectionString)
{
InitialCatalog = HttpContext.Current.Request.Headers["TennantId"] // Or something similar from the request.
};
return connectionString.ToString();
}

That will give you a context for each HTTP request with a tenant specific connection. You might need to wrap the context in a disposable service to commit any transactions etc. but that should be easy.

Cheers,

Alex.
Reply all
Reply to author
Forward
0 new messages