155

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}.

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • I ended up writing all my routes manually anyway for various reasons and I think it's hard to avoid doing that with anything that's not just CRUD. So I just wrote them in lowercase. – Pablo Fernandez Aug 18 '09 at 12:04
  • Using **Web Forms**? Go here: https://msdn.microsoft.com/en-us/library/cc668177.aspx?f=255&MSPPError=-2147217396. (I am gradually converting my project from web forms to MVC and have both in the project) – Jess Mar 11 '16 at 14:16
  • im pretty sure you can do this as default –  Nov 11 '19 at 10:56
  • i dont think it matters if you type in the routes in lower or upper case. –  Nov 11 '19 at 10:56

8 Answers8

255

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();
            }
        }
    }
ITmeze
  • 1,902
  • 2
  • 21
  • 32
44

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);
}
dlras2
  • 8,416
  • 7
  • 51
  • 90
derek lawless
  • 2,544
  • 2
  • 16
  • 13
  • 1
    Actually the Wordpress link (@GoNeale's article) is superior. It provides extension methods for a friendlier registration, and includes handling to redirect incoming requests that aren't in lower case, so that you don't fragment your page ranking in search engines between multiple versions of the same page. – Drew Noakes Sep 07 '10 at 22:42
  • 9
    GONeale link has changed; URL is now http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/ – Daniel Liuzzi Jan 14 '11 at 03:45
  • Does this still apply to MVC3? – Phill Jul 25 '11 at 11:13
  • @Phill Yes the tutorials are still valid. – derek lawless Aug 09 '11 at 16:13
  • 4
    @Derek - Nope, the tutorials break down when using Area's. After 3 days of trying EVERYTHING... I found a better solution, theres a library called Attribute Routing. Solves the problem and makes life a lot easier. http://www.philliphaydon.com/2011/08/mvc-routing-with-attributes-makes-routing-awesome/ – Phill Aug 09 '11 at 22:32
  • @Phill I took another look at where I'm using it and sure enough I made a small modification to get it to work with areas in the GetVirtualPath() method. – derek lawless Sep 06 '11 at 08:13
  • 1
    This solution doesnot work for me for areas it still works fine for regular case. All my urls in the area section are still not lower case – MoXplod Sep 11 '11 at 00:52
  • sorry, but how and where do you use the Route subclass? Can you please edit the answer to show where to inject this in the asp.net system? – Ivan G. Mar 12 '12 at 18:02
  • it has error in some cases. if you add some Object to RouteValue, it cann't cast to string, and it throw exception. – Rasoul Zabihi Oct 03 '12 at 15:04
  • 6
    For mvc 4 there is a better solution using property routes.LowercaseUrls = true; More info on http://www.dhuvelle.com/2012/11/tips-for-aspnet-mvc-4-lowercase-urls.html – Marc Cals Nov 23 '12 at 09:27
  • @Marc but does not work for areas http://stackoverflow.com/questions/13271048/mvc-4-routecollection-lowercaseurls-breaks-when-using-area – Jeroen K Apr 12 '13 at 11:53
  • @MarcCals Actually routes.LowercaseUrls comes with .NET 4.5, not MVC 4. Sadly for me. – Andrew May 30 '13 at 18:38
  • 4
    @GONeale What happened to your URL? Second link is dead now! Made me laugh though, the title is "The Best **Gone Ale** Site on the Internet" – Luke Sep 30 '14 at 13:36
  • 1
    You can read [goneale.com's article here](https://web.archive.org/web/20110310094337/http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/) – Quentin S. Sep 13 '16 at 12:21
  • 2
    @chteuchteu that link didn't work for me, but [this one](http://web.archive.org/web/20131228221926/http://goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/) did. – chue x Feb 16 '17 at 16:09
33

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);
Community
  • 1
  • 1
Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
21

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).

Matt
  • 21
  • 1
  • 5
16

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...

John Oxley
  • 14,698
  • 18
  • 53
  • 78
  • GONeale's article provides an extension method so you can write `routes.MapRouteLowercase(...` which is nicer than the above: http://goneale.wordpress.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc/ – Drew Noakes Sep 08 '10 at 12:58
  • 1
    GONeale's entire blog disappeared. [Here is another blog entry](http://blog.dantup.com/2009/04/reducing-duplicate-content-with-aspnet.html) with similar content (and the same extension method). It addresses this situation in the context of reducing duplicate content. – patridge Apr 29 '11 at 02:45
11

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
        );
    }
Ian Robinson
  • 16,892
  • 8
  • 47
  • 61
Markus Wolters
  • 203
  • 1
  • 3
  • 10
  • For anyone reading this, the `LowercaseRoute` class in the first code snippet above appears to come from [this other answer](http://stackoverflow.com/a/931764/1822514) – chue x Jun 30 '14 at 14:55
5

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...

GalacticCowboy
  • 11,663
  • 2
  • 41
  • 66
3

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.

GuyIncognito
  • 1,246
  • 11
  • 11
  • I haven't tried that - will it let you use either one? ("moredetails" or "more_details") – GalacticCowboy May 18 '09 at 22:07
  • 1
    To follow up, I tried it and it requires you to use the specified name, so no, it won't allow you to handle it either way. Also, depending how you constructed your controller action and view, you may need to specify the name of the view explicitly. – GalacticCowboy May 19 '09 at 14:08