Setting Connection Strings In .Net Core Web API With Multiple Tenant SQL DB

I just wanted to share with you all about an idea I came across while working with multiple tenant DB based web applications where each consumer/subscriber of the application will have a separate database to maintain their data. We all know that it's pretty easy to set up a connection string for your Web API and use the same value every time from the appSettings.json file whenever your application gets a request to process.
 
But it will not help in this case where you would not have any idea about the connection string of the database. You are going to connect at the time of coding or compilation. Now, we have got to know that it is a challenging task to achieve but after reading this blog, I am sure you won't feel the same way.
 
Let's get to the point and get our hands dirty with some code!!
  1. Create your .NET Core 2.1/2.2 Web API with a controller and an action method in it.
    1. [Route("api/[controller]")]  
    2. [ApiController]  
    3. public class BlogsController: ControllerBase {  
    4.     // GET api/values    
    5.     [HttpGet]  
    6.     public ActionResult < string > Get() {  
    7.         return "value1";  
    8.     }  
    9.     // GET api/values/5    
    10.     [HttpGet("{id}")]  
    11.     public ActionResult < string > Get(int id) {  
    12.         return "value";  
    13.     }  
    14. }  
  2. Make sure you have created a DbContext class using Entity Framework Core (both code first/ Db first approach will work).
    1. public partial class BlogDbContext: DbContext {  
    2.     public BlogDbContext() {}  
    3.     public BlogDbContext(DbContextOptions < BlogDbContext > options): base(options) {}  
    4.     public BlogDbContext(string connectionString): base(GetOptions(connectionString)) {}  
    5. }  
  3. Create a repository class to perform the basic CRUD operations with DbContext class (this is just an additional step to create a well designed application and it can be skipped when your application has other designs).
    1. public class BlogRepository: IBlogRepository {  
    2.     private BlogDbContext context;  
    3.     public BlogRepository(BlogDbContext dbContext) {  
    4.         context = dbContext;  
    5.     }  
    6.     public void CreateBlog(List < Blog > blogs) {  
    7.         context.Blogs.Add(blogs.FirstOrDefault());  
    8.     }  
    9. }  
  4. Use .NET Core dependency injection to get the repository class instance on your API Controller action method. You will be able to call CRUD operations from your controller.
    1. public class BlogsController: ControllerBase {  
    2.     private IBlogRepository _blogRepository;  
    3.     public BlogsController(IBlogRepository blogRepository) {  
    4.         _blogRepository = blogRepository;  
    5.     }  
    6.     // GET api/values    
    7.     [HttpGet]  
    8.     public ActionResult < IEnumerable < Blog >> Get() {  
    9.         return _blogRepository.GetBlog();  
    10.     }  
    11. }  
  5. Add a connection string property to the appSettings.json file and refer to your DbContext class inside Startup.cs file along with connection string. You will be all set to call your API to connect to a single SQL database for now.
    1. "ConnectionStrings": {    
    2.    "MasterDatabase""Server=.\\SQLEXPRESS;Database=Master;Trusted_Connection=True;"    
    3. }    

  6. Until now, we just followed the regular steps to create a Web API with basic CRUD operation with SQL database. Now, we will make this API into a multi-tenant database API. For that, we need a master database where all of our subscriber's DB connection strings are stored or any other way of the source which holds their value in any form. (In my case, I had a master database where all subscriber DB connection strings were stored along with their username and password).
    1. public void ConfigureServices(IServiceCollection services) {  
    2.     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);  
    3.     services.AddDbContext < MasterDbContext > (options => options.UseSqlServer(Configuration.GetConnectionString("MasterDatabase")), ServiceLifetime.Singleton);  
    4.     services.AddDbContext < BlogDbContext > ();  
    5. }  
  7. Go to your DbContext class, add a static property, and name it as ConnectionString. Then, add the following code into OnConfiguring() method as shown below.
    1. public static string ConnectionString {  
    2.     get;  
    3.     set;  
    4. }  
    5. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {  
    6.     //if (!optionsBuilder.IsConfigured)    
    7.     //{    
    8.     //#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.    
    9.     if (!string.IsNullOrEmpty(ConnectionString)) optionsBuilder.UseSqlServer(ConnectionString);  
    10.     // }    
    11. }  
  8. Now, create a middleware (Don't panic. Just a normal .cs file) and add the following code into it. Middleware are the layers in the Web API which will be executed in the order they were configured in the startup file. So every time a request comes into the API, it will pass through this middleware and we can decide what these middlewares are going to do.
    1. public class AuthenticationMiddleware {  
    2.     private readonly RequestDelegate _next;  
    3.     private MasterDbContext _masterContext;  
    4.     public AuthenticationMiddleware(RequestDelegate next, MasterDbContext masterDevContext) {  
    5.         _next = next;  
    6.         _masterContext = masterDevContext;  
    7.     }  
    8.     public async Task Invoke(HttpContext context, BlogDbContext blogDBContext) {  
    9.         try {  
    10.             string authHeader = context.Request.Headers["Authorization"];  
    11.             if (authHeader != null && authHeader.StartsWith("Basic")) {  
    12.                 //Extract credentials    
    13.                 string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();  
    14.                 Encoding encoding = Encoding.GetEncoding("iso-8859-1");  
    15.                 var usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));  
    16.                 int seperatorIndex = usernamePassword.IndexOf(':');  
    17.                 var username = usernamePassword.Substring(0, 9);  
    18.                 var password = usernamePassword.Substring(seperatorIndex + 1);  
    19.                 if (true//check if your credentials are valid    
    20.                 {  
    21.                     BlogDbContext.ConnectionString = ""//_masterContext.Retrive Your subscriber connection string here    
    22.                     if (string.IsNullOrEmpty(BlogDbContext.ConnectionString)) {  
    23.                         //no authorization header    
    24.                         context.Response.StatusCode = 401; //Unauthorized    
    25.                         return;  
    26.                     }  
    27.                     await _next.Invoke(context);  
    28.                 } else {  
    29.                     context.Response.StatusCode = 401; //Unauthorized    
    30.                     return;  
    31.                 }  
    32.             } else {  
    33.                 // no authorization header    
    34.                 context.Response.StatusCode = 401; //Unauthorized    
    35.                 return;  
    36.             }  
    37.         } catch (Exception e) {  
    38.             // no authorization header    
    39.             context.Response.StatusCode = 400;  
    40.             return;  
    41.         }  
    42.     }  
    43. }  
  9. Inside middleware, we are getting the userId & password (in encrypted form) through HTTP request headers from the client request. Using these credentials, we need to query the master database and retrieve the respective client's database connection string. Once the connection string is retrieved, set it to the DbContext static ConnectionString property.
    1. if (true//check if your credentials are valid    
    2. {  
    3.     BlogDbContext.ConnectionString = ""//_masterContext.Retrive Your subscriber connection string here    
    4.     if (string.IsNullOrEmpty(BlogDbContext.ConnectionString)) {  
    5.         //no authorization header    
    6.         context.Response.StatusCode = 401; //Unauthorized    
    7.         return;  
    8.     }  
    9.     await _next.Invoke(context);  
    10. else {  
    11.     context.Response.StatusCode = 401; //Unauthorized    
    12.     return;  
    13. }  
  10. If you do not receive a valid userId and password, you can easily redirect the bad response code to the client. 
  11. Finally, you can configure your middleware in the startup.cs file under the Configure method.
    1. public void Configure(IApplicationBuilder app, IHostingEnvironment env) {  
    2.     if (env.IsDevelopment()) {  
    3.         app.UseDeveloperExceptionPage();  
    4.     } else {  
    5.         // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.    
    6.         app.UseHsts();  
    7.     }  
    8.     app.UseHttpsRedirection();  
    9.     app.UseMiddleware < AuthenticationMiddleware > ();  
    10.     app.UseMvc();  
    11. }  
  12. Now, whenever there is an incoming HTTP request, it will go first to middleware, get the dbConnection string, and then route it to the appropriate controller.

  13. That's it. We are done with the implementation of setting the connection strings for every request in .NET Core API with the multi-tenant database.
Thanks for reading! Your feedback is most welcomed. Please notify me with the reason if you think this is not the right way of implementation.

Next Recommended Reading Dynamic Connection String In .NET Core