Multitenancy in ASP.NET Core â Simplest Way to achieve Multitenancy
In this article, letâs learn how to implement Multitenancy in ASP.NET Core in a rather simple way making use of Entity Framework Core. You can find the source code of the entire implementation in this repository.
Multitenancy in ASP.NET Core is yet another topic that is not very well documented on the internet. Itâs kind of tricky to build a Multitenant application right from scratch. In my ongoing open-source project âfullstackhero .NET WebApi Boilerplateâ, Multitenancy was the top priority on my to-do list. Letâs get started with understanding and implementing a simple, yet feature-packed version of Multitenancy in ASP.NET Core.
What is MultiTenancy?
Multitenancy is an architectural pattern where-in a software system can serve multiple groups of users or organizations. SAAS Products are a prime example of Multitenant architecture.
We are all familiar with single tenancy, where an API / Service is used by a single group of users or a single organization. In such a scenario, single application deployment is needed for every new set of users or organization.
Letâs imagine we have an application that is needed by several Organizations or User Groups. Itâs known that the application will have the same code base for all the users. In such a scenario, is it always smart to deploy the same application over and over again for each of the tenants? Doesnât it add to the infrastructure cost and maintenance? Isnât it smarter to design an application in such a way that it can accommodate multiple user groups in a single deployment? This can dramatically reduce the deployment count and server costs.
Refer to the following diagram.

Here, the first section demonstrates the usual Single Tenant Application where Separate Deployments are needed per tenant. Tenant refers to the user group or the Organization / Company that wishes to use our application. You can see that each of the tenants will need a full-fledged deployment of the infrastructure.
Whereas, in the second section, single application deployment is shared by multiple tenants. The tenants can also choose to share the database as well or have a completely isolated database for themselves.
Database Access Strategies
For the Multitenant application to communicate with the database, there are essentially the following strategies.
Schema Seperation
This is one of the lesser-used strategies where each tenant will get its own Database Table, separated by the schema name. Letâs say an application has 3 tenants, alpha, beta, and gamma. Its products tables will look like this:
- [alpha].[Products]
- [beta].[Products]
- [gamma].[Products]
In this way, there is a clear separation of the tenant data.
Single Database â Tenant Column Seperation
This is a quite popular approach where the application has just 1 Database, and all the tenants share both the application and the database. This is more of an economical approach when there is not a lot of data operations happening in the application.
Each of the tables will have an additional column âTenantIdâ, which will be responsible for filtering the data tenant-wise.

When your tenants are worried about data security and have a lot of data flow happening, this may not be an ideal solution. There are high chances that the developer forgets to filter out the tenantId of a service which can potentially expose the data of other tenants as well. Also, when a tenant is highly data space-hungry, this approach wonât be much ideal.
Multiple Database â Complete Data Isolation
In this approach, each of the tenants gets to enjoy a separate database and thus complete data isolation and security. This is the most preferred solution when it comes to Multitenancy.

Hybrid Approach â Favorite!
Here is an interesting approach, where we design an application that can allow the tenants to choose if they need a separate database or shared database. This can be important when you know that not all tenants are going to have high database usage. The tenants who use the database in a minimal way can be assigned to use the shared database, while the more Data-intensive tenants can choose to have a separate database. This also has a positive impact economically.
In this tutorial, we will build an ASP.NET Core WebApi that supports Hybrid Multitenancy!
Identifying Tenants
Now that we understood how Database Access works in Multitenant applications, letâs learn how our ASP.NET Core application can identify tenants from the incoming requests.
Query String
This is a simple mechanism, where the tenant can be identified using a Query string. Itâs as simple as passing â?TenantId=alphaâ in the request URL and the application gets to know that this request is meant for tenant alpha. As this strategy has several vulnerabilities, itâs advised to use this approach only for Test and Development purposes.
Request IP Address
Here, we assume that each tenant request will be generated from a particular IP range. Once an IP range is set for a tenant, the application can detect which tenant it belongs to. Although this is a secure approach, itâs not always convenient to use this.
Request Header
This is a more robust strategy to identity Tenants. Each of the requests will have a Header with TenantID in it. The application then serves the request for that particular tenant. This approach is recommended to be used when requesting Authentication Tokens only. We will be using this approach in our tutorial.
Claims
A more secure way to detect Tenants. In systems where JWT Tokens are involved for Authentication, the tenantId of the user can be encoded into the claims of the Token. This approach ensures that the request is both authenticated and belongs to a user from the mentioned tenant.
This is the approach I took for implementing Multitenancy in fullstackheroâs .NET WebApi Boilerplate project.
I generated a token from the Token endpoint and tried to decode the JWT. As you can see, there is tenant data available in the claims of the token, which further on can be worked upon by the client or server application with ease.

