Multi Presentation (tenancy) Websites for c#

So you want to build a single web application that can be used multiple times and deployed once? Have you ever built a site, only to have another customer request to have it replicated with some minor changes? One possibility is to copy the files into another directory, make changes as needed and deploy. Of course a month later your customer requests a bug fix or functionality changes to approximately the same file that now exists in two sites. Wouldn’t it be nice to have one set of files that you could change and have the change reflected on all your applications? Creating a multi presentation project might be for you.

In a Nutshell (the short version)

To make a fairly long story short, we will be creating an httpmodule that, upon being called checks to see if an application level variable for the application version has been set. If it finds that the application setting variable is null, the system will attempt to look up a record for itself inside the web.config file using the Request.URL.Host string. If the string exists as an app setting, we can grab the value (in this example I will store a numeric ID to pull up a database record for settings. Once you have your settings the only question is how to get all those sites pointed at the same set of files. To get this accomplished I use IIS to create multiple sites pointed at the same set of files with host header records to store what the incoming host name is for the site.

Let’s Break This Down…

Since this type of site takes some doing, lets go over the steps first:

  1. Create your project
  2. Create an CustomAppSettings class to store some basic information about the different versions of your site.
  3. Create a method to fill that CustomAppSettings class per site.
  4. Modify your site based on the version of the site your users have requested.
  5. Setup IIS to handle the different sites.

Create your project

There isn’t really anything tricky going on in the early stages here. Since we will be using the settings in our web.config file, go ahead and add a System.Configuration reference to your project. The System.Configuration reference will allow us to make calls to ConfigurationManager.AppSettings.
Adding the System.Configuration reference

Create an AppSettings class to store some basic information about the different versions of your site.

So now we want to create a class that will store some basic information like an application id, the site’s address, and any other settings that one might need for the application version to run independently of the other applications you will be hosting.

	public class CustomAppSettings
	{
		public int ID { get; set; }
		
		//This property will be to store the short name of our application version
		//for a template directory, if we choose to use one.
		public string ApplicationReference { get; set; }
		
		//This is the hostname our application is coming in on.
		public string HostName { get; set; }
	}	

This app settings class can be expanded significantly to include all sorts of specific settings like you would normally put in a single presentation project like SMTP servers, etc.

Create a method to fill that CustomAppSettings class per site.

We now need a method to store our CustomAppSettings into our Application object for easy access later. In a recent post (Storing User Information in Session for c#) I wrote up a class called GlobalVars that makes accessing untyped storage objects like Application and Session easier. For this project, let’s use the GlobalVars class to create a placeholder for our CustomAppSettings Object.

	public static class GlobalVars
	{
		public static CustomAppSettings CurrentAppSettings
		{
			get
			{
				object o = HttpContext.Current.Application["_CurrentAppSettings"];
				if (o != null)
				{
					//one could potentially do thier setting of the currentapp variable here
					//using the HTTPContext.Current object.
					return (CustomAppSettings)o;
				} 
				return null;
			}
		}
	}

With this code we can just type in GlobalVars.CurrentAppSettings.HostName to easily access our CurrentAppSettings whenever we need it.

The next part of this step is to get the CurrentAppSettings application level variable stored so it doesn’t return null all the time. To do this, we are going to create an httpmodule.

public class ConfigureApp : IHttpModule
{
	public void Dispose()
	{

	}

	public void Init(System.Web.HttpApplication context)
	{
		context.BeginRequest += context_BeginRequest;
	}

	public void context_BeginRequest(object sender, EventArgs e)
	{
		HttpApplication application = (HttpApplication)sender;
		Uri url = application.Context.Request.Url;

		//plug on the application setting the first time someone pulls up the app.
		if (CurrentAppSettings == null)
		{
			//check to see if our value exists in the web.config file.
			string config = ConfigurationManager.AppSettings[url.Host.ToString().ToLower()];
			
			//Setup a long variable for our try parse.
			long holderid = 1;
			if (!string.IsNullOrEmpty(config))
			{
				Int64.TryParse(config, out holderid);
				if (holderid <= 0)
				{
					holderid = 1;
				}
			}
			
			//this is where we would load our id from the db and set
			//the different settings.
			CurrentAppSettings = LoadMyApp(holderid);
		}
	}
}

While building the httpmodule you might have noticed that there is a call in there to the web.config. We actually need to make a couple different changes to the web.config file, first the reference to run the new httpmodule in our web app and, second, the application url references. Let’s take a look at those now.

Registering the http module

	<system.web>
		<httpModules>
		  <add name="ConfigureApp" type="ConfigureApp" />
		</httpModules>
	  </system.web>
	<system.webServer>
		<modules>
		  <add name="ConfigureApp" type="ConfigureApp" />
		</modules>
	</system.webServer>

Note that there are two different registrations here. The first is for iis6, the second for iis7. You will also note that the name and type both reference the name of our class from the http module.

Creating Our Application References

<configuration>
	<appSettings>
		<add key="MySite1.MyDomain.com" value="1"/>
		<add key="www.MySite1.MyDomain.com" value="1"/>
		<add key="MySite2.MyDomain.com" value="2"/>
		<add key="localhost" value="1"/>
	</appSettings>
</configuration>

We have added a few different settings into our appSettings section here to let the httpmodule know which app should point at which application. An important note here is that any individual host header record one makes into IIS needs to be entered here as, without it, the record won’t pull up from the web.config. You will also note that I have included a reference for “localhost”. If you are doing work on the different versions of the site in Visual Studio, the local web server will show up “localhost”. You can alter which version of the site you want to work on by changing the value of the localhost app setting.

Create the Site in IIS

We are on the home stretch now. Open up IIS and add a web site.
Adding a new web site
Note we are using a host header record here. Point your new web site to your file set and hit Ok. Your new web site will be up on that new host header record. To put up more sites on the same set of files, rinse and repeat!

About these ads

9 responses to this post.

  1. Posted by Matt on February 22, 2010 at 6:22 pm

    I’m in the same boat in that I have an MVC site where I want to run a second copy of it for a different client with some presentation differences.
    I like your solution, I was just wondering if you could share some details on what your project looks like now.
    Where do you store the different presentation files? Or do you have giant if statements in each file to show different html based on the current GlobalVars.CurrentAppSettings.HostName?

    Reply

    • I usually add a property to my CurrentAppSettings class that is an app reference — usually the name of a directory in a /Template/ directory on the server. That directory will then be called in subsequent calls for master page files, etc.., usually in the page_init. An excellent question which I should probably address in an edit to the article eh?

      Reply

  2. Posted by David on June 2, 2010 at 12:25 am

    Great post but I think I’m missing something. Any chance you have a sample project you could upload?

    Reply

  3. Posted by Ramesh on August 25, 2010 at 3:36 pm

    Can you pls. provide me a working sample for this? I would greatly appreciate that.

    Reply

  4. Posted by GYNCSCEROUNSE on February 28, 2011 at 4:14 am

    чому б і ні:)

    Reply

  5. Posted by Suk on May 30, 2011 at 8:58 pm

    I have a different type of need. I have one corporate ASP.net web application which is hosted on 2 different physical servers (load balanced by a load balancer) at this point. But the hardware config of every server is really beefy (4 quad core processors, 32 GB RAM, etc). Even though IIS is set to take advantage of full hardware available, my thought is that having multiple sites (multiple copies of the same application) might be able to maximize the hardware usage which could in-turn can host more user load in-directly. This might mean, every app instance will be running on its own port and having its own app pool and so on.
    What is your thought on this? We are using Win2003 server, IIS6, Sql Server 2005.

    Thank you!!

    Reply

  6. Trying to follow your example here, and in the ConfigureApp httpmodule, I’m getting “The name ‘CurrentAppSettings’ does not exist in the current context” errors when I try to compile. Is there an additional line I should be putting somewhere to reference this?

    Reply

  7. Nice article. I use a slightly different approach but with some similarities. We’re hosting 20+ sites and each site has 2 to 4 domain names appointed for it. All requests go to one web application. In my database i can create domains for the site where i want to listen it to. Now here comes the trick (really easy!) . When a user enters the site, i will use Request.Url to get the domain he used, and from there i do a lookup to see which site needs to be loaded. Works like a charm, very scalable and easy to implement.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: