When I wrote a simple CMS, I had a difficult time using the PageHandlerFactory to get it to do what I wanted. In the end I switched to a IHttpModule.
My module would first check to see if there was an .aspx file in the requested path. I'd only do that if the page has user controls on it or didn't fit into the CMS for some reason. So if the file existed, it would return out of the module. After that it would look at the requested path and condense it into a "navigation tag." Thus ~/aboutus/default.aspx would become page.aspx?nt=aboutusdefault. page.aspx would load the proper content form the CMS. Of course, the redirect occurs server-side so the users/spiders never know anything different happened.
using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
namespace MyCMS.Handlers {
/// <summary>
/// Checks to see if we should display a virutal page to replace the current request.
/// Code adapted from:
/// Rewrite.NET -- A URL Rewriting Engine for .NET
/// By Robert Chartier
/// http://www.15seconds.com/issue/030522.htm
/// </summary>
public class VirtualPageModule : IHttpModule {
/// <summary>
/// Init is required from the IHttpModule interface
/// </summary>
/// <param name="Appl"></param>
public void Init(System.Web.HttpApplication Appl) {
// make sure to wire up to BeginRequest
Appl.BeginRequest += new System.EventHandler(Rewrite_BeginRequest);
}
/// <summary>
/// Dispose is required from the IHttpModule interface
/// </summary>
public void Dispose() {
// make sure you clean up after yourself
}
/// <summary>
/// To handle the starting of the incoming request
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void Rewrite_BeginRequest(object sender, System.EventArgs args) {
// Cast the sender to an HttpApplication object
HttpApplication httpApp = (HttpApplication)sender;
// See if the requested file already exists
if (System.IO.File.Exists(httpApp.Request.PhysicalPath)) {
// Do nothing, process the request as usual
return;
}
string requestPath = VirtualPathUtility.ToAppRelative(httpApp.Request.Path);
// Organic navigation tag (~/aboutus/default.aspx = nt "aboutusdefault")
Regex regex = new Regex("[~/\\!@#$%^&*()+=-]");
requestPath = regex.Replace(requestPath, string.Empty).Replace(".aspx", string.Empty);
string pageName = "~/page.aspx";
string destinationUrl = VirtualPathUtility.ToAbsolute(pageName) + "?nt=" + requestPath;
SendToNewUrl(destinationUrl, httpApp);
}
public void SendToNewUrl(string url, HttpApplication httpApp) {
applyTrailingSlashHack(httpApp);
httpApp.Context.RewritePath(
url,
false // RebaseClientPath must be false for ~/ to continue working in subdirectories.
);
}
/// <summary>
/// Applies the trailing slash hack. To circumvent an ASP.NET bug related to dynamically
/// generated virtual directories ending in a trailing slash (/).
/// As described by BuddyDvd:
/// http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=105061
/// </summary>
/// <param name="httpApp">The HttpApplication.</param>
/// <remarks>
/// Execute this function before calling RewritePath.
/// </remarks>
private void applyTrailingSlashHack(HttpApplication httpApp) {
if (httpApp.Request.Url.AbsoluteUri.EndsWith("/") && !httpApp.Request.Url.AbsolutePath.Equals("/")) {
Type requestType = httpApp.Context.Request.GetType();
object clientFilePath = requestType.InvokeMember("ClientFilePath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty, null, httpApp.Context.Request, null);
string virtualPathString = (string)clientFilePath.GetType().InvokeMember("_virtualPath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField, null, clientFilePath, null);
clientFilePath.GetType().InvokeMember("_virtualPath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField, null, clientFilePath, new object[] { virtualPathString });
requestType.InvokeMember("_clientFilePath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField, null, HttpContext.Current.Request, new object[] { clientFilePath });
object clientBaseDir = requestType.InvokeMember("ClientBaseDir", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty, null, httpApp.Context.Request, null);
clientBaseDir.GetType().InvokeMember("_virtualPath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField, null, clientBaseDir, new object[] { virtualPathString });
requestType.InvokeMember("_clientBaseDir", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField, null, HttpContext.Current.Request, new object[] { clientBaseDir });
}
}
}
}