I've tried rewriting urls a couple of times and I've settled on using an IHttpHandlerFactory. It isn't perfect, but it works.
The question came up again in a forum, so I'm posting my sample with an implementation based on the question.
There are 3 classes in the example:
The code assumes that you will be running in an application called ForumHelpExamples. It takes a request fo localhost/ForumHelpExamples/en-ca/Default.aspx extracts the en-ca to set the thread culture and executes localhost/ForumHelpExamples/Default.aspx. The CultureInUrlHandler could easily be changed to add a querystring parameter too.
using System.Web;
using System.Web.UI;
namespace ForumExamplesLibrary
{
abstract public class RemapHandler : IHttpHandlerFactory
{
protected RemapHandler()
: base()
{
}
IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
RemapInfo target = GetRemapInfo(context, requestType, url, pathTranslated);
string filename = context.Server.MapPath(target.VirtualPath);
//Rewrite path is called to update the query string values
context.RewritePath(url, url, target.QueryString);
IHttpHandler appHandler = PageParser.GetCompiledPageInstance(target.VirtualPath
, filename
, context);
return appHandler;
}
void IHttpHandlerFactory.ReleaseHandler(IHttpHandler handler)
{
}
protected abstract RemapInfo GetRemapInfo(HttpContext context, string requestType, string url, string pathTranslated);
}
}
namespace ForumExamplesLibrary
{
public class RemapInfo
{
private string _virtualPath;
private string _queryString;
public RemapInfo( string virtualPath, string queryString)
{
this._virtualPath = virtualPath;
this._queryString = queryString;
}
public string VirtualPath
{
get { return _virtualPath; }
}
public string QueryString
{
get { return _queryString; }
}
}
}
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Globalization;
using System.Threading;
namespace ForumExamplesLibrary
{
public class CultureInUrlHandler : RemapHandler
{
protected override RemapInfo GetRemapInfo(HttpContext context
, string requestType
, string url
, string pathTranslated)
{
string originalPath = HttpContext.Current.Request.Path;
string stemPath = "ForumHelpExamples/";
string newPath = originalPath.Substring(originalPath.IndexOf(stemPath) + stemPath.Length);
string[] segments = newPath.Split('/');
string queryString = HttpContext.Current.Request.ServerVariables["QUERY_STRING"];
try
{
string languagePart = segments[0];
//It will be inefficient to throw exceptions when the lang is invalid
CultureInfo ci = new CultureInfo(languagePart);
System.Threading.Thread.CurrentThread.CurrentCulture = ci;
return new RemapInfo("/" + stemPath + string.Join("/", segments, 1, segments.Length - 1)
, queryString);
}
catch (NullReferenceException)
{
}
return new RemapInfo(originalPath, queryString);
}
}
}
And in the web config...
<system.web>
<httpHandlers>
<add verb="*" path="*.aspx" type="ForumExamplesLibrary.CultureInUrlHandler, ForumExamplesLibrary"/>
</httpHandlers>
Edit:I was posting this in a commment, figured it would be good to add it here.
You can have query string variables and they work as normal.
The example just looks for the language part and removes it to determinethe real page, it passes the query string along as-is
The only time you should have any issue with the qs is if you want to use it to convert parts of the path into a query string.
Its better to keep a custom dictionary (eg "PathParamers") in the httpcontext.current items to hold them. (If you convert the path directly to qs arguments, so that site/color/red/All.aspx executes the pages site/All.aspx?color=red, then when you have postback, you postback to site/color/red/All.aspx?color=red)