ASP.NET Multi-Tenant SaaS App in 20 Minutes (EF Core) - Free Tutorial + GitHub Code Project

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello.net developers today I'm going to show you how to build a multi-tenant web application we're going to use asp.net 7 and Entity framework core first we'll set up our web API application as just a regular crud application with one entity called products and then after that we'll transform the solution to be multi-tenant so it's going to be a simple architecture it's going to be a single database multi-tenant architecture we're not going to be using any third-party plugins we're going to do all of this with just middleware and some query filters by the way there's a written version of this tutorial it's on my blog at aspnano.com alright so the first part of this video like I said we'll set it up as a credit application I'm going to go kind of quickly through that because that's not the focus of this tutorial but once we have that we'll start adding in the elements that makes this multi-tenant Behavior work so I'm going to go create a new project in Visual Studio 2022 and I'm going to select asp.net core web API so give it a name and choose the framework we won't have any authentication we're not going to use ASP identity we won't have any login or anything like that again if you're interested in that type of thing definitely check out the boilerplate the links in the description the boilerplate has all of that okay so here's our application I'm just going to delete this weather forecast so let's start by installing the necessary Entity framework nougat packages there's three that we need to install the first one is Entity framework core the next one is Entity framework SQL Server because we're going to use SQL Server as our database and lastly Entity framework core tools this will help us scaffold our migrations okay so next let's create a folder called models and we'll add a class called applicationdb context make sure your application DB context derives from DB context which is an Entity framework core class and next let's create a new entity called Product our product entity will just have an integer ID a name and a description next let's add that entity as a DB set to our application DB context let's create a new folder called services we'll create a new class called Product service and an interface called iproduct service let's also create a folder called dtos and let's create a class called create product request this will just be a simple dto class for when we create products okay next you'll want to create the product service methods like this first you'll create a Constructor with the application DB context we'll have one method for get all products that returns a list a create product method that takes the product request and saves the changes to the database and a delete product method that deletes the product from the database and at the top here you see that the service implements the iproduct service interface so next let's add this service to our service container I'm going to get rid of swagger and just here this line Builder Services add transient iproduct service the interface and the product service the service next let's create a controller that can use this service we're going to create an API controller empty and let's call it the products controller the products controller will use our product service like so using dependency injection and we'll create a git method a post method and a delete method to use each of the methods that we have in our service with that out of the way we should be able to do credit operations using rest commands and interact with our application once it's running with Postman one more thing we need to do is we need to go to app settings.json and we need to add our default connection string so add that like so and you can change this database value to be whatever you want the name of the database to be in mine I'm naming it MTA DB multi-tenant app database next go to the program.cs and add this line builder.services add DB context application DB context we're going to pass some options to use SQL server and tell it to use the default connection at this point we've done everything that we need to create a functional crud application web API and no doubt you've seen this a thousand times before nothing we've done up to this point is new but now that we're all on the same page we can start to make this multi-tenant so let's start by creating a new entity called tenant now most tutorials there's not that many tutorials on multi-tenancy but almost all of the ones that I've seen they don't actually show you how to manage the tenant Keys within a database all of the examples are super simple and just show you how to do it with a Json list or something like that but that's not really useful because you're going to store tenants in a database right our tenant entity is going to have two columns it's going to have an ID and a name the ID is going to be a string value not an integer and why is that well it's because it'll make our life easier and also because tenants are typically identified by strings if you think about a SAS application where every tenant has their own subdomain that subdomain is usually the ID of the tenant so it makes sense to save our tenant Keys as strings now since we're doing that we need to tell Entity framework that this is what we want to use for our ID so that's why we have this first annotation here the second annotation is saying that we will generate our own Keys we don't want the database to do it so you'll need to add both of these data annotations and you'll have these using statements at the top in this tutorial we're just going to manually create tenants in our database directly we're not going to set up any kind of controller or service for managing that now in our product entity we're going to need to add another column which is the tenant ID but before we do that let's do something else let's create a contract interface the contract interface we're going to create is called I must have tenant and this interface is going to have one property a string tenant ID now let's implement this interface on product and add the field so this interface will help us Group which entities should be tenant isolated maybe not all of them should be tenant isolated next let's add this DB set tenants in our application DB context and now we can create our first migration so add migration initial and this looks good so let's go ahead and apply the migration with the update database command now if we open up our database Explorer we'll see that there's a new database created MTA DB with two tables products and tenants and the products table has name description tenant ID just like we wanted and the tenants let's go ahead and create a few now the way that we're going to resolve our tenants is by reading a request header so for every single request we're going to be looking for a header that has a key value of tenant and we're going to read that value on every request that we send up we can change this and it will signify that a different tenant is making that request now in the real world you would only want to do this on unauthenticated requests like when someone is logging into the application but for authenticated requests you wouldn't want to do that because these can be easily changed and authenticated request you would want to read a value or a claim from a token or a cookie if you're using pages now that's outside of the scope of this tutorial but if that's something that you're interested in look into the Nano boilerplate because that's got all that already set up and it can be quite tedious to try and work through all that so again the links in the description if you're looking to Fast Track a new web application or a SAS project so on every request the thing that we want to do before anything else happens is read that tenant key in the header the perfect tool for the job in that case is middleware because middleware runs before any controller is hit or anything else happens in the application so let's create some middleware let's start by creating a new folder called middleware and we'll add a new class in middleware called tenant resolver so I've set up this middleware just like you would set up any middleware with the request delegate in the Constructor and with this async method here what we're doing is we're reading from the HTTP context object the HTTP context object is an important object it includes all of the information about the incoming requests and the information we want to get there is the headers so here what we're doing is we're looking for a header with a key value called tenant what we want to do is we want to hold on to that ID and make it available for the rest of the request lifecycle do that what we'll do is we'll create a scoped service so let's do that let's go in the services let's create a new interface in a new class we'll create a new interface called I current tenant service this service will have one property string property for the tenant ID and a method for setting that ID now let's create the service called current tenant service and the service will look like this will have a string where we save the tenant ID and we'll have the method where we set it in the method set tenant we'll need to check the database to see that this tenant exists we also might want to load subscription information or to check to see if this tenant is active now the important thing about this service is that we register it as a scoped service and the reason for that is because we don't want this value to change at all throughout the entire lifetime of the request in the program.cs will add this here as a scope service now let's remember to add our middleware here with the used middleware directive and pass in tenant resolver now back in the tenant resolver we'll want to make use of this service with middleware we inject services like this we don't actually inject them into the Constructor it's just convention for middleware now if we find a header called tenant and we have a key then we'll use our current tenant service method set tenant and populate that value now what we can do in application DB context is we can use this service and we can read the tenant ID anytime that the database context is accessed so now we'll add a new override method in the application DB context public override save changes and this will run anytime that we save any changes to our database so anytime that we save changes the current tenant ID that we get from our service will be saved in the tenant ID column of that entity now that's one piece of the puzzle the other piece of the puzzle is that we want to filter anytime that we read from the database so to do that we'll have to add another override method and the one we're looking for is on model creating on model creating is an override that fires when the application starts up unlike save changes that buyers every time we save something within on model creating this is where we'll use a query filter our filter will say only return those rows where the tenant ID equals the current tenant ID from our current tenant service and with those two things we have a basic multi-tenant architecture but if you try to run this right now you'll get an error the problem is is that in our tenant resolver which triggers at the beginning of the request we use this current tenant service which has the application DB context being instantiated in the Constructor and the application DB context needs this service to already be ready to go so that's a problem now if you try to run this right now this is the air that you'll get a circular dependency was detected so what we need to do is we need to create another DB context one that is just used for looking up the tenants in that middleware so go to your models folder and let's create a new DB context let's call this tenant DB context the tenant DB context should also derive from the DB context class from Entity framework core and it should only have one DB set which is tenants now like we said earlier this is only going to be used for looking up tenants it won't be used for any kind of migrations when we add any other kind of entity to our application like customers or orders or whatever we won't add them here we'll add them to our application DB context but since we're using two DB contexts now we need to we need to give DB context options another parameter so just put tenant DB context here and an application DB context pass application DB context to the options here finally we'll need to register this new database context like so using the same default connection back in our current tenant service we'll use this tenant DB context instead of the application DB context and now we won't have any problems with the circular dependency so let's go ahead and run the app so open up Postman and make sure that you've got your localhost port correct to whatever yours is locally and make sure that you have a key for tenant and let's give it a value for a beta tenant that's one of the tenants we created manually in SQL Server and our body will go to Raw Json and format it like so giving it a name and a description click Send and so far it looks successful let's go ahead and create a few more products and change the header for some of these products now when we go to get our products when we put in our beta tenant we should only retrieve the products that belong to Beta and let's see what happens perfect it looks like it works so let's change the value to Alpha now and send the request and look at that multi-tenancy now if we want to see how this all works step by step we can go back here and we can set a few break points the first break point that will be hit is our tenant resolver so let's go ahead and set a breakpoint here the next step in the request journey is the current tenant service so let's set a break point here and then let's set a breakpoint in the controller and we'll follow it through at that point so our header here is set to Alpha let's go and send as you can see the first break point hit is the tenant resolver and here we can see that the tenant from header value is Alpha so now it's going to go to the tenant service and it's going to find that this tenant exists at this point maybe we retrieve other information about the tenant who knows so if our tenant info is populated then we set that tenant ID here in the service and now the request is hitting the controller when we go and add the product with the application DB context we can see that the application DB context has instantiated with the alpha tenant ID and so it's automatically going to save that tenant ID as Alpha when it saves This Record now if we do the same thing for a get request we'll see once again that the first thing that gets hit is the tenant resolver middleware and then we set that information in the current tenant service and in the application DB context I'll set a breakpoint here in the Constructor because I want to show you that if we set a breakpoint here in on model creating it'll never be hit and the reason for that is is that this override only fires once on the app startup so that might be a bit difficult to wrap your head around when you're messing with this but that's just the way it works so that concludes the tutorial I hope that you learned something again there's a written version of this at my blog aspnano.com this is similar to how multi-tenancy is implemented in the Nano boilerplate the Nano boilerplate however takes this a few steps further it integrates with ASP identity it's built around clean architecture it implements the repository pattern the generic specification pattern it has endpoints and controllers for managing tenants managing users and the full version comes with two UI projects one is a Razer Pages project and the other is a react project both of them are made with premium themes with a full set of components light and dark mode if you're an individual or a startup or a small team and you want to build SAS or a multi-tenant web application definitely check it out it'll save you a lot of time the Link's in the description if you like this video go ahead and subscribe and I'll post some more and good luck with your journey
Info
Channel: Nano ASP Boilerplate
Views: 11,486
Rating: undefined out of 5
Keywords:
Id: Cm1UXLm-vPc
Channel Id: undefined
Length: 22min 24sec (1344 seconds)
Published: Sun Sep 24 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.