I’ve been faced with a daunting challenge the last few months which is how to effectively create a multi-tenant architecture utilizing asp.net MVC 4. Creating an architecture like this can work several different ways depending on what you want to do. My particular project has a few goals.
First, the application should support tenants as “sites”. The entire project is hosted on one IIS configuration with one application pool. I’m not really going to get into the benefits of Multi-tenant architectures but one inherent benefit is that any number of sites can run on one code base. That is the approach I want to take.
Second, I’m basically converting an existing project to be multi tenant. What my approach does is introduce a Tenant table in the database which exposes a few basic properties about each tenant including a TenantID. This TenantID needs to be introduced into any table you want to be able to segregate based on tenant. You’ll find at least three schools of thought how to segregate tenant data in a database. You can create tables for each tenant making it easy to back up and restore only one tenant. You can also even create separate databases. My method is keeping all the data together and segregating by TenantID.
Third, I need a custom view engine to facilitate the view organization I desire. The structure I would like to end up with is View -> Tenant Name -> Views. I also want a View -> Global -> Views folder which is available if the view is not found in the Tenant folder. This allows me to share similar views such as in my project eCommerce shopping cart, check out, and payment code. This image below gives a nice image of what I’m talking about. More on the view engine in a bit.
Handling Static Files
You can also see I’ve restructured the /Content folder creating Global Folders for images and styles, then breaking out another Tenants folder for tenant specific resources. One issue you will run into quite quickly is how to deal with files such as robots.txt or favicon.ico. These two files are common on most any site (including many more) and must have copies for each tenant. My solution is to utilize the IISrewrite feature storing the rewrite rules directly in my web.config. An example below routes the favicon for site 1 to the proper folder. This isn’t ideal in my opinion since the web.config can get large quickly, but it does work quite well.Storing the Tenant List in Memory
As you will soon see we need to know the list of tenants every request to determine which tenant is requesting the page. In order to do this I load the current list of tenants at the application_start(). This is a simple FetchAll() into a list using Entity Framework 5. I’m not an expert on thread safety but once this data is loaded it will only be read from this point on.public class MvcApplication : System.Web.HttpApplication |
Determining the Tenant at the Controller
It is useful for several reasons to always know which tenant you are dealing with at the controller level. For this reason I overloaded Controller and created my own MultiTenantController that inherits Controller. All my controllers now inherit from MultiTenantController. I can do a few useful things now including intercepting the MasterName property of the ViewResult and setting that manually if needed. More importantly this is where I determine the tenant based on the domain name.public class MultiTenantController : Controller |
Custom View Engine
This is something that might not fit your needs exactly, but this is a pretty generic approach to handling views. As I discussed above I want a global folder which allows me to create views that are shared across all tenants. I also want to be able to specify views for specific tenants since each site can have different features.Here is sort of what I based my approach off of.
http://weblogs.asp.net/imranbaloch/archive/2011/06/27/view-engine-with-dynamic-view-location.aspx
This view engine is pretty straight forward. I’m using razor in my project so this extends RazorViewEngine. The key part is that I’m pulling out of the controllerContext the controllerContext.Controller and casting as my MultiTenantController. Once I do this step I can now access my CurrentTenant variable we just talked about. At this point the %1 is simply replaced with the CurrentTenant folder name.
public class MulitTenantRazorViewEngine : RazorViewEngine |
The Final Result
At this point you can have any number of tenants hosted under one code base. On my particular project this allows me to maintain much less code while any enhancements I make to say checkout pages or features shared across tenants are all echoed immediately. I also have the ability to create totally different views and experiences for each tenant while still sharing key parts of my application. From an eCommerce standpoint my orders now funnel into one administration area making it much simpler to manage.Further Considerations
Here are some other things I will be considering during this projectSSL
For an eCommerce site it is important to have a means to SSL secure. Since the site is hosted under one application in IIS it is not possible to use separate SSL certificates for each domain name. My strategy involves a wildcard SSL certificate for your “main” site. This can be difficult if your tenants maintain no relation at all, but mine do. Because of the way I route static files, and test domain name my system already works for sub domain based SSL. If you need SSL on site2 you would simply do https://site2.site1.com assuming site one is the “main” site you want to create the certificate for. The routes and domain test are set up to only read the information before the first, so site2 is flagged as the CurrentTenant.Modular Design
This is more a design consideration. When creating features for multi tenant sites it is wise to create features that are modular. Perhaps visualize something you can turn on and off arbitrarily for each tenant. By doing so you can create features that can be easily shared among current or future tenants.Update – A Sample Project
Ok, I get it everybody wants a sample project to try this. One of the reasons I haven’t yet is because this configuration isn’t something you can just open and hit run. You’re going to have to configure some custom settings to make the demo work, but here it is!Keep in mind there are various ways to route to static files such as robots.txt/favicon.ico. The demo shows a pretty bare bones way of doing it, it could easily be tweaked to be slightly more maintainable such filtering each file run into one rule for each file.
Set Up the Hosts File
You need to add two entries to your local DNS server or HOST file in windows. If you don’t know how, read this127.0.0.1 site1.com
127.0.0.1 site2.com
Setting Up the Sample Project
Download the sample project here- You’ll need to run visual studio as administrator. Right click Visual Studio > Run as Administrator
- Extract and open the project. You might get an error about IIS.
- We need to set up an IIS site to run the project, we will not be using the built in web server. Create a new site in IIS, map it to the folder of your project. Add two bindings as show below.
- Head back into Visual Studio and goto Project > MvcMultiTenant Properties then set up as below
- The previous step requires administrative access so that is why you need to start visual studio with Admin privileges.
- At this point you should be able to build the project and start/Debug
0 nhận xét:
Đăng nhận xét