How can I have lowercase routes in ASP.NET MVC?

asp.net MvcUrlRoutesCaseCanonical Link

asp.net Mvc Problem Overview


How can I have lowercase, plus underscore if possible, routes in ASP.NET MVC? So that I would have /dinners/details/2 call DinnersController.Details(2) and, if possible, /dinners/more_details/2 call DinnersController.MoreDetails(2)?

All this while still using patterns like {controller}/{action}/{id}.

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

With System.Web.Routing 4.5 you may implement this straightforward by setting LowercaseUrls property of RouteCollection:

public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.LowercaseUrls = true;

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }

Also assuming you are doing this for SEO reasons you want to redirect incoming urls to lowercase (as said in many of the links off this article).

protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //You don't want to redirect on posts, or images/css/js
        bool isGet = HttpContext.Current.Request.RequestType.ToLowerInvariant().Contains("get");
        if (isGet && !HttpContext.Current.Request.Url.AbsolutePath.Contains("."))
        {
            //You don't want to modify URL encoded chars (ex.: %C3%8D that's code to Í accented I) to lowercase, than you need do decode the URL
            string urlLowercase = Request.Url.Scheme + "://" + HttpUtility.UrlDecode(HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);
            //You want to consider accented chars in uppercase check
            if (Regex.IsMatch(urlLowercase, @"[A-Z]") || Regex.IsMatch(urlLowercase, @"[ÀÈÌÒÙÁÉÍÓÚÂÊÎÔÛÃÕÄËÏÖÜÝÑ]"))
            {
                //You don't want to change casing on query strings
                urlLowercase = urlLowercase.ToLower() + HttpContext.Current.Request.Url.Query;

                Response.Clear();
                Response.Status = "301 Moved Permanently";
                Response.AddHeader("Location", urlLowercase);
                Response.End();
            }
        }
    }

Solution 2 - asp.net Mvc

These two tutorials helped when I wanted to do the same thing and work well:

http://www.coderjournal.com/2008/03/force-mvc-route-url-lowercase/ http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/

EDIT: For projects with areas, you need to modify the GetVirtualPath() method:

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
  var lowerCaseValues = new RouteValueDictionary();

  foreach (var v in values)
  {
    switch (v.Key.ToUpperInvariant())
    {
      case "ACTION":
      case "AREA":
      case "CONTROLLER":
        lowerCaseValues.Add(v.Key, ((string)v.Value).ToLowerInvariant());
        break;
      default:
        lowerCaseValues.Add(v.Key.ToLowerInvariant(), v.Value);
        break;
    }
  }
  return base.GetVirtualPath(requestContext, lowerCaseValues);
}

Solution 3 - asp.net Mvc

If you happened to use ASP.NET Core, you probably should have a look at this:

> Add the following line to the ConfigureServices method of the Startup class. > > services.AddRouting(options => options.LowercaseUrls = true);

Solution 4 - asp.net Mvc

If you are using the UrlHelper to generate the link, you can simply specify the name of the action and controller as lowercase:

itemDelete.NavigateUrl = Url.Action("delete", "photos", new { key = item.Key });

Results in: /media/photos/delete/64 (even though my controller and action are pascal case).

Solution 5 - asp.net Mvc

I found this at Nick Berardi’s Coder Journal, but it did not have information on how to implement the LowercaseRoute class. Hence reposting here with additional information.

First extend the Route class to LowercaseRoute

public class LowercaseRoute : 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;
    }
}

Then modify the RegisterRoutes method of Global.asax.cs

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
    routes.Add(new LowercaseRoute("{controller}/{action}/{id}", 
        new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }), 
        new MvcRouteHandler()));
    
    //routes.MapRoute(
    //    "Default",                                              // Route name
    //    "{controller}/{action}/{id}",                           // URL with parameters
    //    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    //);
}

I would however like to know a way to use routes.MapRoute...

Solution 6 - asp.net Mvc

You can continue use the MapRoute syntax by adding this class as an extension to RouteCollection:

public static class RouteCollectionExtension
{
    public static Route MapRouteLowerCase(this RouteCollection routes, string name, string url, object defaults)
    {
        return routes.MapRouteLowerCase(name, url, defaults, null);
    }

    public static Route MapRouteLowerCase(this RouteCollection routes, string name, string url, object defaults, object constraints)
    {
        Route route = new LowercaseRoute(url, new MvcRouteHandler())
        {
            Defaults = new RouteValueDictionary(defaults),
            Constraints = new RouteValueDictionary(constraints)
        };

        routes.Add(name, route);

        return route;
    }
}

Now you can use in your application's startup "MapRouteLowerCase" instead of "MapRoute":

    public void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Url shortcuts
        routes.MapRouteLowerCase("Home", "", new { controller = "Home", action = "Index" });
        routes.MapRouteLowerCase("Login", "login", new { controller = "Account", action = "Login" });
        routes.MapRouteLowerCase("Logout", "logout", new { controller = "Account", action = "Logout" });

        routes.MapRouteLowerCase(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
        );
    }

Solution 7 - asp.net Mvc

This actually has two answers:

  1. You can already do this: the route engine does case-insensitive comparison. If you type a lower-case route, it will be routed to the appropriate controller and action.
  2. If you are using controls that generate route links (ActionLink, RouteLink, etc.) they will produce mixed-case links unless you override this default behavior.

You're on your own for the underscores, though...

Solution 8 - asp.net Mvc

Could you use the ActionName attribute?

 [ActionName("more_details")]
 public ActionResult MoreDetails(int? page)
 {
  
 }

I don't think case matters. More_Details, more_DETAILS, mOrE_DeTaILs in the URL all take you to the same Controller Action.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpupenoView Question on Stackoverflow
Solution 1 - asp.net MvcITmezeView Answer on Stackoverflow
Solution 2 - asp.net Mvcderek lawlessView Answer on Stackoverflow
Solution 3 - asp.net MvcEbrahim ByagowiView Answer on Stackoverflow
Solution 4 - asp.net MvcMattView Answer on Stackoverflow
Solution 5 - asp.net MvcJohn OxleyView Answer on Stackoverflow
Solution 6 - asp.net MvcMarkus WoltersView Answer on Stackoverflow
Solution 7 - asp.net MvcGalacticCowboyView Answer on Stackoverflow
Solution 8 - asp.net MvcGuyIncognitoView Answer on Stackoverflow