http://www.developer.com/

Back to article

Introduction to Multi-Tenant Architecture


February 11, 2009

Have you ever built multiple websites that have similar core functionality but really differ only in a few ways, such as layout and UI? This can be costly and time consuming. Instead of copying and pasting code, giving it different namespaces, and then setting each to a different site name, I will discuss an architecture concept referred to as Multi-Tenant Websites, which helps improve upon the scenario I just mentioned. These websites allow developers to create a multitude of sites with slight discrepancies, while maintaining a shared code-base and appearing different to the end-user. I will go into the main concepts that the Multi-Tenant Architecture Utilizes and show some examples of these.

Tenants

The first piece to start with is having a table in your database to map each tenant (NOTE: When I refer to "Tenant," this term can be used interchangeable with "Website" to limit confusion) to an ID. For this case, a simple Database Table with an Identity column and name of the tenant as shown in Figure 1 will be enough.

Figure 1: Table Containing List of Tenants using the website.

This Table will allow you to Map all tenant specific data to its proper tenant.

App URL and App Settings

This next piece is the most critical concept to successfully differentiate websites from each other sharing the same code base and, in most cases, the same database. From the first section, you might wonder how you determine which tenant is active as a user is navigating to its respective site. Well, you can utilize the URL string and create a mapping table of all the possible URLs for the web server and relate it to its corresponding tenant ID (I will demonstrate the mapping later on in the article). Figure 2 illustrates the table used for the mapping.

Figure 2: Table mapping URLs to a specific Tenant Id.

This table maps the "localhost" URL to load Tenant Id = 0.

One piece critical to a Web Application is utilizing the Web.Config to store global settings that can be configured easily and not hard-coded in the application. The problem with multiple websites sharing the same application is some app settings may be applicable to all of them but the value may need to be different. To handle this, I have created an App Settings table shown in Figure 3 that matches a group of app settings to a tenant.

Figure 3: App Settings Table Definition

Now, when a user accesses one of the tenants the first time, an app setting is used. You need to determine which tenant it is and load the app settings for that tenant. To prevent constant database hits and increase performance, I have created a class to store the app settings in the HTTP Cache inside a hash table that will help maintain the concept of accessing the settings from the Web.Config file. Quick note, I am using a LINQ To SQL Data Context to handle all the database interaction.

namespace MultiTenantSite.Data
{

/// <summary>
/// This class is used to manage the Cached AppSettings
/// from the Database
/// </summary>
public class AppSettings
{
   /// <summary>
   /// This indexer is used to retrieve AppSettings from Memory
   /// </summary>
   public string this[string Name]
   {
      get
      {
         //See if we have an AppSettings Cache Item
         if (HttpContext.Current.Cache["AppSettings"] == null)
         {
            int? TenantID = 0;
            //Look up the URL and get the Tenant Info
            using (MultiTenantDataContext dc =
               new MultiTenantDataContext())
            {
               AppURL result =
                      dc.AppURLs
                      .Where(a => a.vc_URL =
                         HttpContext.Current.Request.Url.
                            Host.ToLower())
                      .FirstOrDefault();

               if (result != null)
               {
                  TenantID = result.in_Tenant_Id;
               }
            }
            App.AppSettings.LoadAppSettings(TenantID);
         }

         Hashtable ht =
           (Hashtable)HttpContext.Current.Cache["AppSettings"];
         if (ht.ContainsKey(Name))
         {
            return ht[Name].ToString();
         }
         else
         {
            return string.Empty;
         }
      }
   }

   /// <summary>
   /// This Method is used to load the app settings from the
   /// database into memory
   /// </summary>
   public static void LoadAppSettings(int? TenantID)
   {
      Hashtable ht = new Hashtable();

      //Now Load the AppSettings
      using (MultiTenantDataContext dc =
         new MultiTenantDataContext())
      {
         var results = dc.AppSettings.Where(a =>
            a.in_Tenant_Id == TenantID);

         foreach (var appSetting in results)
         {
            ht.Add(appSetting.vc_Name, appSetting.vc_Value);
         }
      }

      //Add it into Cache (Have the Cache Expire after 1 Hour)
      HttpContext.Current.Cache.Add("AppSettings",
         ht, null,
         System.Web.Caching.Cache.NoAbsoluteExpiration,
         new TimeSpan(1, 0, 0),
         System.Web.Caching.CacheItemPriority.NotRemovable, null);

         }
      }
   }
}

With this App Settings class, I can access app settings for the current tenant efficiently and, more importantly, when I write my code, the settings will adapt as long as they are updated respectively for each tenant.

The following code listing shows a page sending an email after a user registers for the site. Typically upon registration, some sort of email is sent to give that sense of confirmation or welcome message to the new user. If I had multiple tenants, I couldn't store the email settings in the Web.config because each site may want a different subject, body, and so forth. And, you definitely do not want to hard code for each case!!

