Trusted answers to developer questions
Trusted Answers to Developer Questions

Related Tags

multitenancy
dot-net-c
csharp
communitycreator

How to enable multitenancy of your web APP with .NET Core

Boris Zaikin

Introduction

Businesses need to grow in order to be successful and handle an increasing number of clients and partners. If a company is not ready to respond to this load, then there is a big chance that opportunities will be missed.

This brings us to the topic of scalability as one of the main requirements that a company should address. One of the possible ways to address this requirement is to build a multi-tenant solution.

As this topic gains more importance, lots of options are available to achieve multitenancy; for example, using the Microsoft Elastic database (elastic tools). However, in particular cases, like the case I faced in my project, not all of the product requirements could be satisfied with the available options.

This brought me to the idea of gathering my experience on this topic and presenting it below.

Types of scaling

As we are aware, there are two main approaches to tackling application scaling: horizontal and vertical. Horizontal scaling will bring you the benefit of scaling on the fly and will imply dealing with multiple databases, as each tenant has its own database/shard. The vertical approach to scaling presumes that you have one database that serves several tenants.

In this shot, I will address the approach of horizontal scaling with a step-by-step guide on how to build a multi-tenant web API application.

Solution

Let’s briefly take a look at the architecture first. The example below is designed based on N-tire architecture and has the following layers:

  • Presentation layer or Web API.
  • Service layer that will accommodate all the business logic.
  • Data access layer that is implemented using UnitOfWork and Repository patterns. As an ORM, in this example, I used Entity Framework Core.

All the magic of tenant switching is in ContextFactory, which contains logic to get the tenant id from the HTTP header, retrieve a tenant database name using DataBaseManager, and replace a database name in the connection string. As a result, a database context (EF context) is created.

You can see the process of tenant switching in the code fragment below:

private SqlConnectionStringBuilder ChangeDatabaseNameInConnectionString() {
      var sqlConnectionBuilder = new SqlConnectionStringBuilder(this.settings.Value.DefaultConnection);

      string dataBaseName = this.dataBaseManager.GetDataBaseName(this.TenantId);
      if (dataBaseName == null) {
       throw new ArgumentNullException(nameof(dataBaseName));
      }

      // Remove old DataBase name from connection string AND add new one
      sqlConnectionBuilder.Remove(DatabaseFieldKeyword);
      sqlConnectionBuilder.Add(DatabaseFieldKeyword, dataBaseName);

      return sqlConnectionBuilder;
     }

Below, you can find the complete code sample.

/// <summary>
    /// Entity Framework context service
    /// (Switches the db context according to tenant id field)
    /// </summary>
    /// <seealso cref="IContextFactory" />
    public class ContextFactory: IContextFactory {
     private
     const string TenantIdFieldName = "tenantid";
     private
     const string DatabaseFieldKeyword = "Database";

     private readonly HttpContext httpContext;
     private readonly IOptions <ConnectionSettings> settings;
     private readonly IDataBaseManager dataBaseManager;

     public ContextFactory(
      IHttpContextAccessor httpContentAccessor,
      IOptions <ConnectionSettings> connectionSetting,
      IDataBaseManager dataBaseManager) {
      this.httpContext = httpContentAccessor.HttpContext;
      this.settings = connectionSetting;
      this.dataBaseManager = dataBaseManager;
     }

     public IDbContext DbContext {
      get {
       var dbOptionsBuidler = this.ChangeDatabaseNameInConnectionString();

       // Add new (changed) database name to db options
       var bbContextOptionsBuilder = new DbContextOptionsBuilder<DeviceContext>();
       bbContextOptionsBuilder.UseSqlServer(dbOptionsBuidler.ConnectionString);

       return new DevicesApiContext(bbContextOptionsBuilder.Options);
      }
     }

     // Gets tenant id from HTTP header
     private string TenantId {
      get {
       if (this.httpContext == null) {
        throw new ArgumentNullException(nameof(this.httpContext));
       }

       string tenantId = this.httpContext.Request.Headers[TenantIdFieldName].ToString();

       if (tenantId == null) {
        throw new ArgumentNullException(nameof(tenantId));
       }

       return tenantId;
      }
     }

     private SqlConnectionStringBuilder ChangeDatabaseNameInConnectionString() {
      var sqlConnectionBuilder = new SqlConnectionStringBuilder(this.settings.Value.DefaultConnection);

      string dataBaseName = this.dataBaseManager.GetDataBaseName(this.TenantId);
      if (dataBaseName == null) {
       throw new ArgumentNullException(nameof(dataBaseName));
      }

      // Remove old DataBase name from connection string AND add new one
      sqlConnectionBuilder.Remove(DatabaseFieldKeyword);
      sqlConnectionBuilder.Add(DatabaseFieldKeyword, dataBaseName);

      return sqlConnectionBuilder;
     }
    }
ContextFactory.cs

Let’s quickly walk through the code. First of all, in the code, we need to inject (using IoC) HTTP context with tenant id, and an options object with a connections string. The entry point to the class is DbContextproperty. It does the following:

  • Instantiates the DbOptionBuilder object with the changed database name in the connection string.
  • Instantiates bbContextOptionsBuilder using the connection string.
  • Builds Database context using DbOptionBuilder. This step creates a connection to the specific tenant database.

You can find related source code in the Repository.

This code is completely modular, so you can reuse it, with a bit of adaption, in your project.

RELATED TAGS

multitenancy
dot-net-c
csharp
communitycreator
RELATED COURSES

View all Courses

Keep Exploring