How do I ensure lower case URLs on POST?

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

Answers


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
}

Need Your Help