Jak mogę mieć trasy małymi literami w ASP.NET MVC?

145

Jak mogę mieć trasy małymi literami i podkreśleniem, jeśli to możliwe, w ASP.NET MVC? Żebym miał /dinners/details/2zadzwonić DinnersController.Details(2)i, jeśli to możliwe, /dinners/more_details/2zadzwonić DinnersController.MoreDetails(2)?

Wszystko to przy użyciu wzorów takich jak {controller}/{action}/{id}.

J. Pablo Fernández
źródło
Skończyło się na tym, że napisałem wszystkie moje trasy ręcznie z różnych powodów i myślę, że trudno jest tego uniknąć z czymkolwiek, co nie jest tylko CRUDem. Więc napisałem je małymi literami.
pupeno
Korzystasz z formularzy sieci Web ? Przejdź tutaj: msdn.microsoft.com/en-us/library/… . (Stopniowo konwertuję swój projekt z formularzy internetowych do MVC i mam oba w projekcie)
Jess
Nie sądzę, żeby miało to znaczenie, jeśli wpiszesz trasy małymi lub dużymi literami.

Odpowiedzi:

238

Dzięki System.Web.Routing 4.5 możesz zaimplementować to w prosty sposób, ustawiając właściwość LowercaseUrls w 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 }
        );
    }

Zakładając również, że robisz to ze względów SEO, chcesz przekierować przychodzące adresy URL na małe litery (jak wspomniano w wielu linkach w tym artykule).

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(".") == false)    
  {
     string lowercaseURL = (Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);
     if (Regex.IsMatch(lowercaseURL, @"[A-Z]"))
     {
      //You don't want to change casing on query strings
      lowercaseURL = lowercaseURL.ToLower() + HttpContext.Current.Request.Url.Query;

      Response.Clear();
      Response.Status = "301 Moved Permanently";
      Response.AddHeader("Location", lowercaseURL); 
      Response.End();
    }
 }
}
Aaron Sherman
źródło
4
To zdecydowanie najprostsza rzecz, jaką można zrobić w wersji 4.0.
Paul Turner
1
Chciałbym, żeby to było na górze ... Prawie dużo kodu kultowego!
akatakritos
2
Świetna odpowiedź :-) Druga część SEO ładnie pasuje do modułu HTTP.
David Kirkland,
1
@Aaron Sherman Gdzie ma się znaleźć część Application_BeginRequest? Daje mi błędy, gdy znajduje się w publicznej klasie RouteConfig, a także gdy jest poza if.
Richard Mišenčík
1
@ richard-mišenčík dodaj go do pliku Global.asax
ITmeze
44

Te dwa samouczki pomogły, gdy chciałem zrobić to samo i dobrze działać:

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

EDYCJA: W przypadku projektów z obszarami należy zmodyfikować metodę GetVirtualPath ():

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);
}
Derek Lawless
źródło
9
Link do GONeale uległ zmianie; Adres URL to teraz goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc
Daniel Liuzzi
4
@Derek - Nie, samouczki nie działają, gdy używasz Area. Po 3 dniach wypróbowywania WSZYSTKIEGO ... znalazłem lepsze rozwiązanie, jest biblioteka o nazwie Attribute Routing. Rozwiązuje problem i znacznie ułatwia życie. philliphaydon.com/2011/08/…
Phill
6
W przypadku mvc 4 jest lepsze rozwiązanie przy użyciu właściwości route.LowercaseUrls = true; Więcej informacji na dhuvelle.com/2012/11/tips-for-aspnet-mvc-4-lowercase-urls.html
Marc Cals
4
@GONeale Co się stało z Twoim adresem URL? Drugi link jest teraz martwy! Rozśmieszył mnie jednak, tytuł to „Witryna z najlepszymi kawami w Internecie”
Luke
2
@chteuchteu ten link nie działał dla mnie, ale ten działał .
chue x
22

Jeśli zdarzyło Ci się używać ASP.NET Core, prawdopodobnie powinieneś rzucić okiem na to :

Dodaj następujący wiersz do ConfigureServicesmetody Startupklasy.

services.AddRouting(options => options.LowercaseUrls = true);
Ebrahim Byagowi
źródło
1
To samo dotyczy Core 2.0. Również na stackoverflow.com/a/45777372/195755
yzorg
21

Jeśli używasz UrlHelper do generowania linku, możesz po prostu określić nazwę akcji i kontrolera jako małe litery:

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

Wyniki w: / media / photos / delete / 64 (mimo że mój kontroler i akcja to przypadek pascal).

Matt
źródło
16
Myślę, że wykonanie tej pracy w jednym centralnym miejscu jest najłatwiejszym i najbardziej standardowym rozwiązaniem. To jest tak samo złe, jak wbudowany CSS. (Najwyraźniej 15 osób używa wbudowanego CSS).
The Muffin Man,
Czy dotyczy to również serwerów Linux?
QMaster
15

Znalazłem to w Coder Journal Nicka Berardiego , ale nie było tam informacji o tym, jak wdrożyć LowercaseRoutezajęcia. Stąd ponowne zamieszczenie tutaj dodatkowych informacji.

Najpierw rozszerz Routeklasę doLowercaseRoute

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

Następnie zmodyfikuj RegisterRoutesmetodę 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
    //);
}

Chciałbym jednak wiedzieć, jak używać tras.MapRoute ...

John Oxley
źródło
Artykuł GONeale zapewnia metodę rozszerzenia, dzięki czemu możesz pisać, routes.MapRouteLowercase(...która jest ładniejsza niż powyższe: goneale.wordpress.com/2008/12/19/...
Drew Noakes
1
Cały blog GONeale zniknął. Oto kolejny wpis na blogu o podobnej treści (i tej samej metodzie rozszerzenia). Zajmuje się tą sytuacją w kontekście ograniczania powielania treści.
patridge
11

Możesz nadal używać składni MapRoute, dodając tę ​​klasę jako rozszerzenie do 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;
    }
}

Teraz możesz użyć „MapRouteLowerCase” podczas uruchamiania aplikacji zamiast „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
        );
    }
Markus Wolters
źródło
Dla każdego, kto to czyta, LowercaseRouteklasa w pierwszym fragmencie kodu powyżej wydaje się pochodzić z innej odpowiedzi
rozdział x
6

To faktycznie ma dwie odpowiedzi:

  1. Możesz już to zrobić: silnik tras przeprowadza porównania bez uwzględniania wielkości liter. Jeśli wpiszesz trasę pisaną małymi literami, zostanie ona skierowana do odpowiedniego kontrolera i akcji.
  2. Jeśli używasz formantów, które generują linki tras (ActionLink, RouteLink itp.), Będą generować łącza o różnej wielkości, chyba że zastąpisz to domyślne zachowanie.

Ale jesteś sam dla podkreślenia ...

GalacticCowboy
źródło
2

Czy możesz użyć atrybutu ActionName?

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

 }

Myślę, że sprawa nie ma znaczenia. More_Details, more_DETAILS, mOrE_DeTaILs w adresie URL prowadzą do tej samej akcji kontrolera.

GuyIncognito
źródło
Nie próbowałem tego - czy pozwoli ci to użyć jednego z nich? („moredetails” lub „more_details”)
GalacticCowboy
Aby kontynuować, wypróbowałem to i wymaga użycia określonej nazwy, więc nie, nie pozwoli ci to obsłużyć w żaden sposób. Ponadto, w zależności od tego, jak została utworzona akcja i widok kontrolera, może być konieczne jawne określenie nazwy widoku.
GalacticCowboy