Nano ASP.NET SaaS Boilerplate
Admin credentials (all tenants): admin@email.com / Password123!
Sample data resets every hour
Nano ASP.NET SaaS Boilerplate
General
.NET Solution
Vue UI
React UI
Razor Pages
.NET Solution

Multi-Tenancy

Multi-tenancy is achieved using query filters and override methods in the DB contexts.

Domain

In Domain/Multitenancy, the Tenant table contains the tenant data. You can use any data-type you prefer for the tenant Id, but strings are used for simplicity. If a null value is provided for ConnectionString, the tenant will use the default database.

The IMustHaveTenant interface contains one field, TenantId. Any table that implements this interface will have tenant isolated data. TenantBaseEntity and AuditableEntity are common base classes that implement IMustHaveTenant.

Current Tenant User Service

CurrentTenantUserService is a scoped service that will ‘always’ contain a value for tenant ID. It is triggered on every request in middleware, TenantResolver. TenantResolver will look for the tenant ID as a claim in the JWT token, or as a request header in an unauthenticated request, like login.

DB Contexts

There are two DB contexts which are used for actual data persistence, BaseDbContext and ApplicationDbContext, and one auxiliary context, TenantDbContext.

The TenandDbContext is used in the CurrentTenantUserService to look up a tenant when a request comes in. The reason this needs to be a separate context is that BaseDbContext and ApplicationDbContext use CurrentTenantUserService when they construct and have query filters that rely on the tenant Id being present. Having a separate context avoids a circular dependency.

BaseDbContext inherits from IdentityDbContext and contains the ASP Identity related tables and the Tenant table. In a multiple database scenario, these tables will not be created on every tenant database, only the main database. ApplicationDbContext contains all other tables related to your application. In a multiple database scenario, these tables will be created on every tenant table. The products entity for example, is managed within the ApplicationDbContext. If you plan on only having a single database, these two contexts could be merged into one. Refer to the Persistence & Infrastructure guide on how to create migrations.

Query Filters & On Configuring

Queries on tenant data are isolated by use of global query filters provided by Entity Framework core. The OnModelCreating method dynamically applies query filters to any entity implementing the IMustHaveTenant interface, with help from the ApplyGlobalQueryFilter extension in Infrastructure/Persistence/Extensions.

ApplicationDbContext contains an OnConfiguring method to dynamically switch the connection string per each request.

Save Changes

Whenever tenant isolated data changes, the Tenant Id and audit fields are handled by the TenantAndAuditFields method in Infrastructure/Persistence/Extensions.

Tenant Management Service

The TenantManagementService found in Infrastructure/Multitenancy is responsible for tenant CRUD operations. The GetTenants method returns a full list of tenants for the client-side tables.

The SaveTenant method creates a new tenant. When a new tenant is created, an admin user for that tenant is also created. When creating a tenant with an isolated database, the default database name and new tenant id will be combined to name the new database.

New tenant databases will use any pending migrations from ApplicationDbContext to generate their initial schema.

Handling Migrations

Any migrations are applied automatically when the app starts up and are handled by the extension AddAndMigrateTenantDatabases. This method will apply any pending migrations to the BaseDbContext and then read the list of tenants. Any pending ApplicationDbContext migrations are then applied to tenants in that list if they contain a unique connection string.

The BaseDbContextFactory is necessary to guide the Entity Framework design time tools when scaffolding new migrations locally.  You’ll find quick reference EF commands for scaffolding migrations commented in each of the DB contexts.

Next Steps

That covers multi-tenancy. Next we’ll explore persistence and other infrastructure.