Speeding up StackExchange is a good example of the motive and benefits to running multiple sites out of one instance of an ASP.NET app domain.
: Another good description is at Multi-tenancy in ASP.NET MVC – Why do we want it?
Not a new concept of course – the app domain itself is a framework-level implementation of multi-tenancy in a process. You could also say the process itself is an example of multi-tenancy in the operating system, and virtualized guest OS is multi-tenancy in the entire computer. You can see the pattern that forms, though, where each level you drop down has better isolation, and allows greater degree of variance between tenants, but comes at a higher cost in terms of resources or efficiency.
So it goes that multi-tenancy all the way up at the web application level should be the most efficient if you’re able to work with some limitations in freedom.
The most obvious limitation – everybody is running the same palette of code – isn’t even really the biggest issue. Being able to assume all of your tenants are on the same code, and that you can stage, test, and update all of them at a shot, could actually be a really nice benefit more than a problem. The real problems are singletons – anything static and stateful is a badness. This can be singleton extensibility points, or collections in libraries, or of course anything that’s in web config.
Your most powerful tool in avoiding places where your web application scrapes against (or introduces) a static and stateful point would be any of the modern inversion of control containers. An example of their use in that way is in the most recent development iteration of Orchard which gained the ability to host multiple sites in the same app domain.
The mechanics of spinning up per-tenant container: There’s a single IOrchardHost component created during startup from a root IoC container. That component’s job boils down to creating any number child IoC containers from the root, one for each active IOrchardShell instance, and rebuilding them when needed. The root container has the fewest number of components needed – just the ones required to create and build the shell containers.
The mechanics of hitting the right tenant: When a shell is built and activated it registers routes that have an association to its IoC container. And if there is a host header defined for the shell the route will only match those requests. At the point when a request comes in and hits a Route registered by one of the shells there is an established affinity to the IoC container for the rest of the request.
The mechanics of lightweight isolation: The rest is of the concerns all fall into place naturally. The controller factory draws the controller instance from the shell’s container – so only the components and dependencies which are enabled for that tenant are resolved. All of the configuration (right down to the settings on the database connection provider and the table name prefix) are specific to the shell. Many of the components rely on a singleton pattern to hold various aspects of long-lived state. Those components do always resolve as the same instance – to the best of their knowledge at least. In reality for multi-tenant purposes the singleton component pattern means the same instance will be returned for the lifetime scope of any given shell instance.
There are a few places you need to shim, like tenant-specific controller factory, or view engine concerns, or aspnet membership providers become lightweight classes that reach up into the request context to resolve and invoke the tenant-specific implementation of the capability in question. But other than that – the net effect – it all kind of works.