I think I have found the solution to this. Rather than try and achieve this functionality by managing routes, it is better to use a custom HttpModule. Here is the code.
HttpModule
public class WebFormsHttpHandler
: IHttpModule
{
// All Web Form (*.aspx) pages will exist in this folder hierarchy
public const string WebFormsRootFolder = "~/WebPages";
#region Constructor
public WebFormsHttpHandler()
{
}
#endregion Constructor
#region IHttpModule Methods
public void Dispose()
{
/* Nothing to dispose */
}
#endregion Constructor
// Wire up the interesting events
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
context.AuthorizeRequest += context_AuthorizeRequest;
context.PreRequestHandlerExecute += context_PreRequestHandlerExecute;
}
#region BeginRequest
// Capture the original request path so we can restore it to the browser
// in the PreRequestHandlerExecute event if required
void context_BeginRequest(object sender, EventArgs e)
{
var context = ((HttpApplication)sender).Context;
var path = context.Request.Url.AbsolutePath;
context.Items["originalUrl"] = path;
}
#endregion BeginRequest
#region AuthorizeRequest
void context_AuthorizeRequest(object sender, EventArgs e)
{
var context = ((HttpApplication)sender).Context;
string requestPath = context.Request.Url.AbsolutePath ?? "";
string filePath = context.Server.MapPath(requestPath);
// Check for physical existence
if (File.Exists(filePath))
{
return;
}
var virtualPath = WebFormsRootFolder;
if (!requestPath.StartsWith("/")) { virtualPath += "/"; }
virtualPath += requestPath;
filePath = context.Server.MapPath(virtualPath);
// Is the virtualPath a directory?
if (Directory.Exists(filePath))
{
if (!virtualPath.EndsWith("/")) { virtualPath += "/"; }
virtualPath += "Default.aspx";
}
else if (!virtualPath.EndsWith(".aspx")) {
virtualPath += ".aspx";
}
filePath = context.Server.MapPath(virtualPath);
if (!File.Exists(filePath))
{
return;
}
/*
** Should I set a context.Response.StatusCode here?
*/
context.RewritePath(virtualPath);
}
#endregion AuthorizeRequest
#region PreRequestHandlerExecute
// Restore the original request path on the browser - the user doesn't
// need to know what's happened
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
var context = ((HttpApplication)sender).Context;
string originalUrl = context.Items["originalUrl"] as string;
if (originalUrl != null)
{
context.RewritePath(originalUrl);
}
}
#endregion PreRequestHandlerExecute
}
Register HttpModule in Web.config
<system.webServer>
<modules>
<add name="WebFormsHttpHandler" type="MultiApp.Web.WebFormsHttpHandler" />
</modules>
</system.webServer>
Sample/Test Route Configuration
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// WebForm Route Definitions
routes.MapPageRoute(
"product-details",
"product/{productid}",
"~/WebPages/SubFolder/Product.aspx"
);
// MVC Route Definitions
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
},
namespaces: new[] {
"MultiApp.Web.Controllers"
}
);
}
I have tested this with a wide format of URL constructs and it appears to work as required. If anyone has any suggestions for improvement, please let me know.
I would like to thank Rahul Rajat Singh's project Implementing HTTPHandler and HTTPModule in ASP.NET which was a big help.