namespace MultiTenantSite
{
   public partial class _Default : System.Web.UI.Page
   {
      protected void Page_Load(object sender, EventArgs e)
      {

      }

      protected void CreateUserWizardControl_CreatedUser(object
         sender, EventArgs e)
      {
         // Create the Mail Message from the Cached App Settings
         // For the Tenant
         MailMessage confirmEmail = new MailMessage()
         {
            From = new MailAddress(new Data.AppSettings()
               ["Site.Registration.Email.From"]),
            Subject = new Data.AppSettings()
               ["Site.Reigstration.Email.Subject"],
            Body = new Data.AppSettings()
               ["Site.Registration.Email.Body"],
         };

         // Create the SMTP Client and Send the Message
         SmtpClient emailSender = new SmtpClient("Some SMTP Host");

         emailSender.Send(confirmEmail);

      }
   }
}

Using a Content Management System

The next component to a multi-tenant application, a Content Management System (CMS), is optional but will help to display content dynamically on-demand for tenants that use the site. Designing a CMS can be an article within itself. Really, in this situation, you would want to use the CMS system to create content in a web-part format. You then can use these web parts interchangeably and on demand as you design your pages for a tenant. Typically, if the content is static with only different app settings and different App Themes, a CMS may not be needed.

To illustrate what I am talking about, assume I have a CMS system for a Multi-Tenant site where a database drives the system and stores web parts that are created are stored through some web part manager on the site. Now, in the actual solution, I have a footer control that is rendered on the master page, and on the footer I have three links: FAQ, Contact Us, and About. Each tenant that is using this base website may have different text that will need to be displayed for each link. With a Multi-Tenant site powered by CMS, I could create web parts for each different link for each tenant and set the PostBackUrl Property of each link button through the app settings.

Footer Markup

<table width="100%">
   <tr>
      <td align="center">
         <asp:LinkButton runat="server" ID="lnFAQ">
         </asp:LinkButton>
      </td>
      <td align="center">
         <asp:LinkButton runat="server" ID="lnContactUs">
         </asp:LinkButton>
      </td>
      <td align="center"><asp:LinkButton runat="server"
                                         ID="lnAbout">
         </asp:LinkButton>
      </td>
   </tr>
</table>

Code Behind (Setup Links)

public partial class Footer : System.Web.UI.UserControl
   {
      protected void Page_Load(object sender, EventArgs e)
      {
         // Set the Post Back URLs for each link
         lnContactUs.PostBackUrl =
            GetRelativeCmsUrl("Site.Link.ContactUs");
         lnFAQ.PostBackUrl = GetRelativeCmsUrl("Site.Link.FAQ");
      }

      public static string GetRelativeCmsUrl(string configKeyName)
      {
         // return link to the CMS Page that Handles Web Parts,
         // Retrieve the ID of the Web Part from the
         // Cached App Settings
         return (String.Format("~/cms/page.aspx?ContentID={0}",
                 new Data.AppSettings()[configKeyName]));
      }
   }

IIS Setup

The last piece to successfully implement a Multi-Tenant Web Site is setting up IIS to handle each website. This is a relatively simple concept, especially if you have already set up a basic ASP.NET website in IIS. To make this a little more complex and really demonstrate where Multi-Tenant architecture is handy, assume your server has only one public facing IP to register a website under. First, you will need to create a new website for each tenant. One problem with this: You need to assign an IP to each site but you only have one. A solution to this is to use host headers for each site, map each host header to the single IP, and then register the host headers with your respective DNS server.

As an example, say you have a standard multi-tenant designed website that supports the needs for a newspaper company and there are three small newspapers that cannot afford to host their own sites, so they combine their finances to use the multi-tenant solution on a server. They are assigned an IP (999.9.999.9), and then you create host headers that map to the IP:

IP Host Header Name Tenant Id
(For App URL Table)
999.9.999.9 newspaperA.com 1
999.9.999.9 newspaperB.com 2
999.9.999.9 newspaperC.com 3

Now, when each URL is hit, as demonstrated earlier in the article, you will refer to the App URL to get the proper tenant ID from the URL, thus allowing you to display and load the proper content for the site.

Summary

The Multi-Tenant Architecture can be a very powerful design decision when working with potential business solutions that have similar core functionality but differ only in higher-level aspects such as UI and Layout. The concepts explained here are the foundation for the Multi-Tenant Architecture. Depending on your application, there may be extra steps to utilize the design in an effective manner. From my experience, I strongly recommend this architecture when creating platform web solutions where the platform is the core functionality across each application.

About the Author

Neal Schneider (MCTS) is a solutions developer with Crowe Horwath LLP in Indianapolis, Indiana. Neal specializes in Web-Based solutions. Reach him at neal.schneider@crowehorwath.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date