Introducing TinySaas: a library for building multitenant applications in ASP.NET Core

Introducing TinySaas: a library for building multitenant applications in ASP.NET Core

Some years back, I used to maintain a SaaS application built on ASP.NET WebForms. Many organizations are aiming to migrate existing applications built on the ASP.NET Framework to the amazing .NET Core.

TinySaas culminated from my work in migrating an ASP.NET legacy application to ASP.NET Core.


Why TinySaas?

There abound on the internet several frameworks that aim to allow developers to build multitenant applications using the .NET Core Framework, but the keyword (Framework).

TinySaas is a library that aims to add multitenancy support to new and existing ASP.NET Core applications without forcing the adoption of a new framework.

The credit for this library goes to Gunnar and Michael, two great authors whose several blog articles on this topic served as the foundation for this simple library.


Getting Started

Multitenancy support should not require a rewrite nor massive changes. With TinyTenant, you can now add multitenancy support to both new and existing projects in simple steps.

  1. Add CodEaisy.TinySaas.AspNetCore to your application via Nuget or the .NET CLI
dotnet add package CodEaisy.TinySaas.AspNetCore --version 1.0.0
  1. In Startup.cs, add the following inside the ConfigureServices method.

     public void ConfigureServices(IServiceCollection services)
     {
         // register all global singleton services here, and also dependencies for your TenantStore and ResolutionStrategy if any
    
         // ...
    
         // OPTION 1
         services.AddMultiTenancy<Tenant, TenantStore<Tenant>, TenantResolutionStrategy>();
    
         // OPTION 2
         // uses default `CodEaisy.TinySaas.Model.TinyTenant` as tenant model
         services.AddMultiTenancy<TenantStore<TinyTenant>, TenantResolutionStrategy>();
    
         // ...
    
         // services.AddControllers();
     }
    

    Then, add the following in the Configure method.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
       if (env.IsDevelopment())
       {
           app.UseDeveloperExceptionPage();
       }
    
       // enable multitenant support, with missing tenant handler and tenant container
    
       // OPTION 1
       // missing tenant handler has a dependency that can be provided immediately
       app.UseMultitenancy<Tenant, MissingTenantHandler, MissingTenantOptions>(missingTenantOptions);
    
       // OPTION 2
       // missing tenant handler does not have a dependency or dependency is already registered in services
       app.UseMultitenancy<Tenant, MissingTenantHandler>();
    
       // OPTION 3
       // Use `SimpleTenant` as tenant model, and missing tenant handler does not have a dependency or dependency is already registered in the services
    
       app.UseMultitenancy<TMissingTenantHandler>()
    
       // ...
    }
    
  2. In Program.cs, add the following in the CreateHostBuilder method.

    public static IHostBuilder CreateHostBuilder(string[] args) =>
         Host.CreateDefaultBuilder(args)
             .ConfigureWebHostDefaults(webBuilder =>
             {
                 webBuilder.UseStartup<Startup>();
             })
             // OPTION 1: add multitenant support via TenantStartup class
             .ConfigureMultiTenancy<TenantStartup, Tenant>();
             // OPTION 2: add multitenant support via static method
             .ConfigureMultiTenancy<Tenant>(ClassName.StaticMethodName);
    

NOTE:

  • Tenant must implement CodEaisy.TinySaas.Interface ITenant.
  • TenantStore must implement CodEaisy.TinySaas.Interface.ITenantStore.
  • TenantResolutionStrategy must implement CodEaisy.TinySaas.Interface.ITenantResolutionStrategy respectively.
  • TenantStartup must implement IMultiTenantStartup
  • ClassName.StaticMethodName must be of type System.Action<TTenant, Autofac.ContainerBuilder> where TTenant implements ITenant

Use Cases

TinySaas supports the most common use cases for multitenant development, and below are some examples.

Global Services

Global Services are services that do not have a core dependency on the tenant information, and they can be registered just the same way you have been used to in .NET Core, in the Startup.ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddSingleton<IGlobalSingletonService, GlobalSingletonService>();

services.AddScoped<IGlobalScopedService, GlobalScopedService>();

services.AddTransient<IGlobalTransientService, GlobalTransientService>();

        // add multitenancy support here

        services.AddControllers();
    }

Tenant Services

Tenant Services are services that have core dependencies on the tenant information. TinySaas allows you to register these services like every other .NET Core service but in a tenant aware location.

Usually, tenant services will be registered either in the ConfigureServices method of your TenantStartup class or in the delegate passed to the IHostBuilder.ConfigureMultiTenancy in the Program.cs file.

    public void ConfigureTenantServices(Tenant tenant, ContainerBuilder container)
    {
        // you can either register services using the provided container builder

        // tenant singleton
        container.RegisterType<TenantSingletonService>().SingleInstance();

        // tenant scoped
        container.RegisterType<TenantScopedService>().InstancePerRequest();

        // tenant transient
        container.RegisterType<TenantTransient>().InstancePerDependency();

        // basically, you can use all the dependency injection features provided by Autofac on the container.

        #region .NET Core DI way
        // you can also do it in the well-known .NET Core default DI way

        var services = new ServiceCollection();

        // tenant singleton
        services.AddSingleton<ITenantSingletonService, TenantSingletonService>();

        // tenant scoped
        services.AddScoped<ITenantScopedService, TenantScopedService>();

        // tenant transient
        services.AddTransient<ITenantTransientService, TenantTransientService>();

        // THIS IS VERY IMPORTANT IF YOU USE THE SERVICE COLLECTION TO REGISTER ANY SERVICES HERE.
        container.Populate(services);

        #endregion

    }

Databases

Multitenancy is not complete without databases, and TinySaas has an answer to all your needs.

Shared Database (Single Schema and Schema per Tenant)

Register your DbContext the old way and inject IHttpContextAccessor via your DbContext constructor.

// Startup.cs class
public void ConfigureService(IServiceCollection services)
{
    //....
    services.AddDbContext<AppDbContext>(...);
    //...
}

// AppDbContext.cs
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options, IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;

        // to get the tenant information in any method
        // var tenant = _httpContextAccessor.HttpContext.GetCurrentTenant<TTenant>();
    }
}
`

Database Per Tenant

Simply move the DbContext registration to the multitenant service configuration method.

    public void ConfigureTenantServices(Tenant tenant, ContainerBuilder container)
    {
         // assume you had connectionString as a property on the tenant, you can easily do
        var services = new ServiceCollection();
        services.AddDbContext<AppDbContext>(tenant.ConnectionString);

        container.Populate(services);
    }

Other Concerns

Options can also be registered the same way as discussed for services and databases.

You can check out the source repo for TinySaas on Github, and give feedback via the issues tab.

Thank you for reading 🍾.