I am trying to ensure that all URLs used to access my ASP.NET MVC site are lower case. In the event that an upper case letter is in the URL, I'm changing the status code to 301 and changing the location to the lowercase version of the URL with this code:
protected void Application_BeginRequest(object sender, EventArgs e)
{
var url = Request.Url.ToString();
if (Regex.IsMatch(url, @"[A-Z]"))
{
Response.Clear();
Response.Status = "301 Moved Permanently";
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.AddHeader("Location", url.ToLower());
Response.End();
}
}
However, recently a co-worker was trying to POST a form to a URL with a capital letter (forgetting about the redirect), but the action (marked with an HttpPost attribute) was not being hit. Looking at the requests in Firebug showed the original POST, but then it returned 301 and issued a GET to the lower case version.
I'm guessing the best solution is to just make sure all POSTs are to the lowercase version of the URL, but I came here to see if there is another way to handle this issue
Here's a version based on scottm's solution that works with .NET 4.0
protected void Application_BeginRequest(object sender, EventArgs e) {
string url = Request.Url.ToString();
if (Request.HttpMethod == "GET" && Regex.Match(url, "[A-Z]").Success) {
Response.RedirectPermanent(url.ToLower(), true);
}
}
You can create an extension method for routing that will render all url's lowercase:
The Code:
public class LowercaseRoute : System.Web.Routing.Route
{
public LowercaseRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler) { }
public LowercaseRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler) { }
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData path = base.GetVirtualPath(requestContext, values);
if (path != null)
path.VirtualPath = path.VirtualPath.ToLowerInvariant();
return path;
}
}
public static class RouteCollectionExtensions
{
public static void MapRouteLowercase(this RouteCollection routes, string name, string url, object defaults)
{
routes.MapRouteLowercase(name, url, defaults, null);
}
public static void MapRouteLowercase(this RouteCollection routes, string name, string url, object defaults, object constraints)
{
Check.Argument.IsNotNull(routes, "routs");
Check.Argument.IsNotNull(url, "url");
var route = new LowercaseRoute(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};
if (String.IsNullOrEmpty(name))
routes.Add(route);
else
routes.Add(name, route);
}
}
Your Route:
routes.MapRouteLowercase(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Now when you use the ...Html.BeginForm()... it will render a lowercase URL for the action. This will also always render lowercase urls whenever you use routing to render a link, i.e. Url.Action(); <%:Html.RenderAction() %>
As Giuseppe pointed out, search engines don't index POST pages. I ended up with this:
protected void Application_BeginRequest(object sender, EventArgs e)
{
if(Request.HttpMethod == "POST") return;
//etc
}
By my understanding, there is no way to force a user to reissue a POST request. You can only redirect them, and doing that only specifies a URL (not POST data).
The bigger question to me is: why you are so adamant about not having uppercase URL's? HTTP specifies that URLS are case insensitive.
Expanding on @bingles great answer, perhaps you do not want to force GET data to lowercase? Then you would want to use the regular expression (?<=^[^?]*)[A-Z]
instead:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string url = Request.Url.ToString();
if (Request.HttpMethod == "GET" && Regex.Match(url, "(?<=^[^?]*)[A-Z]").Success)
{
Response.RedirectPermanent(url.ToLower(), true);
}
}
Regex taken from this other answer: regex to match all uppercase letters before the first question mark