What we will Build?
In this tutorial, we will be building a Multitenant ASP.NET Core WebApi that demonstrates how easy it can be to achieve and understand Multitenancy in ASP.NET Core applications.
We will make this application completely configurable via the appsettings of the ASP.NET Core application. We will have the possibility to configure the details of each tenant with IDs using which we will perform CRUD operations on a simple entity. The tenant IDs will be passed to the application via the request headers. Each tenant will have the possibility of using a shared database or a dedicated database. The application will be performing automatic migrations of the database for each of the tenants, thanks to Entity Framework Core. EFCoreâs Global Filter feature will be used to segregate each of the tenants too!
Letâs get started!
Getting Started with Multitenancy in ASP.NET Core
As mentioned earlier, we will be building a Multitenant ASP.NET Core 5.0 WebApi in this tutorial. I will be using Visual Studio 2019 as my IDE for development.
Letâs start by creating a new ASP.NET Core WebApi Project. Make sure to use the .NET 5.0 Framework.

Next up, letâs add in 2 C# Class Library Projects, namely Core and Infrastructure. Your Solution would look like this now.

Make sure to delete the Classes that are created in the new Library Project. Also, I removed the boilerplate classes and controllers that Visual Studio added when I created the API Project.

Building the Entity
Letâs build a simple Product Entity first! In your Core project, add a new folder named Entities and add in a new class, and name it BaseEntity.
public abstract class BaseEntity { public int Id { get; private set; } }
As you know, this is the Base Abstract class that our Entities will inherit to obtain the ID field. We will be using this in our Product Class.
Next comes the important part, where we need to have a contract/interface that will be implemented for Entities that need Multitenancy support. You can simply add a new property to each tenant called TenantId, but here is a cleaner way to do it.
Create a new folder in the Core Project and name it Contracts. Here, add a new interface and name it IMustHaveTenant. As it sounds, all the entity classes that implement this interface will have a Tenant Id. You will see the importance of having this contract later on in this guide.
public interface IMustHaveTenant { public string TenantId { get; set; } }
After, create a new class in the Entities Folder and name Product.
public class Product : BaseEntity, IMustHaveTenant { public Product(string name, string description, int rate) { Name = name; Description = description; Rate = rate; } protected Product() { } public string Name { get; private set; } public string Description { get; private set; } public int Rate { get; private set; } public string TenantId { get; set; } }
As said earlier, our Product class will inherit from BaseEntity Class and implement the IMustHaveTenant interface, which in turn adds the TenantId property to the class. Also, I have followed a simple DDD pattern for creating the class where all the properties of the Entity have a private setter and have a constructor that can create objects by accepting the name, description, and rate properties.
Tenant Settings â Explained
First, letâs add a Settings class which will be used for the IOptions Pattern later on in this guide. In the Core project, add a new folder named Settings and add a new class TenantSettings.
using System.Collections.Generic; namespace Core.Options { public class TenantSettings { public Configuration Defaults { get; set; } public List<Tenant> Tenants { get; set; } } public class Tenant { public string Name { get; set; } public string TID { get; set; } public string ConnectionString { get; set; } } public class Configuration { public string DBProvider { get; set; } public string ConnectionString { get; set; } } }
Remember that we will be using the same structure in the appsettings.json as well.
- TenantSettings will have a Default Configuration which includes the DBProvider (MSSQL will be used in this tutorial), and a Connection String to the default shared database.
- Tenant Setting will also have a definition for the List of Tenants that are supposed to have access to the system.
- A Tenant Object will have Name, TID and Tenant Specific Connection String.
- In cases where the tenant needs to use the shared database, the idea is to leave the Connection string of the tenant blank.
Letâs add a sample settings node in the appsettings.json to understand the setup even better. Open up your appsettings.json from the API project and add in the following.
"TenantSettings": { "Defaults": { "DBProvider": "mssql", "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=sharedTenantDb;Integrated Security=True;MultipleActiveResultSets=True" }, "Tenants": [ { "Name": "alpha", "TID": "alpha", "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=alphaTenantDb;Integrated Security=True;MultipleActiveResultSets=True" }, { "Name": "beta", "TID": "beta", "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=betaTenantDb;Integrated Security=True;MultipleActiveResultSets=True" }, { "Name": "charlie", "TID": "charlie" }, { "Name": "java", "TID": "java" } ] }
In this demo settings, we mention that we have 4 Tenants set up to use our API. Tenant alpha and beta wish to have a separate Database, while tenant charlie and java are ok to use the shared database which is mentioned in the default section of the tenant settings.
Now, that we got our requirements and concepts clear, letâs how to proceed further.
Installing the Required Packages
To go further, letâs first install the required Nuget packages to the Infrastructure Project. I would recommend copying and pasting the following PackageReference to the csproj file to make things easier. If you face any trouble, you can individually install these packages as well.
PS, there may be newer versions of the packages available at the time you are reading this article. Make sure to update all the packages after you install them. Use the update-package command in the Package Manager Console of Visual Studio Code!
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.9" /> <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup>
To give a brief idea, we need packages related to EFCore and SQLServer, Extension packages of Microsoft for IOptions, Configurations, and finally HTTP package to be able to read the Request header of the incoming request.
Also, in the API Project, make sure to install the following package. This is needed when we start generating migrations for our EFCore Context.
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
Tenant Service
We need to devise a way to identify the tenant from the incoming requests. To achieve this, letâs start by creating an ITenantService interface in the Core Project. Create a new folder named Interfaces in the Core project and add a new interface, ITenantService.
public interface ITenantService { public string GetDatabaseProvider(); public string GetConnectionString(); public Tenant GetTenant(); }
Basically, this interface should return the current DBProvider, Connection string, and Tenant Data. Letâs implement this interface in the Infrastructure project next.
Create a new folder in the Infrastructure project and name it Services. Here, add a new class and name it TenantService.
public class TenantService : ITenantService { private readonly TenantSettings _tenantSettings; private HttpContext _httpContext; private Tenant _currentTenant; public TenantService(IOptions<TenantSettings> tenantSettings, IHttpContextAccessor contextAccessor) { _tenantSettings = tenantSettings.Value; _httpContext = contextAccessor.HttpContext; if (_httpContext != null) { if (_httpContext.Request.Headers.TryGetValue("tenant", out var tenantId)) { SetTenant(tenantId); } else { throw new Exception("Invalid Tenant!"); } } } private void SetTenant(string tenantId) { _currentTenant = _tenantSettings.Tenants.Where(a => a.TID == tenantId).FirstOrDefault(); if (_currentTenant == null) throw new Exception("Invalid Tenant!"); if (string.IsNullOrEmpty(_currentTenant.ConnectionString)) { SetDefaultConnectionStringToCurrentTenant(); } } private void SetDefaultConnectionStringToCurrentTenant() { _currentTenant.ConnectionString = _tenantSettings.Defaults.ConnectionString; } public string GetConnectionString() { return _currentTenant?.ConnectionString; } public string GetDatabaseProvider() { return _tenantSettings.Defaults?.DBProvider; } public Tenant GetTenant() { return _currentTenant; } }
Line 10 to 20 â We first check if HTTP context is not null, then we try to read the tenant key from the header of the request. If a tenant value is found, we set the tenant using the SetTenant(string tenantId) method.
Line 22 to 30 â Here, we take in tenant from the request header and compare it against the data we have already set in the appsettings of the application. If the matching tenant is not found, it throws an exception. If the found tenant doesnât have a connection string defined, we simply take the default connection string and attach it to the connection string property of the current tenant, as simple as that.
The remaining methods are quite straightforward.
Note that we have not yet registered any of the services of settings into the DI Container of the application. We will be doing it towards the end of the tutorial only.
Extended ApplicationDBContext
Now that we have our Tenant Service ready, letâs do the DbContext part.
In the Infrastructure project, create a new folder named Persistence and add in a new class named ApplicationDbContext.
public class ApplicationDbContext : DbContext { public string TenantId { get; set; } private readonly ITenantService _tenantService; public ApplicationDbContext(DbContextOptions options, ITenantService tenantService) : base(options) { _tenantService = tenantService; TenantId = _tenantService.GetTenant()?.TID; } public DbSet<Product> Products { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Product>().HasQueryFilter(a => a.TenantId == TenantId); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var tenantConnectionString = _tenantService.GetConnectionString(); if (!string.IsNullOrEmpty(tenantConnectionString)) { var DBProvider = _tenantService.GetDatabaseProvider(); if (DBProvider.ToLower() == "mssql") { optionsBuilder.UseSqlServer(_tenantService.GetConnectionString()); } } } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { foreach (var entry in ChangeTracker.Entries<IMustHaveTenant>().ToList()) { switch (entry.State) { case EntityState.Added: case EntityState.Modified: entry.Entity.TenantId = TenantId; break; } } var result = await base.SaveChangesAsync(cancellationToken); return result; } }
- It is assumed that, whenever the application tries to access the database via EFCore context, there is a valid tenantID present in the request header, or atleast provided by the Tenant Service.
- Line 14 â This is the part where we define the Global Query Filter for the DBContext. Everytime a new request is passed to the DBContext, the applicationDbContext will be smart enough to work with the data the is relavant to the current tenantId only.
- Line 16 to 27 â Here, everytime a new instance of ApplicationContext is invoked, the connection string is pulled from the tenant settings and set to EFCore Context.
- Finally, in Line 28 to 42, we override the SaveChanges method. In this method, whenever there is a modification of the Entity of type IMustHaveTenant, TenantId is written to the entity during the Save process.
Product Service
Letâs quickly write a Product Service Implementation that takes care of Creating a new product, Getting all Products and Getting Product Details by Id. First, letâs create a new interface in the Core Project, IProductService.
public interface IProductService { Task<Product> CreateAsync(string name, string description, int rate); Task<Product> GetByIdAsync(int id); Task<IReadOnlyList<Product>> GetAllAsync(); }
Now, in the Infrastructure/Services folder, letâs create a new class and name it ProductService.
public class ProductService : IProductService { private readonly ApplicationDbContext _context; public ProductService(ApplicationDbContext context) { _context = context; } public async Task<Product> CreateAsync(string name, string description, int rate) { var product = new Product(name, description, rate); _context.Products.Add(product); await _context.SaveChangesAsync(); return product; } public async Task<IReadOnlyList<Product>> GetAllAsync() { return await _context.Products.ToListAsync(); } public async Task<Product> GetByIdAsync(int id) { return await _context.Products.FindAsync(id); } }
This is a very straightforward usage of DBContext to perform some simple CRUD Operations. Letâs proceed to the fun part next.
Automated Migrations
We will build an extension method that can
- Create DBs for each tenant on startup
- Update the new Databases with available Migrations
- Register the ApplicationDbContext.
Under the Infrastructure Project, create a new folder named Extensions. Here add a new static class named ServiceCollectionExtensions
public static class ServiceCollectionExtensions { public static IServiceCollection AddAndMigrateTenantDatabases(this IServiceCollection services, IConfiguration config) { var options = services.GetOptions<TenantSettings>(nameof(TenantSettings)); var defaultConnectionString = options.Defaults?.ConnectionString; var defaultDbProvider = options.Defaults?.DBProvider; if (defaultDbProvider.ToLower() == "mssql") { services.AddDbContext<ApplicationDbContext>(m => m.UseSqlServer(e => e.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); } var tenants = options.Tenants; foreach (var tenant in tenants) { string connectionString; if (string.IsNullOrEmpty(tenant.ConnectionString)) { connectionString = defaultConnectionString; } else { connectionString = tenant.ConnectionString; } using var scope = services.BuildServiceProvider().CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); dbContext.Database.SetConnectionString(connectionString); if (dbContext.Database.GetMigrations().Count() > 0) { dbContext.Database.Migrate(); } } return services; } public static T GetOptions<T>(this IServiceCollection services, string sectionName) where T : new() { using var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService<IConfiguration>(); var section = configuration.GetSection(sectionName); var options = new T(); section.Bind(options); return options; } }
- Line 5 to 7 â Gets default settings from appsettings.json file
- Line 10 â Registers ApplicationDbContext using the SQLServer package.
- Line 13 to 31 â Iterates through the list of tenants configured. If a tenant has no Connection String declared, it assigns the default connection string to it. From there, we extract the DBContext Service, set itâs connection to the tenantâs connection string and finally perform migrations.
- Line 34 to 42 â This is a generic method to Get the configuration from AppSettings.json in a static file.
Setting up the Controller
With that done, letâs create a new API controller for our test purpose.
Under the API Project, Create a ProductsController in the Controllers folder.
namespace Multitenant.Api.Controllers { [Route("api/[controller]")] [ApiController] public class ProductsController : ControllerBase { private readonly IProductService _service; public ProductsController(IProductService service) { _service = service; } [HttpGet] public async Task<IActionResult> GetAsync(int id) { var productDetails = await _service.GetByIdAsync(id); return Ok(productDetails); } [HttpPost] public async Task<IActionResult> CreateAsync(CreateProductRequest request) { return Ok(await _service.CreateAsync(request.Name, request.Description, request.Rate)); } } public class CreateProductRequest { public string Name { get; set; } public string Description { get; set; } public int Rate { get; set; } } }
Nothing complicated here as well. Just a simple controller that uses the IProductService DI at the container level and exposes 3 Endpoints
- Get By Id
- Create a new Product
- You can add in the endpoint to consume the GetAll Service method as well.
Generating the Migrations
As usual, make the API Project your startup project and open up the Package Manager Console. Now, set the Default project as the Infrastructure project.
Finally, run the âadd-migration ânew-migrationââ command to generate the required migrations.

