Published: 06 Jan 2012
By: Bipin Joshi
Download Sample Code

Develop a solution that supports multiple websites in a single hosting space and database.

Contents [hide]

Introduction

Many small companies having limited budget for website hosting prefer to host multiple websites in a single shared hosting space using domain pointers. The shared hosting plans offered by many web hosts use some third party URL rewriting software or IIS URL rewriting module to segregate one website from the other. Often these techniques require that you keep files of individual website into a folder of its own. This approach goes well with websites using totally different codebase. However, if the websites under consideration are using the same codebase this approach may not be suitable. In such cases you need to take control in your own hands by programmatically handling multiple domains and sub domains. This article illustrates one possible approach to do just that.

Defining the problem

Consider that you have two domain names with you, say domain1.com and domain2.com. Now, you wish to host these two domains in a single hosting space. As mentioned earlier if you use some software for URL rewriting, you will be expected to segregate multiple websites into a folder of their own. Have a look at Figure 1.

Figure 1: Sample folder structure of a website

Sample folder structure of a website

The folder structure as shown in Figure 1 belongs to two websites -domain1.com and domain2.com. You will be repeating similar folder structure for all the websites you wish to host under the same hosting account. The "separate folder for each website" approach works well when the websites under consideration are totally independent of each other and there are very few things common between them. In such cases, they use different codebase (in terms of web forms, user controls, classes, components and such items) and database tables.

Now, consider a case that you wish to build two websites using a common codebase or framework. In this case, the web forms, user controls, classes and components will be the same for all the websites built using the same codebase. What changes is the content or data of the websites and possibly the look and feel of the websites. Suppose that you wish to develop two shopping websites -one selling electronic gadgets and the other selling books. Obviously both of the websites will have many features common and you can develop a framework that can be used by both of them. These websites will look different and work with their own data but at code level they share the same functionality (membership, product catalog, shopping cart etc.). In such cases repeating the same codebase in many folders is not a good idea. Tomorrow if the codebase changes you will need to synchronize all the folders with the new codebase. Some other examples where the same code base can be used for multiple websites are Content Management Systems (CMS), portals, blog engines, community websites and discussion forums.

There is one more point that needs some consideration while using "separate folder for each website" approach. Not all web hosts use the same software for URL rewriting. When you shift from one web host to another, this can pose a problem because you may need to adjust your website structure or configuration to meet requirements of the software used by your new web host.

To conclude, there are times when you need to take control of dealing with multiple websites in your own hands by programmatically handling multiple domains and sub domains. In the remainder of this article you will learn one approach to do the same.

The Solution

As a solution to the problem at hand, you will develop a database driven scheme that maintains "metadata" of multiple websites. The metadata includes website ID, title, host, Master Page, default page and so on. Notice that here "host" refers to www.domain1.com, domain2.com etc. In all the tables that store the website data such as menus, users, pages and other application specific data, you will have a column that stores website ID. Thus all the data belonging to a website can be retrieved using the ID of that website. The metadata to be stored will be clear when you create required database tables in the later section.

Once your website metadata is in place, you are ready to handle incoming requests. You will essentially check the incoming request URL to determine the host (domain1.com or domain2.com) and then fetch website metadata for that host. You then apply master page, theme and other settings on the fly. From the end user's perspective, they will see two different websites, each with its own content, layout and look and feel.

For our solution to work, you must ask your web hosting provider to point additional domain pointers to your primary domain. For example, if you have hosted domain1.com with your web host and also wish to run domain2.com from the same hosting space, you will need to ask them to point domain2.com to the same space as domain1.com. If you wish to host multiple sub-domains -e.g. blog.domain1.com, photos.domain1.com -again you will need to get in touch with your hosts and ask them to do the required configuration. Note that some web hosting companies allow domain pointers free of charge whereas some charge a small fee. Some allow you to do it on your own via their web based control panel whereas some expect you to seek help from their support personnel. You need to figure out what their offerings are and add the required domain pointers accordingly.

Figure 2: Multiple root domain pointers

Multiple root domain pointers

Now, let's begin developing a sample that illustrates what we discussed so far.

Creating SQL Server database

You will use a SQL Server database to store website metadata. So, begin by creating a new ASP.NET website and add App_Data folder to it. Then add a new SQL Server Express database to App_Data folder and create two tables - Websites and Pages -as shown in Figure 3.

Figure 3: Tables storing website and page information

Tables storing website and page information

As you can see the WebSites table consists of six columns viz. WebSiteId, Title, Host, Theme, MasterPage and DefaultPage. WebSiteId column represents the primary key and is a GUID. Title column is a descriptive name for the website and you can also use it for page title if you so wish.

