¿Cómo puedo tener rutas en minúsculas en ASP.NET MVC?

145

¿Cómo puedo tener rutas en minúsculas y guiones bajos si es posible, en ASP.NET MVC? ¿Para que tuviera que /dinners/details/2llamar DinnersController.Details(2)y, si es posible, /dinners/more_details/2llamar DinnersController.MoreDetails(2)?

Todo esto mientras sigues usando patrones como {controller}/{action}/{id}.

J. Pablo Fernández
fuente
Terminé escribiendo todas mis rutas manualmente de todos modos por varias razones y creo que es difícil evitar hacerlo con algo que no sea solo CRUD. Entonces los acabo de escribir en minúsculas.
pupeno
¿Usa formularios web ? Vaya aquí: msdn.microsoft.com/en-us/library/… . (Estoy convirtiendo gradualmente mi proyecto de formularios web a MVC y tengo ambos en el proyecto)
Jess
im bastante seguro de que puede hacer esto por defecto
No creo que importe si escribe las rutas en minúsculas o mayúsculas.

Respuestas:

238

Con System.Web.Routing 4.5 puede implementar esto directamente configurando la propiedad LowercaseUrls de 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 }
        );
    }

También suponiendo que está haciendo esto por razones de SEO que desea redirigir las URL entrantes a minúsculas (como se dice en muchos de los enlaces de este artículo).

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
fuente
44
Esto es, con mucho, la cosa más simple que hacer en 4.0.
Paul Turner
1
Ojalá esto estuviese en la cima ... ¡Casi carga-cultivó mucho código!
akatakritos
2
Gran respuesta :-) La última parte de SEO encaja perfectamente en un módulo HTTP.
David Kirkland
1
@Aaron Sherman ¿Dónde se supone que debe ir la parte Application_BeginRequest? Me da errores cuando está dentro de la clase pública RouteConfig y también cuando está fuera de if.
Richard Mišenčík
1
@ richard-mišenčík agréguelo al archivo Global.asax
ITmeze
44

Estos dos tutoriales me ayudaron cuando quería hacer lo mismo y trabajar bien:

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

EDITAR: Para proyectos con áreas, debe modificar el método 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
fuente
9
El enlace de GONeale ha cambiado; URL es ahora goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc
Daniel Liuzzi
44
@Derek - No, los tutoriales se desglosan cuando se usan los de Área. Después de 3 días de intentar TODO ... Encontré una solución mejor, hay una biblioteca llamada Enrutamiento de atributos. Resuelve el problema y hace la vida mucho más fácil. philliphaydon.com/2011/08/…
Phill
66
Para mvc 4 hay una mejor solución usando rutas de propiedad. LowercaseUrls = true; Más información en dhuvelle.com/2012/11/tips-for-aspnet-mvc-4-lowercase-urls.html
Marc Cals
44
@GONeale ¿Qué pasó con tu URL? ¡El segundo enlace está muerto ahora! Sin embargo, me hizo reír, el título es "El mejor sitio de Gone Ale en Internet"
Luke
2
@chteuchteu ese enlace no funcionó para mí, pero este sí.
chue x
22

Si utilizaste ASP.NET Core, probablemente deberías echarle un vistazo a esto :

Agregue la siguiente línea al ConfigureServicesmétodo de la Startupclase.

services.AddRouting(options => options.LowercaseUrls = true);
revs Ebrahim Byagowi
fuente
1
Lo mismo para Core 2.0. También en stackoverflow.com/a/45777372/195755
yzorg
21

Si está utilizando UrlHelper para generar el enlace, simplemente puede especificar el nombre de la acción y el controlador en minúsculas:

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

Resultados en: / media / photos / delete / 64 (aunque mi controlador y acción son pascal case).

Mate
fuente
16
Creo que hacer este trabajo en una ubicación central es la solución más fácil y estándar. Esto es tan malo como el CSS en línea. (Aparentemente, 15 personas usan CSS en línea).
The Muffin Man
¿Es cierto también para los servidores Linux?
QMaster
15

Encontré esto en Coder Journal de Nick Berardi , pero no tenía información sobre cómo implementar elLowercaseRoute clase. Por lo tanto, volver a publicar aquí con información adicional.

Primero extienda la Routeclase aLowercaseRoute

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

Luego modifique el RegisterRoutesmétodo de 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
    //);
}

Sin embargo, me gustaría saber una forma de usar rutas. MapRoute ...

John Oxley
fuente
El artículo de GONeale proporciona un método de extensión para que pueda escribir, routes.MapRouteLowercase(...que es mejor que el anterior: goneale.wordpress.com/2008/12/19/…
Drew Noakes
1
Todo el blog de GONeale desapareció. Aquí hay otra entrada de blog con contenido similar (y el mismo método de extensión). Aborda esta situación en el contexto de reducir el contenido duplicado.
patridge
11

Puede continuar usando la sintaxis de MapRoute agregando esta clase como una extensión a 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;
    }
}

Ahora puede usar en el inicio de su aplicación "MapRouteLowerCase" en lugar de "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
fuente
Para cualquiera que lea esto, la LowercaseRouteclase en el primer fragmento de código anterior parece provenir de esta otra respuesta
chue x
6

Esto en realidad tiene dos respuestas:

  1. Ya puede hacer esto: el motor de ruta hace una comparación que no distingue entre mayúsculas y minúsculas. Si escribe una ruta en minúsculas, se enrutará al controlador y la acción apropiados.
  2. Si está utilizando controles que generan enlaces de ruta (ActionLink, RouteLink, etc.), producirán enlaces de mayúsculas y minúsculas a menos que anule este comportamiento predeterminado.

Sin embargo, estás solo para los guiones bajos ...

Galáctico vaquero
fuente
2

¿Podría usar el atributo ActionName?

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

 }

No creo que el caso importe. More_Details, more_DETAILS, mOrE_DeTaILs en la URL te llevan a la misma acción del controlador.

GuyIncognito
fuente
No lo he intentado, ¿te permitirá usar cualquiera de los dos? ("moredetails" o "more_details")
GalacticCowboy
Para el seguimiento, lo probé y requiere que uses el nombre especificado, por lo que no, no te permitirá manejarlo de ninguna manera. Además, dependiendo de cómo construyó la acción y la vista de su controlador, es posible que deba especificar el nombre de la vista explícitamente.
GalacticCowboy