Como posso ter rotas em minúsculas no ASP.NET MVC?

145

Como posso ter rotas em minúsculas e sublinhado, se possível, no ASP.NET MVC? Para que eu /dinners/details/2ligasse DinnersController.Details(2)e, se possível,/dinners/more_details/2 ligasse DinnersController.MoreDetails(2)?

Tudo isso enquanto ainda usa padrões como {controller}/{action}/{id}.

J. Pablo Fernández
fonte
Acabei escrevendo todas as minhas rotas manualmente de qualquer maneira por várias razões e acho difícil evitar fazer isso com algo que não seja apenas CRUD. Então, eu apenas escrevi em letras minúsculas.
pupeno
Usando formulários da Web ? Acesse aqui: msdn.microsoft.com/en-us/library/… . (Estou gradualmente converter meu projeto de formulários web para MVC e ambos têm no projeto)
Jess
im certeza que você pode fazer isso como padrão
Eu não acho que importa se você digitar as rotas em maiúsculas ou minúsculas.

Respostas:

238

Com o System.Web.Routing 4.5, você pode implementar isso diretamente, definindo a propriedade 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 }
        );
    }

Supondo também que você esteja fazendo isso por razões de SEO, você deseja redirecionar os URLs recebidos para minúsculas (como dito em muitos dos links deste artigo).

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
fonte
4
Essa é de longe a coisa mais simples a se fazer na 4.0.
Paul Turner
1
Queria que isso estivesse no topo ... Quase um monte de código com cultura de carga!
Akatakritos
2
Ótima resposta :-) A última parte de SEO se encaixa perfeitamente em um módulo HTTP.
David Kirkland
1
@ Aaron Sherman Onde é que a parte Application_BeginRequest deve ir? Está me dando erros quando está dentro da classe pública RouteConfig e também quando está fora do if.
Richard Mišenčík
1
@ richard-mišenčík adicioná-lo ao arquivo Global.asax
ITmeze 3/15/15
44

Esses dois tutoriais ajudaram quando eu queria fazer a mesma coisa e funcionar bem:

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

EDIT: Para projetos com áreas, você precisa modificar o 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
fonte
9
O link GONeale foi alterado; O URL agora é goneale.com/2008/12/19/lowercase-route-urls-in-aspnet-mvc
Daniel Liuzzi
4
@ Derek - Não, os tutoriais são divididos ao usar o Area. Depois de 3 dias tentando TUDO ... Encontrei uma solução melhor, existe uma biblioteca chamada Attribute Routing. Resolve o problema e torna a vida muito mais fácil. philliphaydon.com/2011/08/…
Phill
6
Para o mvc 4, há uma solução melhor usando as rotas de propriedade.LowercaseUrls = true; Mais informações em dhuvelle.com/2012/11/tips-for-aspnet-mvc-4-lowercase-urls.html
Marc Cals
4
@GONeale O que aconteceu com o seu URL? O segundo link está morto agora! Me fez rir, no entanto, o título é "O Melhor Site de Cervejas Alemãs na Internet"
Luke
2
@chteuchteu esse link não funcionou para mim, mas este funcionou.
chue x
22

Se você usou o ASP.NET Core, provavelmente deveria dar uma olhada no seguinte :

Adicione a seguinte linha ao ConfigureServicesmétodo da Startupclasse.

services.AddRouting(options => options.LowercaseUrls = true);
Ebrahim Byagowi
fonte
1
O mesmo para o Core 2.0. Também em stackoverflow.com/a/45777372/195755
yzorg
21

Se você estiver usando o UrlHelper para gerar o link, basta especificar o nome da ação e do controlador em minúsculas:

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

Resultados em: / media / photos / delete / 64 (embora meu controlador e ação sejam caso pascal).

Matt
fonte
16
Eu acho que fazer esse trabalho em um local central é a solução mais fácil e mais padrão. Isso é tão ruim quanto CSS embutido. (Aparentemente, 15 pessoas usam CSS embutido).
O homem de muffin
Também é verdade para servidores Linux?
QMaster 23/05
15

Encontrei isso no Coder Journal de Nick Berardi , mas ele não tinha informações sobre como implementar a LowercaseRouteclasse. Portanto, republicando aqui com informações adicionais.

Primeiro estenda a Routeclasse paraLowercaseRoute

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

Modifique o RegisterRoutesmétodo 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
    //);
}

No entanto, gostaria de saber uma maneira de usar routes.MapRoute ...

John Oxley
fonte
O artigo de GONeale fornece um método de extensão para que você pode escrever routes.MapRouteLowercase(...o que é mais agradável do que o anterior: goneale.wordpress.com/2008/12/19/...
de Drew Noakes
1
O blog inteiro de GONeale desapareceu. Aqui está outra entrada de blog com conteúdo semelhante (e o mesmo método de extensão). Ele aborda essa situação no contexto de redução de conteúdo duplicado.
patridge
11

Você pode continuar usando a sintaxe do MapRoute adicionando esta classe como uma extensão ao 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;
    }
}

Agora você pode usar na inicialização do aplicativo "MapRouteLowerCase" em vez 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
fonte
Para quem lê isso, a LowercaseRouteclasse no primeiro trecho de código acima parece vir dessa outra resposta
chue x 30/06
6

Na verdade, isso tem duas respostas:

  1. Você já pode fazer isso: o mecanismo de rota faz uma comparação sem distinção entre maiúsculas e minúsculas. Se você digitar uma rota em minúscula, ela será roteada para o controlador e a ação apropriados.
  2. Se você estiver usando controles que geram links de rota (ActionLink, RouteLink etc.), eles produzirão links com maiúsculas e minúsculas, a menos que você substitua esse comportamento padrão.

Você está por conta própria para os sublinhados, embora ...

GalacticCowboy
fonte
2

Você poderia usar o atributo ActionName?

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

 }

Acho que o caso não importa. More_Details, more_DETAILS, mOrE_DeTaILs no URL, todos levam você à mesma ação do controlador.

GuyIncognito
fonte
Eu não tentei isso - vai deixar você usar qualquer um deles? ("moredetails" ou "more_details")
GalacticCowboy
Para acompanhar, eu tentei e exige que você use o nome especificado, portanto não, não permitirá que você lide com isso de qualquer maneira. Além disso, dependendo de como você construiu sua ação e exibição do controlador, pode ser necessário especificar explicitamente o nome da exibição.
GalacticCowboy