The Theme, MasterPage and DefaultPage columns indicate theme name, master page and default page of the website respectively. MasterPage and DefaultPage values are URLs to .master and .ascx files (e.g. ~/MasterPages/Master1.master, ~/UserControls/Default1.ascx) respectively. The DefaultPage column needs a bit of explanation. As a part of the framework you will have just one default page (Default.aspx) but at runtime based on the target website you will need to render different content in it. You achieve this by putting the markup and code, that otherwise would have gone in a default page, into a user control. Based on the MasterPage URL and DefaultPage URL, the appropriate master page will be applied and the appropriate user control will be loaded at runtime.

The Host column is important as it holds the domain or sub-domain name (e.g. www.domain1.com, domain1.com, blog.domain1.com). For every unique website host you wish to support, you need to have an entry in the WebSites table.

Pages table stores web pages belonging to the websites and has four columns viz. PageId, WebSiteId, Title and PageContent. PageId uniquely identifies a page and WebSiteId indicates the website to which the web page belongs. This way you can restrict pages only to a particular website. Title is a friendly name for a page and PageContent holds the actual HTML markup that makes the page. In a more real world website you will have additional columns to Pages table for tracking user activity and statistics. You will also have many more tables that store data specific to the framework you are building and each table will have WebSiteId column that associates its data to a particular website. For our purpose it is suffice to have Pages table.

Creating Entity Framework Models

In order to access data in the WebSites and Pages tables, you will create Entity Framework Data Model. Add a new ADO.NET Entity Data Model in App_Code folder (Figure 4).

Figure 4: Adding a new ADO.NET Entity Data Model

Adding a new ADO.NET Entity Data Model

Then drag and drop WebSites and Pages tables from Server Explorer onto the Entity Data Model designer so as to create models as shown in Figure 5.

Figure 5: Entity Data Models for Website and Pages tables

Entity Data Models for Website and Pages tables

Creating a helper class

Next, you will create a helper class -WebSiteHelper -that exposes four important methods. You will be using these methods throughout the website in various web forms. The methods of WebSiteHelper class are listed below:

  • GetCurrentWebSite()
  • GetTheme()
  • GetMasterPage()
  • GetDefaultPage()

The GetCurrentWebSite() method returns an instance of WebSite model class that represents metadata of the current website. The GetTheme(), GetMasterPage() and GetDefaultPage() methods return theme name, master page URL and default page URL respectively. Skeleton of WebSiteHelper class is shown below:

As you can see all the methods of WebSiteHelper class are static so that they can be called without instantiating it. These methods are discussed in detail in the next sections.

Detecting and fetching metadata for current website

The GetCurrentWebSite() method is pivotal to the functioning of our solution since it fetches metadata from the WebSites table by inspecting host from the current URL. The complete code of GetCurrentWebSite() method is given below:

As you can see, the method begins by declaring a dictionary for storing WebSite model instances. When you will be testing our solution on a local machine, you will not be accessing a website with domain as such (your URL will be http://localhost/... and not http://www.domain1.com/...). To simplify the testing process, you will have an <appSettings> section with a key -WebSiteId. If this key has some value, you return metadata for that website without inspecting the host. The following fragment shows how the <appSettings> section looks like:

If WebSiteId key is kept to an empty string, you fetch all the WebSite model instances and store them in websites dictionary. The host acts as a key and WebSite model instance as the value. For performance reasons, you put the dictionary in ASP.NET Cache object. Notice that the cache duration is 1 day and you should change it as per your requirements.

Next, the code examines the host for the current URL. This is done using Request.Url property and then examining the Host property. The Request.Url property returns an instance of System.Uri class and represents a uniform resource identifier (URI). It also allows an easy access to the parts of the URI such as Host. Depending on the host, a WebSite instance is returned from the cached dictionary.

If the <appSettings> section mentions some WebSiteId, a WebSite model instance is directly fetched from the database and returned to the caller.

Deciding Theme, Master Page and Default Page at runtime

The remaining three methods -GetTheme(), GetMasterPage() and GetDefaultPage() -make use of GetCurrentWebSite() method and return theme name, master page URL and default page URL respectively. These methods are shown next:

Creating Theme, Master Pages and Default Pages for testing

Now that you have WebSiteHelper class ready, let's create sample themes, master pages and default pages for testing purpose. Add two themes to your website and name them as Blue and Brown. Also add a couple of master pages and user controls. After adding all these items your website folder structure should resemble Figure 6.

Figure 6: Adding sample themes, master pages and default pages

Adding sample themes, master pages and default pages

We won't discuss themes and master pages in detail as they are quite simple in nature. You can download the code accompanying this article and copy them into your website for testing purpose.

Both the user controls representing default pages contain a GridView that lists all the pages belonging to the current website (Recollect that Pages table has WebSiteId column that indicates the website to which the page belongs). The following fragment shows the GridView markup:

The HyperLinkField points to ShowPage.aspx and passes PageId to it. This way ShowPage.aspx can display contents of the requested page. The code behind of the user control binds this GridView with data.

As you can see, the above code fetches data from Pages table by filtering it based on WebSiteId and binds it with the GridView.

What is more important is the default page (.aspx) of the website. This page shows how themes, master pages and default pages are applied on the fly from the database. Add a web form -Default.aspx -to the root folder of the website and add a PlaceHolder control on it. You will need to handle two events of the web form - Page_PreInit and Page_Load. You must set Theme and Master Page of the web forms in Page_PreInit event handler. Once the page is loaded you can load user control acting as the default page. The following code shows how this is done.

As you can see, Page_PreInit event handler does the job of setting Theme and MasterPageFile properties of the web form. It uses methods of WebSiteHelper class to retrieve theme and master page for the current host.

The Page_Load event handler retrieves the URL of default page user control and then loads it using LoadControl() method. The dynamically loaded user control is then added to the Controls collection of the PlaceHolder control. Thus Default.aspx will load a user control depending on the host and display different content when the default page is accessed.

Displaying website pages

ShowPage.aspx does the job of displaying a particular page. To create ShowPage.aspx, add Pages folder to the website and add ShowPage.aspx web form to it. Place a Literal control on the web form. This Literal control is used to render the page contents.

In the Page_PreInit event handler of ShowPage.aspx, set Theme and MasterPage as you did for Default.aspx.

In the Page_Load event handler, add the following code:

As you can see, the code fetches a single Page instance based on a PageId. The "if" condition checks whether the requested page belongs to current website. If so, its PageContent markup is assigned to the Literal control, otherwise an error message is displayed.

Testing our solution

In order to test our solution, you need to ensure that required domain pointers are properly set. You also need to add some test data in the WebSites and Pages table.

Add at least two records in the WebSites table as shown below:

Table 1: Configuration

Item

Website 1

Website 2

Title

This is WebSite 1

This is WebSite 2

Host

www.domain1.com

www.domain2.com

Theme

Brown

Blue

MasterPage

~/MasterPages/MasterPage1.master

~/MasterPages/MasterPage2.master

DefaultPage

~/DefaultPages/DefaultPage1.ascx

~/DefaultPages/DefaultPage1.ascx

< /td>

Make sure to change host column values to reflect your domain names.

Now run Default.aspx using domain1 as host (i.e. www.domain1.com/default.aspx) and you should see default page as shown in Figure 7.

Figure 7: Sample run with domain1.com

Sample run with domain1.com

Now access Default.aspx using domain2 as host (i.e. www.domain2.com/default.aspx). This time you should see default page as shown in Figure 8.

Figure 8: Sample run with domain2.com

Sample run with domain2.com

See how the same web form changes its display and contents depending on the host. Similarly, test the functionality of ShowPage.aspx by clicking on the respective page links from the GridView.

Summary

At times you need to run multiple websites from the same hosting space. If the websites are using the same codebase, you can device a database driven approach to handle multiple domains and sub-domains. This article illustrates one such approach. The approach discussed in this article doesn't need any external URL rewriting technique. Once you develop such a database driven framework you can support as many websites as you want by pointing additional root domain pointers. In addition to being cost effective it also simplifies future upgrades to the framework. Since all the websites are using the same base framework, upgrading the framework will affect all the websites. You can extend the approach discussed in the article while developing content management systems (CMS), portals, blog engines, shopping websites, discussion boards or any such types of framework supporting multiple websites.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Bipin Joshi

Bipin Joshi is a blogger, author and a Kundalini Yogi who writes about apparently unrelated topics - Yoga & Technology! A former Software Consultant and trainer by profession, Bipin is programming since 1995 and is working with .NET framework ever since its inception. He is an internation...

This author has published 7 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


jQuery Mobile ListView
In this article, we're going to look at what JQuery Mobile uses to represent lists, and how capable ...
JQuery Mobile Widgets Overview
An overview of widgets in jQuery Mobile.
jQuery Mobile Pages
Brian Mains explains how to create pages with the jQuery Mobile framework.
Code First Approach using Entity Framework 4.1, Inversion of Control, Unity Framework, Repository and Unit of Work Patterns, and MVC3 Razor View
A detailed introduction about the code first approach using Entity Framework 4.1, Inversion of Contr...
Exception Handling and .Net (A practical approach)
Error Handling has always been crucial for an application in a number of ways. It may affect the exe...

You might also be interested in the following related blog posts


Refactoring the GoF patterns (Singleton in .NET continued read more
Singleton in .NET read more
Managing Windows Workflow Events on a Web Server read more
Bounded blocking queues read more
Top
 
 
 

Discussion


Subject Author Date
placeholder Nice article with a missed point Morteza Sahragard 1/9/2012 9:29 AM

Please login to rate or to leave a comment.

Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.