Service Registrations
Finally, letâs finish off this implementation by adding the Services to the ASP.NET Core DI Container. Open up the Startup.cs file of the API project and modify the ConfigureServices method as below.
public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Multitenant.Api", Version = "v1" }); }); services.AddTransient<ITenantService, TenantService>(); services.AddTransient<IProductService, ProductService>(); services.Configure<TenantSettings>(config.GetSection(nameof(TenantSettings))); services.AddAndMigrateTenantDatabases(config); }
Thatâs it for Multitenancy in ASP.NET Core Applications. Letâs build our project and do some tests.
Testing
As soon as you build the project, the first thing you expect the application to do is to create the databases for each tenant and update the databases with the available Migrations, right? Letâs see what has happened on our Local Database Server.

As you can see, all the required databases are created along with the Products table. The first part of the Test is passed.
For the next tests, let me use Postman.
Creating a new Product with TenantId
Here is my Request Body

and here are the Request Headers

And here is the response to my POST Request.

You can see that we get a 200 Status Code. Now letâs see the Database to check if our data is available along with the TenantId. As for the tenant settings, the tenant beta is supposed to have a separate database. So, we expect to find our Product there.

There you go! Pretty cool, yeah. Letâs test other scenarios.
Creating a new Product without TenantId
Let me remove the tenant key from the request header of Postman and try to create a product.

Invalid Tenant as expected.
Shared Database Demonstration.
We know that charlie and java are configured to use the shared default database. Let me create products for each of these tenants by passing the required tenant Ids. Once done, letâs check out the Shared Database.

Cool, yeah? Let me try to use the GET endpoint to request for a product with ID = 1 and tenant as java. As you can see from the above screenshot, the Product with ID 1 belongs to charlie and not java. Thus we can expect a blank result. Letâ see.

Exactly like we expected. Now, letâs change the tenant to charlie and see if we are able to retrieve the product with ID 1.

There you go. This is probably the simplest way to achieve Multitenancy in ASP.NET CoreApplications.
Further Enhancements
- Multitenacy can be extended to use Identity as well. We can create Identity users for each tenant. In this way we can restrict the access of the each tennant to the corresponding users / user group only. This is true Multitenancy. I have already implemented this features in the fullstackheroâs .NET WebApi Boilerplate project. Do check it out!
- In this implmentation, we have stored the Tenant Settings to a simple appsettings file. In advanced cases, we can maintain a seperate Database Table to Manage Tenants and their settings.
Also..

I am currently working on bringing about a collection of Enterprise level Boilerplates under the name of fullstackhero for kick start ing application development in a rapid manner. As of now, the planned Boilerplates are:
- .NET 6.0 WebApi Boilerplate â Currently in Progress (Star the Repository!)
- Angular Material Boilerplate that consumes the above API Project.
- Blazor WASM the consumes the API Project
- .NET MVC that consumes the API Project
Do let me know your view on this đ
Summary
I have tried my maximum to research and bring about a well-detailed article on Multitenancy in ASP.NET Core Applications. I hope that this article can help other developers learn and implement Multitenancy. There are definitely several variants to this implementation that is completely dependant on your requirement. But this is definitely a good starting point for achieving Multitenancy with ease!
You can find the source code of the entire implementation of Multitenancy in ASP.NET Core applications here. Cheers!
Great job,
Thanks for sharing with the community.
I wanna know what should we do if we want to place the tenant name as a subdomain in endpoints?
Thank you.
Great, its upgrade my knowledge. Thanks for post.
I do send out a mail, please look into it.
hello excellent work from Multitenancy, but how could I implement this in blazor wasm? Could you help me with this issue I want to do something like what you describe in your article but in blazor
Thank you, a good overview (which I found most useful) and thorough walkthrough. Clarified some questions for me.
Fantastic! Very well written; very simple and straightforward solution to the multitenancy problem. Thanks for sharing. I am eagerly following fullstackhero and BlazorHero. Keep up the good work.
Nice article. Want to add that in a project I recently worked with, there was another way of detecting tenant â from the refer URL of the request. Because every tenant had a different frontend, it worked fine.
Any Example project for monolith with multi tenancy ?
Very nice article. I have only one question. You registered tenant service as transient, but probably scoped would be good also, and maybe better for performance, in case if one request will use many middlewares and services where such service would be needed. Am I right or I missed something?
Yes, you are right. Scoped is probably the best way to register the service.
Hi Mukesh,
It is a great article. We need your service to enhance our stand alone software to multi tenancy. Please reply back to me.
Great article, very simplified explanation of multitenancy in ASP.NET Core.
Thanks and keep doing a great work!
Hi, Thanks for sharing and well explained!
I have a question, if tenant details need to be crossed checked with the database, how we can go about it?
The main issue I have is if I inject a DbContext into TenantService then I cannot access TeanatService from DbContext as it causes a cyclic injection.
Thanks, much appreciated!
In this article, My approach was to store tenant details within appsettings. For advanced cases, you would need to store the tenant data into the database, using a separate DbContext â TenantDbContext. I have a working model already implemented and tested. Please refer to https://github.com/fullstackhero/dotnet-webapi-boilerplate
More specifically , this one https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Services/General/TenantManager.cs
Regards
Hi Mukesh Murugan!
link https://github.com/fullstackhero/dotnet-webapi-boilerplate/blob/main/src/Infrastructure/Services/General/TenantManager.cs not found. Can you give me the new link?
Hello jhon, I hope youâre very well.
Jhon, Do you resolved the cyclic injection?
Can you help me?
ngomezleal@gmail.com
I hope your answers please.
how to use one database in one user
You can use one database per tenant, which is already mentioned in this article. I am not sure of any use case where you need one user to use a single database. However you could still use this approach and just register one user per tenant.
ItÂŽs great. I learned to mucho about Multitenancy, Thanks for share your knowledge
Fantastic work Mukesh. You make programming so worth it.
I am looking forward to the fullstackhero project. It will be great for development.
One quick question. Can I implement this tutorial in blazorhero?
Regards,
George
Hi,
Currently, blazorHero doesnât have multitenancy. Once fullstackhero is done, i will try to add this feature to blazorHero as well.
Regards
Great article, but when I tried to create a new project by following your article, for some reason I cannot create migration. The error says:
âUnable to create an object of type âApplicationDbContextâ. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728â.
I tried to download your source code and try to add new migration as well and got same error. Any idea why is this happening and how to fix it?
nvm, itâs actually working⊠I forgot to build the app so that it creates empty database for migration
I ran into the same issue, for me what worked was this:
Go to the next step where Kesh added in everything to the Startup.cs ⊠do that, then the migration will work
Fantastic. This worked for me.
Did you use net6.0 with only Program.cs file instead of having Program.cs and Startup.cs as net5.0 in this tutorial?
Just copy Program.cs and Startup.cs from this tutorial to your Multitenant.Api and try again.
Pretty much as too the point and well written as one could possible ask for, on a really good topic.
Fantastic articulation, thanks for writing!
You should do a follow up article on how to implement true multi tenant, with accounts đ
Great Job! This is something, that I was really glad to find out. Could you also advise, how to deal with multi-tenancy with async processing of emitted events to message-broker from multi-tenant system? Also, it is interesting if is it worth moving multi-tenancy configs into separate microservice if you have multiple microservices (even with different programming languages)?
Hello Mukesh Murugan Congratulations for your post, its excellent.
I need your help. Can you guid me as make the folloging:
1.- How to get all products but without tenant name in headers? Imagine a catalog products the all tenants in a view.
With the demo, i can get all products by tenant. If I donât enter a tenant name in header, give me a error.
2.- In this post, you use a values for default in appconfig, as: java, charlie.
2.1.- I want register users (Tenants) and use them. How to you make?
Sorry for my english, Iâm learning đ
Thanks for your post, and I have been learnign with your descriptions.
I hope your soon answer.
Regards, Nelson
Great work Mukesh, very well explained and worth reading.
Hello Mukesh! This article looks perfect and promising. But I would like to ask if an application serves multiple database instance, then we are referring that to multi-tenancy, right? Am writing it according to the diagram you shared, one app with multi databases for each tenant. Just to clear it up.
Itâs not only about multiple databases. Multi Tenancy also includes a single databases shared by multiple clients with proper data isolation.
Thank you so much for sharing this excellent and valuable Multitenancy article.
Best Regards.
Hey,
What an amazing article, thanks so much for sharing this. If you wanted to take this one step further by using sql RLS and set the connection context when a connection is opened
Something like:
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = @âexec sp_set_session_context @key=NâCompanyIDâ, @value=@CompanyIDâ;
cmd.Parameters.AddWithValue(â@CompanyIDâ, CompanyIDint);
cmd.ExecuteNonQuery();
Where and how would you do this given the implementation above? Iâve read that you have to use the DbConnectionInterceptor, but not 100% sure where this would have to be implemented.
Thanks again for a great article.
Hi Mukesh,
Thank you for all your blogs, very helpful
Iâm following your steps using .Net6 API Template, after everything is in place, I try to do a Add-Migration and I get an error:
âAn error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Cannot access a disposed object.
Object name: âConfigurationManagerâ.
Unable to create an object of type âApplicationDbContextâ. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728â
I download this project from Github, and I change the TargetFramework to .Net6 and I can create the migration.
Now when I use the .Net 6 template o Program.cs, I got the same error, no code has been changed, just the Program.cs
This is what I add to your Project Program.cs, can you help me to find out what change? :
using Core.Interfaces;
using Core.Options;
using Infrastructure.Extensions;
using Infrastructure.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
// Add services to the container.
builder.Services.AddHttpContextAccessor();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.Configure(config.GetSection(nameof(TenantSettings)));
builder.Services.AddAndMigrateTenantDatabases(config);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
same problem did you get solution
you have to change the config. to builder.Configuration.
This is a very comprehensive architecture.
What do you think if I implement a generic repository using this instead of each entity with its own interface?
Great post, thanks for sharing.. I just ask myself, why we really do need that tenancy-id.. I mean, for each tenant you got an own connection-string (which makes sense) and then you store for each product the same tenant-id in the table? why do we need that? You would need that tenancy-id if you got multiple tenants share the same database, but isnât this the reason why you want to create a database for each tenant? Am I wrong?
Did you have any issues when it comes to seeding data for tenanted entities, especially when writing integration tests? Iâm using IEntityTypeConfiguration for all my entities instead of OnModelCreating (and no migrations either), so when I seed test data, the entity modification code in DbContext.SaveChanges() kicks in and assigns the current tenant ID to the entity, overriding the value that I configured in my test data.
Itâs great man, keep up the good work for the world.
Thanks!
Hello Mukesh
Have you or anyone implemented the functionality to store the tenant data into the database over this project or over this same architecture? Any kind of help and support will be appreciated. Thanks a lot in advance.
Br
Aryan
Hey! Yes, I have. Please check fullstackhero WebAPI đ https://fullstackhero.net/
Thanks a lot, for your prompt response, Mukesh đ
By the way, the project in this article and the full-stack hero web app-boilerplate are done with a similar architecture?
I guess this article is done with N-Layer architecture and full-stack hero web app-boilerplate is done in clean architecture?
Br
Aryan
Hi,
This does not seem near to completion.
Can you please tell us
1. How would you create database on the fly when new organization signs up? (Not on application start)
2. How do you determine tenant id on the fly (which database to check in case of dedicated DBs) on login ?
3. How would handle database crash during migrating new changes while application is being used by any tenant because migration needs to be applied on every database available.
Great & Clear
Thanks for sharing.
Really good step by step doc
Can you let me know how we could modify this code to create tenant with new db at runtime?
Able to create connection string in appsettings.json under tenants, but how to call AddAndMigrateTenantDatabases from custom controller, thatâs the problem
Could you please help on this front?
Thanks for sharing but when I tried to add âAppDataContextFactoryâ. It didnât work. Kindly assist
âpublic class AppDataContextFactory : IDesignTimeDbContextFactory
{
private readonly ITenantService _tenantService;
public AppDataContextFactory(ITenantService tenantService)
{
_tenantService = tenantService;
}
public ApplicationDbContext CreateDbContext(string[] args)
{
var tenantConnectionString = _tenantService.GetConnectionString();
var optionsBuilder = new DbContextOptionsBuilder();
// optionsBuilder.UseSqlServer(tenantConnectionString);
optionsBuilder.UseSqlServer(m => m.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
return new ApplicationDbContext(optionsBuilder.Options, _tenantService);
}â
Hello Mukesh,
I really enjoyed the project you wrote. There are ideas that will form the basis of the project I am working on. However, there is a part where I am stuck. I couldnât find a solution to anyone I asked. Could you please examine it? Iâm stuck and canât move forward.
https://github.com/msbeden/MultiTenantCore
Thank you very much in advance. Good work.
If I introduce new tenant, what should I do without stopping the web api.
Great article! Youâre right, thereâs not many articles that cover advanced aspects of multitenancy. I will be giving your a repo a star!
I build your sample into a prototype of my new API/Angular app. One of the two i have not right in my mind. I am using ASP Identity. So in my opinion the token need to be verified against the database. So my question is where can i see that the verification process takes place after selecting the right dbContext. Second; When i use a second dbcontext for selecting the tenant, how do i prevent the verification process use that dbcontext for verification the token.
How to extend the Multi-Tenant to Worker service/Background service. The same service should run for each tenant and Db to be stored for respective DB.
How do you implement the update the database using the previous migration?
e.g. update-database 20210830190011_initial