ASP.NET Core and Multi-Tenant Applications
Developing multi-tenant applications is a common requirement for developers and it basically means serving multiple web sites based on their host names from a single installation. Below is some information regarding how it should work and what the ASP.NET Core lacks.
The post below is taken from a comment I posted on GitHub. For joining the discussion, please visit the issue and have your say!
Resolve and initialize a tenant
Happens when a request to that tenant comes in for the first time. Not when you are starting up the Asp.Net app, not when you are configuring the services or the request pipeline. After all of these phases passed. Out of the blue. I might even add a new tenant configuration to my database and it should be ready to be initialized as soon as one makes a request to that tenant’s host name.
A tenant – in my humble opinion – is not really a tenant unless it can have its own collection of services and its own service provider (I handle this using some dirty way of cloning the original
IServiceProvider services). Consider your tenants being able to contain plugins. One tenant might have services related to it registered and others not.
These services should be isolated from each other and sometimes not based on the service. Let’s say, if you have a service registered as a singleton, it has cases where it really is a singleton and is shared among the tenant’s own service provider. However, there are cases where a singleton is a singleton within the scope of a tenant.
A tenant – again in my humble opinion – is not really a tenant unless it can have its own request pipeline. Again consider your tenants being able to contain plugins. One tenant might have a middleware registered within its request pipeline while others not.
Re-initialize a tenant
Happens when one calls
tenant.Reload(), let if be for having a new plugin added to the tenant or even having that tenant’s host name configured by the owner of the tenant. Could also be in case one removes a plugin from the tenant. So that the tenant’s service collection, service provider and request pipeline gets all created from the scratch.
Technically speaking, re-initializing is basically removing the tenant from a concurrent list of initialized tenants and having it initialized with the first upcoming request.
Un-initialize a tenant
Happens when a tenant is removed from the data store (be it a database or a configuration file) and that should really leave all other tenants intact and should not cause all other tenants to re-initialize.
Technically speaking, un-initializing is basically removing the tenant from a concurrent list of initialized tenants (of course disposing its services, etc for the sake of scaling and performance).
Back to Asp.Net
@HaoK, no pun intended for you. You are at least trying it. Please don’t take anything below this line personal. I don’t know if it is just me but 80% of the projects I have developed/lead/architected myself in the last 20 years have always required the multi-tenancy scenario. I personally – maybe @PinpointTownes remembers – fought for having Asp.Net Core support the multi-tenancy scenarios even back in 2015. Please see aspnet/Home#743 (comment).
With all these breaking changes – I should now ask those team members to come and cover our times/efforts – and also yours to fix the things that should be well thought initially before the version 1.0 came out.
So many people around me and around the communities complain about the lack of multi-tenancy support in Asp.Net Core framework, also the breaking changes and being have to re-write many things with every release of Asp.Net Core, there is not much to say.
I personally think that the way Asp.Net Core applications are being bootstrapped at the moment is not exactly multi-tenant friendly. @HaoK has no fault in that and my intention is not to point fingers, otherwise I can name a few people who immediately refused my multi-tenancy requests by saying we have other things to do and we will look into that in the future.
I understand the team here develops a “framework” and not a “real app”. Meaning, I am aware of the fact that they cannot support all scenarios which developers may or may not need. However, I consider multi-tenancy to be a
must have when it comes to a framework using which you develop web apps.
For instance, I don’t know who designed it initially, but having the authorization options being have to configured within
ConfigureServices was the
worst idea ever since invention of computers. We don’t have a request, we don’t know what host name, no tenant, no service instances, nothing. All you have is the hosting environment, its primitive service provider containing a few services and the logger factory.
Another instance, the built-in service provider. We cannot even create a child container (we can create just a scope) on demand. Even if we hack the hell out of it and somehow create a child container, we cannot add services to or remove from it. Even the constructor of the out of the box
ServiceProvider is internal, we always have to use the
IServiceProviderFactory which is fine but its
Build method does not allow us to provide it with a new collection of child services, etc. I know you can plugin your own
IServiceProviderFactory but that requires you to configure one more thing – the
IHostBuilder and I think that is a bit of too much. Funny thing is that I read the community asking for child containers but – without pointing finger – some people say we don’t see the need for it and still
seal classes here and there. Hey, this is a framework we have to extend!
Honestly speaking, I have been developing Asp.Net since version 1 beta 1 (2001?) and I believe I have some idea about what to expect from a framework, I still cannot suggest my clients use Asp.Net Core. It is almost version 2.0 coming out and in my opinion, I still consider it a product in its
beta stages. That is mostly because of the incredible amount of breaking changes being announced with every single push to the repo. There is no backwards-compatibility if you update your packages – and you always end-up being have to, to take advantage of a new feature being introduced. Anyone here remembers Microsoft Solutions Framework? MSF states that you should not – and cannot – introduce breaking changes after an RC version is released. Is it just me remembering a million breaking changes being introduced after the RC versions of Asp.Net Core 1.0 were released? Forget about MSF, how in the world a product can get to RC if it is going to have a major re-write of the API and functionality? It is Release Candidate after all.
I think I can sit here and spend my time writing and waste your time reading how important it is for real-world-apps to have multi-tenancy support out of the box for the next 2 days.
To summarize the things in a single sentence: The current implementation of Asp.Net Core does not support multi-tenancy as a first-class citizen and it will not be able to unless the service container and request pipeline can be configured based on a tenant.
When it comes to multi-tenancy in Asp.Net Core, all we are doing here is finding dirty workarounds, unfortunately.
Long live tenants!