Pages

Sunday, February 22, 2009

Virtual site handler in .net

I cam across a situation the other day where I found that my ASP.NET was limited to 1 website and I wanted to be able to host more than one website. Each site wasn't going to be that big or clever so I decided to see if I could come up with an IHttpModule which would be able to make it serve different pages depending on which domain was requested.

The core of the solution I came up with is the VirtualSiteManager class which given a request will return a re-written path which an IHttpModule can rewrite to.
public class VirtualSiteManager
{
public VirtualSiteManager(string defaultSiteName)
{
_defaultSiteName = defaultSiteName;
Sites = new Dictionary<string, VirtualSite>();
}

private string _defaultSiteName;

private static VirtualSiteManager _instance;
private static Type _lock = typeof(VirtualSiteManager);

public static VirtualSiteManager Instance
{
get
{
lock (_lock)
{
if (_instance == null)
{
_instance = (VirtualSiteManager) WebConfigurationManager.GetSection("virtualSite");
}
}

return _instance;
}
}

public VirtualSite DefaultSite
{
get
{
VirtualSite defaultSite = null;

if (Sites.ContainsKey(_defaultSiteName))
{
defaultSite = Sites[_defaultSiteName];
}

return defaultSite;
}
}

public Dictionary<string, VirtualSite> Sites
{
get;
private set;
}

public void AddSite(VirtualSite site)
{
if (!Sites.ContainsKey(site.Host))
{
Sites.Add(site.Host, site);
}
else
{
Sites[site.Host] = site;
}
}

public void AddSite(string name, string host, string virtualPath)
{
AddSite(new VirtualSite(name, host, virtualPath));
}

public string RewritePath(HttpContext context)
{
string host = context.Request.Url.Host;
VirtualSite handlingSite = (Sites.ContainsKey(host)) ? Sites[host]: DefaultSite;
string currentPath = context.Request.CurrentExecutionFilePath;

context.Response.AddHeader("X-Debug-currentPath", currentPath);

string returnPath = handlingSite.VirtualPath + (currentPath.StartsWith("/") ? currentPath.Substring(1) : currentPath);

if (returnPath.EndsWith("/"))
{
returnPath += "index.aspx";
}

return returnPath;
}
}

I store the domains and what their virtual path's are in the Web.Config like this:
<configSections>
<section name="virtualSite" type="RomfordEvan.Web.Components.VirtualSite.VirtualSiteHandler, RomfordEvan.Web.Components"/>
</configSections>
<virtualSite default="default">
<site name="default" host="www.domain1.com" virtualPath="/"/>
<site name="localhost" host="localhost" virtualPath="/"/>
<site name="domain2" host="www.domain2.com" virtualPath="/domain2/"/>
</virtualSite>

The IConfigurationSectionHandler to read the Web.Config and create the VirtualSiteManager looks like this:
public class VirtualSiteHandler : IConfigurationSectionHandler
{
#region IConfigurationSectionHandler Members

public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
XmlAttribute def = section.Attributes["default"];

string defaultSiteName = string.Empty;

if (def != null && def.InnerText.Length > 0)
{
defaultSiteName = def.InnerText;
}

VirtualSiteManager manager = new VirtualSiteManager(defaultSiteName);

XmlNodeList siteNodes = section.SelectNodes(".//site");

foreach (XmlNode siteNode in siteNodes)
{
manager.AddSite(siteNode.Attributes["name"].InnerText, siteNode.Attributes["host"].InnerText, siteNode.Attributes["virtualPath"].InnerText);
}

return manager;
}

#endregion
}

This works fine with the following caveats:
  • Under IIS6 it requires wildcard mapping to the ASP.NET ISAPI filter.
  • You will need to add HttpHandlers for all the file types which your site will serve (see below)

<add verb="GET,POST" path="*/" validate="true" type="System.Web.UI.PageHandlerFactory" />
<add verb="GET" path="*.html" validate="true" type="System.Web.StaticFileHandler" />
<add verb="GET" path="*.js" validate="true" type="System.Web.StaticFileHandler" />
<add verb="GET" path="*.gif" validate="true" type="System.Web.StaticFileHandler" />
<add verb="GET" path="*.jpg" validate="true" type="System.Web.StaticFileHandler" />
<add verb="GET" path="*.png" validate="true" type="System.Web.StaticFileHandler" />
<add verb="GET" path="*.css" validate="true" type="System.Web.StaticFileHandler" />

Wednesday, February 18, 2009

Generic, type safe Configuration Manager

I like to store lots of config data in the Web.Config and I get fed up with having to cast the ConfigSection to the concrete class:
MyConfigSectionHandler handler =
(MyConfigSectionHandler)
WebConfigurationManager.GetSection(sectionName);

So I've written a helper class where now you can use:
MyConfigSectionHandler config =
ConfigManager.Instance.GetSection<MyConfigSectionHandler>
(sectionName);

Here's the code - (I've implemented it as a Singleton for a reason I don't remember now):
public class ConfigManager
{
private static ConfigManager _instance;
private static object _lock = typeof(ConfigManager);

private ConfigManager()
{

}

/// <summary>
/// Gets a singleton instance.
/// </summary>
/// <value>The instance.</value>
public static ConfigManager Instance
{
get
{
lock (_lock)
{
if (_instance == null)
{
_instance = new ConfigManager();
}

return _instance;
}
}
}

/// <summary>
/// Gets the section.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sectionName">Name of the section.</param>
/// <returns></returns>
public T GetSection<T>(string sectionName)
where T : class
{
return WebConfigurationManager.GetSection(sectionName) as T;
}
}