Building Multitenant Architecture Using ASP.NET Core And Micro Services

Introduction 

Web applications are shifting toward cloud infrastructure and microservices, to achieve high availability and extensibility.

Instead of deploying an application per client, organizations prefer multitenancy to save infrastructure cost and deployment time. Multitenant architecture helps to adopt changes for different clients under the hood. In this article, I am not going to discuss database design for multitenant applications but I will be focused on achieving separation for different clients.

In this article, I will explain how we can build a multitenant system using asp.net core and autofac.

A multitenant syetsm
 
 
 
 

How to achieve multitenancy

In multitenant architecture, the very first step is to identify tenant. In real infrastructure we have multiple ways to identify tenants; e.g., from subdomain name tenant1.xyz.com, tenant2.xyz.com. Since there are many ways to identify tenant and resolve all the dependencies for specific tenants, I am going to use autofac multitenant(DI framework) which has a great support for multitenancy.

In this application I am using url segment as {tenant}/Home/Index to identify tenant. I will use custom implementation of ITenantIdentificationStrategy to retrieve tenant id to identify tenant from url as follows.

  1. public class TenantResolverStrategy: ITenantIdentificationStrategy {  
  2.     private IHttpContextAccessor _httpContextAccessor;  
  3.     public TenantResolverStrategy(IHttpContextAccessor httpContextAccessor) {  
  4.         _httpContextAccessor = httpContextAccessor;  
  5.     }  
  6.     public bool TryIdentifyTenant(out object tenantId) {  
  7.         tenantId = null;  
  8.         try {  
  9.             var context = _httpContextAccessor.HttpContext;  
  10.             if (context != null && context.Request != null) {  
  11.                 // get very first segment  
  12.                 var uriSeg = context.Request.Path.Value.Split('/');  
  13.                 tenantId = uriSeg[1];  
  14.             }  
  15.         } catch (Exception) {}  
  16.         return (tenantId != null || tenantId == (object)  
  17.             "");  
  18.     }  
  19. }  

Our next step will be to add default (common) implementation for tenants. In multitenant system most of the clients(tenants) will be using common behaviour but many of them will ask for the different behaviour and business rules as per their business model.

In order to consider both, we must have two implementations as follows,

  1. Default implementation : common implementation.
  2. Custom implementation : custom implementation.

Now we will create three different loosely coupled services (microservices) for above architecture as follows,

  1. DefaultService : it will have basic implementation for getting user data.
  2. Tenant1Service : it will have custom implementation for tenant1.
  3. Tenant2Service : it will have custom implementation for tenant2.

I am using a proxy implementation for each service and using the same for the different tenant. If there is no service registered for current tenant or user, the default implementation will work. The purpose of separate service is scalability.

We use Multitenant container to register tenant-wise service dependency as follows,

  1. var mtc = new MultitenantContainer(appContainer.Resolve < ITenantIdentificationStrategy > (), appContainer);  
  2. // Configure the overrides for each tenant by passing in the tenant ID  
  3. mtc.ConfigureTenant("tenant1", b => {  
  4.     b.RegisterType < Tenant1ServiceProxy > ().As < IServiceProxy > ().InstancePerDependency();  
  5. });  
  6. mtc.ConfigureTenant("tenant2", b => {  
  7.     b.RegisterType < Tenant2ServiceProxy > ().As < IServiceProxy > ().InstancePerDependency();  
  8. });  

Once we have done this, we can resolve serivces as usual,

  1. public HomeController(IServiceProxy service){ }  

and tenant related tasks will be managed by autofc framework.

Using different controller for tenant

In this sample project (github link), I am having different projects(service and controllers) for tenants(default, tenant1,tenant2).

ASP.NET Core

  1. Using IServiceProxy interface  
  2. public interface IServiceProxy {  
  3.     IList < UserModel > GetAll();  
  4. }  

I have different implementation for default, tenant1 and tenant2, which are having a simple http call to access data fom respective services.

Now as you can see we can have different implementation of services as per tenant. But still, the challenge is to have the same controller.

What if we need a set of different actions for a particular tenant?

Since Mvc framework registers all the contollers and from the referenced assembly or from the same assembly, while calling controller action it looks up for all the controllers according to route parameters.

In order to invoke an appropriate action for current tenant, I am overriding SelectBestAction method of ActionSelecor class which will returns controllers for tenant in our case. It provides full qualified named of controller classes but for simplicity, I m looking for the name which has tenant name in it.

  1. public class TenantActionSelector: ActionSelector {  
  2.     private ITenantIdentificationStrategy _tenantIdentificationStrategy;  
  3.     public TenantActionSelector(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, ActionConstraintCache actionConstraintCache, ILoggerFactory loggerFactory, ITenantIdentificationStrategy tenantIdentificationStrategy): base(actionDescriptorCollectionProvider, actionConstraintCache, loggerFactory) {  
  4.         _tenantIdentificationStrategy = tenantIdentificationStrategy;  
  5.     }  
  6.     protected override IReadOnlyList < ActionDescriptor > SelectBestActions(IReadOnlyList < ActionDescriptor > actions) {  
  7.         List < ActionDescriptor > result = null;  
  8.         var tid = GetTenantId();  
  9.         // find controller dependency for current tenant  
  10.         if (tid != null && (string) tid != "") {  
  11.             // here we will have full assembly name  
  12.             var tenantController = string.Format("{0}controllers", (string) tid).ToLower();  
  13.             result = actions.Where(x => x.DisplayName.ToLower().Contains(tenantController)).ToList();  
  14.         }  
  15.         // add default if not found  
  16.         if (result == null || result.Count() == 0) {  
  17.             result = actions.Where(x => x.DisplayName.ToLower().Contains("defaultcontrollers")).ToList();  
  18.         }  
  19.         return result;  
  20.     }  
  21.     protected object GetTenantId() {  
  22.         var tenantId = (object) null;  
  23.         var success = _tenantIdentificationStrategy.TryIdentifyTenant(out tenantId);  
  24.         if (!success || tenantId == null) {  
  25.             return "";  
  26.         }  
  27.         return tenantId;  
  28.     }  
  29. }  
Lets visualise it ,
 


Now we are done.

I am not good at design :(. So I am using a simple table template for demonstration.

Default Tenant(default service, default controller)

ASP.NET Core

Tenant 1 (has seperate service and controller with additional action method)

ASP.NET Core

Tenant 2 (Default controller , seperate service)

ASP.NET Core
Note

This is a sample project, to get started with multitenancy, and I have not covered all the parts such as authentication and security. However, you can achieve it by exploring the autofac and aspnet core docs.

You can explore source code to play with and customize the same as per your requirements. For this sample project, you will have to run multiple projects together (all services along with the main app).

Github link

https://github.com/Anupam-/AspNetCoreMultitenantMicroservicesArchitecture

Kindly share your suggestions and feedback. Thanks :).