Partilhar via


Roteamento para ações do controlador no ASP.NET Core

Por Ryan Nowak, Kirk Larkin e Rick Anderson

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Controladores do ASP.NET Core usam o Routing middleware para corresponder às URLs das solicitações de entrada e os associar a ações. Modelos de rota:

  • São definidos na inicialização em Program.cs ou em atributos.
  • Descreva como os caminhos de URL são correspondidos às ações.
  • São usados para gerar URLs para links. Os links gerados normalmente são retornados em respostas.

As ações são roteadas convencionalmente ou roteadas por atributos. Adicionar uma rota no controlador ou ação torna a rota roteada por atributos. Consulte Roteamento misto para obter mais informações.

Este documento:

  • Explica as interações entre MVC e roteamento:
    • Como os aplicativos MVC típicos usam os recursos de roteamento.
    • Abrange ambos:
    • Consulte Roteamento para obter detalhes avançados de roteamento.
  • Refere-se ao sistema de roteamento padrão chamado roteamento de ponto final. É possível usar controladores com a versão anterior do roteamento para fins de compatibilidade. Consulte o guia de migração 2.2-3.0 para obter instruções.

Configurar rota convencional

O modelo MVC ASP.NET Core gera código de roteamento convencional semelhante ao seguinte:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

MapControllerRoute é usado para criar uma única rota. A única rota é chamada rota default. A maioria dos aplicativos com controladores e modos de exibição usa um modelo de rota semelhante à default rota. REST As APIs devem usar roteamento de atributos.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

O modelo "{controller=Home}/{action=Index}/{id?}"de rota :

  • Corresponde a um caminho de URL como /Products/Details/5

  • Extrai os valores { controller = Products, action = Details, id = 5 } de rota tokenizando o caminho. A extração de valores de rota resulta em uma correspondência se o aplicativo tiver um controlador nomeado ProductsController e uma Details ação:

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo é fornecido pelo pacote Rick.Docs.Samples.RouteInfo NuGet e exibe informações de rota.

  • /Products/Details/5 associa o valor de id = 5 para definir o parâmetro id como 5. Consulte Vinculação de modelo para obter mais detalhes.

  • {controller=Home} define Home como padrão controller.

  • {action=Index} define Index como padrão action.

  • O ? caractere em {id?} define id como opcional.

    • Os parâmetros de rota padrão e opcionais não precisam estar presentes no caminho da URL para corresponder. Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe do modelo de rota.
  • Corresponde ao caminho /da URL .

  • Produz os valores de rota { controller = Home, action = Index }.

Os valores para controller e action fazem uso dos valores padrão. id não produz um valor, pois não há nenhum segmento correspondente no caminho da URL. / só corresponde se houver uma ação de HomeController e Index

public class HomeController : Controller
{
    public IActionResult Index() { ... }
}

Usando a definição de controlador e o modelo de rota anteriores, a ação é executada HomeController.Index para os seguintes caminhos de URL:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

O caminho / da URL usa os controladores do modelo de rota padrão Home e a ação Index. O caminho /Home da URL usa a ação padrão Index do modelo de rota.

O método MapDefaultControllerRoutede conveniência :

app.MapDefaultControllerRoute();

Replaces:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Important

O roteamento é configurado usando o middleware UseRouting e UseEndpoints. Para usar controladores:

Normalmente, as aplicações não necessitam de chamar UseRouting ou UseEndpoints. WebApplicationBuilder configura um pipeline de middleware que envolve o middleware adicionado em Program.cs com UseRouting e UseEndpoints. Para obter mais informações, consulte Roteamento no ASP.NET Core.

Roteamento convencional

O roteamento convencional é usado com controladores e visualizações. O default percurso:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

O precedente é um exemplo de uma rota convencional. É chamado de roteamento convencional porque estabelece uma convenção para caminhos de URL:

  • O primeiro segmento do caminho, {controller=Home}, corresponde ao nome do controlador.
  • O segundo segmento, {action=Index}, corresponde ao nome da ação.
  • O terceiro segmento, {id?}, é utilizado para algo opcional id. O ? in {id?} torna-o opcional. id é usado para mapear para uma entidade modelo.

Usando esta default rota, o caminho da URL:

  • /Products/List mapeia a ação ProductsController.List.
  • /Blog/Article/17 mapeia para BlogController.Article e normalmente o modelo vincula o id parâmetro a 17.

Este mapeamento:

  • Baseia-se apenas nos nomes do controlador e da ação.
  • Não se baseia em namespaces, locais de arquivos de origem ou parâmetros de método.

Usar o roteamento convencional com a rota padrão permite criar o aplicativo sem ter que criar um novo padrão de URL para cada ação. Para uma aplicação com ações no estilo CRUD, garantir a consistência das URLs entre os controladores:

  • Ajuda a simplificar o código.
  • Torna a interface do usuário mais previsível.

Warning

No código anterior, o id é definido como opcional pelo modelo de rota. As ações podem ser executadas sem a ID opcional fornecida como parte da URL. Geralmente, quando id é omitido do URL:

  • id é definido como 0 por vinculação de modelo.
  • Nenhuma entidade é encontrada no banco de dados correspondente id == 0.

O roteamento de atributos fornece controle refinado para tornar a ID necessária para algumas ações e não para outras. Por convenção, a documentação inclui parâmetros opcionais, como id quando é provável que eles apareçam no uso correto.

A maioria dos aplicativos deve escolher um esquema de roteamento básico e descritivo para que as URLs sejam legíveis e significativas. A rota convencional padrão {controller=Home}/{action=Index}/{id?}:

  • Suporta um esquema de roteamento básico e descritivo.
  • É um ponto de partida útil para aplicativos baseados em interface do usuário.
  • É o único modelo de rota necessário para muitos aplicativos de interface do usuário da Web. Para aplicações Web com interfaces de utilizador maiores, outra rota usando Áreas é frequentemente tudo o que é necessário.

MapControllerRoute e MapAreaRoute :

  • Atribua automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados.

Roteamento de ponto de extremidade no ASP.NET Core:

  • Não tem um conceito de rotas.
  • Não fornece garantias de encomenda para a execução de extensibilidade, todos os pontos finais são processados de uma só vez.

Habilite o registro em log para ver como as implementações de roteamento internas, como Route, correspondem às solicitações.

O roteamento de atributos é explicado posteriormente neste documento.

Várias rotas convencionais

Várias rotas convencionais podem ser configuradas adicionando mais chamadas para MapControllerRoute e MapAreaControllerRoute. Isso permite definir várias convenções ou adicionar rotas convencionais dedicadas a uma ação específica, como:

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

A blog rota no código anterior é uma rota convencional dedicada. É chamada de rota convencional dedicada porque:

Porque controller e action não aparecem no modelo "blog/{*article}" de rota como parâmetros:

  • Eles só podem ter os valores { controller = "Blog", action = "Article" }padrão.
  • Esta rota sempre corresponde à ação BlogController.Article.

/Blog, /Blog/Articlee /Blog/{any-string} são os únicos caminhos de URL que correspondem à rota do blog.

O exemplo anterior:

  • blog rota tem uma prioridade maior para correspondências do que default rota porque é adicionada primeiro.
  • É um exemplo de roteamento no estilo Slug em que é típico ter um nome de artigo como parte da URL.

Warning

Em ASP.NET Core, o roteamento não:

  • Defina um conceito chamado rota. UseRouting Adiciona mapeamento de rotas ao pipeline de middleware. O UseRouting middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência de ponto de extremidade com base na solicitação.
  • Fornecer garantias sobre a ordem de execução de extensões como IRouteConstraint ou IActionConstraint.

Consulte Roteamento para obter material de referência sobre roteamento.

Ordem de roteamento convencional

O roteamento convencional corresponde apenas a uma combinação de ação e controlador que são definidos pelo aplicativo. Pretende-se, assim, simplificar os casos em que as rotas convencionais se sobrepõem. Adicionar rotas usando MapControllerRoute, MapDefaultControllerRoute, e MapAreaControllerRoute atribui automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. As correspondências de uma rota que aparece mais cedo têm uma prioridade maior. O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas devem ser colocadas mais cedo, pois são mais específicas do que as rotas sem área. Rotas convencionais dedicadas com parâmetros de rota abrangentes como {*article} podem tornar uma rota demasiado excessiva, o que significa que ela corresponde a URLs que você pretendia que fossem correspondidas por outras rotas. Coloque as rotas gananciosas mais tarde na tabela de rotas para evitar partidas gananciosas.

Warning

Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:

  • Uma rota abrangente, por exemplo, {**slug}"
  • A rota de abrangência total não processa as solicitações que deveria processar.
  • A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.

Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.

Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Resolução de ações ambíguas

Quando dois endpoints combinam através do roteamento, o roteamento deve fazer uma das seguintes ações:

  • Escolha o melhor candidato.
  • Lance uma exceção.

Por exemplo:

public class Products33Controller : Controller
{
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        return ControllerContext.MyDisplayRouteInfo(id, product.name);
    }
}

O controlador anterior define duas ações que correspondem:

  • O caminho da URL /Products33/Edit/17
  • Dados de rota { controller = Products33, action = Edit, id = 17 }.

Este é um padrão típico para controladores MVC:

  • Edit(int) Exibe um formulário para editar um produto.
  • Edit(int, Product) processa o formulário publicado.

Para resolver a rota correta:

  • Edit(int, Product) é selecionado quando a solicitação é um HTTP POST.
  • Edit(int) é selecionado quando o verbo HTTP é qualquer outra coisa. Edit(int) é geralmente chamada via GET.

O HttpPostAttribute, [HttpPost], é fornecido ao roteamento para que ele possa escolher com base no método HTTP da solicitação. O HttpPostAttribute faz Edit(int, Product) uma partida melhor do que Edit(int).

É importante entender o papel de atributos como HttpPostAttribute. Atributos semelhantes são definidos para outros verbos HTTP. No roteamento convencional, é comum que as ações usem o mesmo nome de ação quando fazem parte de um fluxo de trabalho de formulário de exibição, envio de formulário. Por exemplo, consulte Examinar os dois métodos de ação Editar.

Se o roteamento não puder escolher o melhor candidato, um AmbiguousMatchException será lançado, listando os vários pontos de extremidade correspondentes.

Nomes de rotas convencionais

As strings "blog" e "default" nos exemplos seguintes são nomes de rotas convencionais.

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Os nomes de rota dão à rota um nome lógico. A rota nomeada pode ser usada para geração de URL. O uso de uma rota nomeada simplifica a criação de URL quando a ordenação de rotas pode tornar a geração de URL complicada. Os nomes de rota devem ser exclusivos em todo o aplicativo.

Nomes das rotas:

  • Não têm impacto na correspondência de URL ou no tratamento de solicitações.
  • São usados apenas para geração de URL.

O conceito de nome de rota é representado no roteamento como IEndpointNameMetadata. Os termos nome da rota e nome do ponto final:

  • São intercambiáveis.
  • Qual deles é usado na documentação e no código depende da API que está sendo descrita.

Roteamento de atributos para REST APIs

REST As APIs devem usar o roteamento de atributos para modelar a funcionalidade do aplicativo como um conjunto de recursos onde as operações são representadas por verbos HTTP.

O roteamento de atributos usa um conjunto de atributos para mapear ações diretamente para modelos de rota. O código a seguir é típico de uma REST API e é usado no próximo exemplo:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

No código anterior, MapControllers é chamado para mapear controladores roteados de atributo.

No exemplo a seguir:

  • HomeController corresponde a um conjunto de URLs semelhante ao que a rota convencional padrão {controller=Home}/{action=Index}/{id?} abrange.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

A ação HomeController.Index é executada para qualquer um dos caminhos de URL /, /Home, /Home/Index ou /Home/Index/3.

Este exemplo destaca uma diferença de programação fundamental entre o roteamento de atributos e o roteamento convencional. O roteamento de atributos requer mais entrada para especificar uma rota. A rota padrão convencional lida com rotas de forma mais sucinta. No entanto, o roteamento de atributos permite e requer um controle preciso de quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributos, os nomes do controlador e da ação não têm influência na correspondência da ação, a menos que a substituição de token seja usada. O exemplo a seguir corresponde às mesmas URLs do exemplo anterior:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

O código a seguir usa a substituição de token para action e controller:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O seguinte código aplica-se no controlador:

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

No código anterior, os modelos de método Index devem adicionar / ou ~/ aos modelos de rota. Modelos de rota aplicados a uma ação que começam com / ou ~/ não são combinados com modelos de rota aplicados ao controlador.

Consulte Precedência do modelo de rota para obter informações sobre a seleção do modelo de rota.

Nomes de roteamento reservados

As palavras-chave a seguir são nomes de parâmetros de rota reservados ao usar Controladores ou Razor Páginas:

  • action
  • area
  • controller
  • handler
  • page

Usar page como parâmetro de rota no roteamento de atributos é um erro comum. Fazer isso resulta em um comportamento inconsistente e confuso com a geração de URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Os nomes de parâmetros especiais são usados pela geração de URL para determinar se uma operação de geração de URL se refere a uma Razor Página ou a um Controlador.

As seguintes palavras-chave são reservadas no contexto de uma Razor vista ou de uma Razor página.

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Essas palavras-chave não devem ser usadas para gerações de links, parâmetros vinculados ao modelo ou propriedades de nível superior.

Modelos de verbo HTTP

ASP.NET Core tem os seguintes modelos de verbos HTTP:

Modelos de rota

ASP.NET Core tem os seguintes modelos de rota:

Roteamento de atributos com atributos verbais Http

Considere o seguinte controlador:

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior:

  • Cada ação contém o atributo, que restringe a [HttpGet] correspondência apenas com solicitações HTTP GET.
  • A ação GetProduct inclui o modelo "{id}", portanto, id é anexado ao modelo "api/[controller]" no controlador. O modelo de métodos é "api/[controller]/{id}". Portanto, esta ação só corresponde a solicitações GET para o formulário /api/test2/xyz,/api/test2/123,/api/test2/{any string}, etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • A GetIntProduct ação contém o "int/{id:int}" modelo. A :int parte do modelo restringe os id valores de rota a cadeias de caracteres que podem ser convertidas em um inteiro. Um pedido GET para /api/test2/int/abc:
    • Não corresponde a esta ação.
    • Retorna um erro 404 Não encontrado .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • A GetInt2Product ação contém {id} no modelo, mas não restringe id a valores que podem ser convertidos em um inteiro. Um pedido GET para /api/test2/int2/abc:
    • Coincide com este percurso.
    • A model binding falha em converter abc a um inteiro. O id parâmetro do método é inteiro.
    • Retorna uma solicitação incorreta de 400 porque a vinculação de modelo não conseguiu converter abc em um inteiro.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

O roteamento de atributos pode usar HttpMethodAttribute atributos como HttpPostAttribute, HttpPutAttributee HttpDeleteAttribute. Todos os atributos HTTP verbais aceitam um modelo de rota. O exemplo a seguir mostra duas ações que correspondem ao mesmo modelo de rota:

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Usando o caminho da URL /products3:

  • A MyProductsController.ListProducts ação é executada quando o verbo HTTP é GET.
  • A MyProductsController.CreateProduct ação é executada quando o verbo HTTP é POST.

Ao criar uma REST API, é raro que você precise usar [Route(...)] em um método de ação porque a ação aceita todos os métodos HTTP. É melhor usar o atributo verbo HTTP mais específico para ser preciso sobre o que a sua API suporta. Espera-se que os clientes de REST APIs saibam quais caminhos e verbos HTTP mapeiam para operações lógicas específicas.

REST As APIs devem usar o roteamento de atributos para modelar a funcionalidade do aplicativo como um conjunto de recursos onde as operações são representadas por verbos HTTP. Isso significa que muitas operações, por exemplo, GET e POST no mesmo recurso lógico usam a mesma URL. O roteamento de atributos fornece um nível de controle necessário para projetar cuidadosamente o layout de ponto de extremidade público de uma API.

Como uma rota de atributo se aplica a uma ação específica, é fácil definir parâmetros como obrigatórios como parte da definição do template de rota. No exemplo a seguir, id é necessário como parte do caminho da URL:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

A Products2ApiController.GetProduct(int) ação:

  • É executado com caminho de URL como /products2/3
  • Não é executado com o caminho /products2da URL .

O atributo [Consumes] permite que uma ação limite os tipos de conteúdo de solicitação suportados. Para obter mais informações, consulte Definir tipos de conteúdo de solicitação com suporte com o atributo Consumes.

Consulte Roteamento para obter uma descrição completa dos modelos de rota e opções relacionadas.

Para obter mais informações sobre [ApiController]o , consulte Atributo ApiController.

Nome da rota

O código a seguir define um nome de rota de Products_List:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Os nomes de rota podem ser usados para gerar uma URL com base em uma rota específica. Nomes das rotas:

  • Não têm impacto no comportamento de correspondência de URL do roteamento.
  • São usados apenas para geração de URL.

Os nomes de rota devem ser exclusivos em todo o aplicativo.

Compare o código anterior com a rota padrão convencional, que define o id parâmetro como opcional ({id?}). A capacidade de especificar APIs com precisão tem vantagens, como permitir /products e /products/5 ser despachado para diferentes ações.

Combinando rotas de atributos

Para tornar o roteamento de atributos menos repetitivo, os atributos de rota no controlador são combinados com atributos de rota nas ações individuais. Todos os modelos de rota definidos no controlador são preparados para rotear modelos nas ações. Colocar um atributo route no controlador faz com que todas as ações no controlador usem o roteamento de atributos.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No exemplo anterior:

  • O caminho de URL /products pode corresponder a ProductsApi.ListProducts
  • O caminho da URL /products/5 pode corresponder a ProductsApi.GetProduct(int).

Ambas as ações só correspondem ao HTTP GET porque estão marcadas com o [HttpGet] atributo.

Modelos de rota aplicados a uma ação que começam com / ou ~/ não são combinados com modelos de rota aplicados ao controlador. O exemplo a seguir corresponde a um conjunto de caminhos de URL semelhante à rota padrão.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

A tabela a seguir explica os [Route] atributos no código anterior:

Attribute Combina com [Route("Home")] Define o modelo de rota
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] No ""
[Route("About")] Yes "Home/About"

Ordem de atribuição de rota

O roteamento cria uma árvore e corresponde a todos os pontos de extremidade simultaneamente:

  • As entradas de rota se comportam como se colocadas em uma ordem ideal.
  • As rotas mais específicas têm a oportunidade de serem executadas antes das rotas mais gerais.

Por exemplo, uma rota de atributo como blog/search/{topic} é mais específica do que uma rota de atributo como blog/{*article}. A blog/search/{topic} rota tem maior prioridade, por padrão, porque é mais específica. Usando o roteamento convencional, o desenvolvedor é responsável por colocar as rotas na ordem desejada.

As rotas de atributo podem configurar uma ordem usando a Order propriedade. Todos os atributos de rota fornecidos pela estrutura incluem Order . As rotas são processadas de acordo com um tipo ascendente da Order propriedade. A ordem padrão é 0. Definir uma rota usando Order = -1 executa antes de rotas que não definem uma ordem. Definir uma rota usando Order = 1 execuções após a ordem de rota padrão.

Evite depender de . Se o espaço de URL de um aplicativo exigir valores de ordem explícitos para rotear corretamente, é provável que também seja confuso para os clientes. Em geral, o roteamento de atributos seleciona a rota correta com correspondência de URL. Se a ordem padrão usada para a geração de URL não estiver funcionando, usar um nome de rota como uma substituição geralmente é mais simples do que aplicar a Order propriedade.

Considere os dois controladores seguintes que definem a rota correspondente /home:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Solicitar /home com o código anterior gera uma exceção semelhante à seguinte:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Adicionar Order a um dos atributos de rota resolve a ambiguidade:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Com o código anterior, /home executa o endpoint HomeController.Index. Para chegar ao MyDemoController.MyIndex, solicite /home/MyIndex. Note:

  • O código anterior é um exemplo ou um design de roteamento incorreto. A propriedade Order foi usada para ilustrar.
  • A Order propriedade apenas resolve a ambiguidade, esse modelo não pode ser correspondido. Seria melhor remover o [Route("Home")] modelo.

Consulte Razor Convenções de rota e aplicativo de páginas: ordem de rota para obter informações sobre a ordem de rota com Razor o Pages.

Em alguns casos, um erro HTTP 500 é retornado com rotas ambíguas. Use registo para ver quais endpoints causaram o AmbiguousMatchException.

Substituição de símbolo em modelos de rota [controller], [action], [area]

Por conveniência, as rotas de atributos suportam a substituição de token colocando um token entre colchetes quadrados ([, ]). Os tokens [action], [area], e [controller] são substituídos pelos valores do nome da ação, nome da área e nome do controlador da ação em que a rota está definida:

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior:

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Jogos /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Jogos /Products0/Edit/{id}

A substituição de token ocorre como a última etapa da criação das rotas de atributo. O exemplo anterior se comporta da mesma forma que o código a seguir:

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Se você estiver lendo isso em um idioma diferente do inglês, informe-nos nesta questão de discussão do GitHub se quiser ver os comentários de código em seu idioma nativo.

As rotas de atributos também podem ser combinadas com herança. A combinação com a substituição de tokens torna isso poderoso. A substituição de token também se aplica a nomes de rota definidos por rotas de atributo. [Route("[controller]/[action]", Name="[controller]_[action]")]Gera um nome de rota exclusivo para cada ação:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Para corresponder aos delimitadores [ ou ] de substituição de token literal, escape-os repetindo o caractere correspondente ([[ ou ]]).

Usar um transformador de parâmetros para personalizar a substituição de token

A substituição de token pode ser personalizada usando um transformador de parâmetros. Um transformador de parâmetros implementa IOutboundParameterTransformer e transforma o valor dos parâmetros. Por exemplo, um transformador de parâmetro personalizado SlugifyParameterTransformer altera o valor de rota SubscriptionManagement para subscription-management:

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString()!,
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

Trata-se RouteTokenTransformerConvention de uma convenção de modelo de aplicação que:

  • Aplica um transformador de parâmetro a todas as rotas de atributos em um aplicativo.
  • Personaliza os valores do token de rota de atributo à medida que são substituídos.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O método anterior ListAll corresponde a /subscription-management/list-all.

O RouteTokenTransformerConvention está registado como opção:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(
                                 new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Consulte documentos web do MDN sobre Slug para a definição de Slug.

Warning

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.

Várias rotas de atributos

O roteamento de atributos suporta a definição de várias rotas que alcançam a mesma ação. O uso mais comum disso é imitar o comportamento da rota convencional padrão, conforme mostrado no exemplo a seguir:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Colocar vários atributos de rota no controlador significa que cada um deles combina com cada um dos atributos de rota nos métodos de ação:

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Todas as restrições de rota do verbo HTTP implementam IActionConstraint.

Quando múltiplos atributos de rota que implementam IActionConstraint são aplicados a uma ação:

  • Cada restrição de ação é combinada com o modelo de rota aplicado ao controlador.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Usar várias rotas em ações pode parecer útil e poderoso, é melhor manter o espaço de URL do seu aplicativo básico e bem definido. Use várias rotas em ações apenas onde necessário, por exemplo, para dar suporte a clientes existentes.

Especificando parâmetros opcionais de rota de atributo, valores padrão e restrições

As rotas de atributo suportam a mesma sintaxe embutida que as rotas convencionais para especificar parâmetros opcionais, valores padrão e restrições.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior, [HttpPost("product14/{id:int}")] aplica uma restrição de rota. A Products14Controller.ShowProduct ação é correspondida apenas por caminhos de URL como /product14/3. A parte {id:int} do modelo de rota restringe esse segmento a apenas inteiros.

Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe do modelo de rota.

Atributos de rota personalizados usando IRouteTemplateProvider

Todos os atributos de rota implementam IRouteTemplateProvider. O tempo de execução do ASP.NET Core:

  • Procura atributos em classes de controlador e métodos de ação quando o aplicativo é iniciado.
  • Usa os atributos que implementam IRouteTemplateProvider para criar o conjunto inicial de rotas.

Implementar IRouteTemplateProvider para definir atributos de rota personalizados. Cada IRouteTemplateProvider um permite que você defina uma única rota com um modelo, ordem e nome de rota personalizados:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O método anterior Get retorna Order = 2, Template = api/MyTestApi.

Usar modelo de aplicativo para personalizar rotas de atributos

O modelo de aplicação:

  • É um modelo de objeto criado na inicialização em Program.cs.
  • Contém todos os metadados usados pelo ASP.NET Core para rotear e executar as ações em um aplicativo.

O modelo de aplicativo inclui todos os dados coletados de atributos de rota. Os dados dos atributos de rota são fornecidos pela IRouteTemplateProvider implementação. Conventions:

  • Pode ser escrito para modificar o modelo de aplicativo para personalizar como o roteamento se comporta.
  • São lidos na inicialização do aplicativo.

Esta seção mostra um exemplo básico de personalização do roteamento usando o modelo de aplicativo. O código a seguir faz com que as rotas se alinhem aproximadamente com a estrutura de pastas do projeto.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

O código a seguir impede que a namespace convenção seja aplicada a controladores que são roteados por atributos:

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Por exemplo, o controlador a seguir não usa NamespaceRoutingConvention:

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

O método NamespaceRoutingConvention.Apply:

  • Não faz nada se o controlador for roteado por atributos.
  • Define o modelo dos controladores baseado em namespace, com a base namespace removida.

O NamespaceRoutingConvention pode ser aplicado em Program.cs:

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Por exemplo, considere o seguinte controlador:

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

No código anterior:

  • A base namespace é My.Application.
  • O nome completo do controlador anterior é My.Application.Admin.Controllers.UsersController.
  • O NamespaceRoutingConvention define o modelo de controladores como Admin/Controllers/Users/[action]/{id?.

O NamespaceRoutingConvention também pode ser aplicado como um atributo em um controlador:

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Roteamento misto: roteamento de atributos versus roteamento convencional

ASP.NET aplicativos principais podem combinar o uso de roteamento convencional e roteamento de atributos. É comum usar rotas convencionais para controladores que servem páginas HTML para navegadores e roteamento de atributos para controladores que servem REST APIs.

As ações são roteadas convencionalmente ou roteadas por atributos. Colocar uma rota no controlador ou na ação faz com que o atributo seja roteado. As ações que definem rotas de atributos não podem ser alcançadas através das rotas convencionais e vice-versa. Qualquer atributo de rota no controlador faz com que todas as ações no atributo do controlador sejam roteadas.

O roteamento de atributos e o roteamento convencional usam o mesmo mecanismo de roteamento.

Roteamento com caracteres especiais

O roteamento com caracteres especiais pode levar a resultados inesperados. Por exemplo, considere um controlador com o seguinte método de ação:

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Quando string id contém os seguintes valores codificados, podem ocorrer resultados inesperados:

ASCII Encoded
/ %2F
+

Os parâmetros de rota nem sempre são decodificados por URL. Este problema poderá ser resolvido no futuro. Para obter mais informações, consulte este problema do GitHub;

Geração de URL e valores ambientais

Os aplicativos podem usar recursos de geração de URL de roteamento para gerar links de URL para ações. A geração de URLs elimina URLs de codificação , tornando o código mais robusto e sustentável. Esta seção se concentra nos recursos de geração de URL fornecidos pelo MVC e aborda apenas noções básicas de como a geração de URL funciona. Consulte Roteamento para obter uma descrição detalhada da geração de URL.

A IUrlHelper interface é o elemento subjacente da infraestrutura entre MVC e roteamento para geração de URL. Uma instância de IUrlHelper está disponível através da propriedade Url em controladores, visualizações e componentes de visualização.

No exemplo a seguir, a IUrlHelper interface é usada por meio da Controller.Url propriedade para gerar uma URL para outra ação.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Se a aplicação estiver a usar a rota convencional padrão, o valor da variável url será a cadeia de caracteres /UrlGeneration/Destination do caminho da URL. Esse caminho de URL é criado pelo roteamento combinando:

  • Os valores de rota da solicitação atual, que são chamados valores ambientais.
  • Os valores passados para Url.Action e a substituição desses valores no modelo de rota:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Cada parâmetro de rota no modelo de rota tem seu valor substituído pela correspondência de nomes com os valores e valores ambientais. Um parâmetro de rota que não tem um valor pode:

  • Use um valor padrão se ele tiver um.
  • Pode ser ignorado se for opcional. Por exemplo, o id do template de rota {controller}/{action}/{id?}.

A geração de URL falhará se qualquer parâmetro de rota necessário não tiver um valor correspondente. Se a geração de URL falhar para uma rota, a próxima rota será tentada até que todas as rotas tenham sido tentadas ou uma correspondência seja encontrada.

O exemplo anterior Url.Action assume roteamento convencional. A geração de URL funciona de forma semelhante com o roteamento de atributos, embora os conceitos sejam diferentes. Com roteamento convencional:

  • Os valores de rota são usados para expandir um modelo.
  • Os valores de rota para controller e action geralmente aparecem nesse modelo. Isso funciona porque as URLs correspondidas pelo roteamento aderem a uma convenção.

O exemplo a seguir usa roteamento de atributos:

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

A Source ação no código anterior gera custom/url/to/destination.

LinkGenerator foi adicionado no ASP.NET Core 3.0 como alternativa ao IUrlHelper. LinkGenerator oferece funcionalidade semelhante, mas mais flexível. Cada método em IUrlHelper tem uma família correspondente de métodos em LinkGenerator também.

Gerando URLs por nome de ação

Url.Action, LinkGenerator.GetPathByAction e todas as sobrecargas relacionadas são projetadas para gerar o ponto de extremidade de destino especificando um nome de controlador e um nome de ação.

Ao usar Url.Actiono , os valores de rota atuais para controller e action são fornecidos pelo tempo de execução:

  • O valor de controller e action fazem parte de ambos os valores ambientais e valores. O método Url.Action sempre usa os valores atuais de e action e gera um caminho de controller URL que roteia para a ação atual.

O roteamento tenta usar os valores em valores ambientais para preencher informações que não foram fornecidas ao gerar uma URL. Considere uma rota como {a}/{b}/{c}/{d} com valores { a = Alice, b = Bob, c = Carol, d = David }ambientais:

  • O roteamento tem informações suficientes para gerar uma URL sem valores adicionais.
  • O roteamento tem informações suficientes porque todos os parâmetros de rota têm um valor.

Se o valor { d = Donovan } for acrescentado:

  • O valor { d = David } é ignorado.
  • O caminho da URL gerada é Alice/Bob/Carol/Donovan.

Aviso: Os caminhos de URL são hierárquicos. No exemplo anterior, se o valor { c = Cheryl } for adicionado:

  • Ambos os valores { c = Carol, d = David } são ignorados.
  • Não há mais um valor para d e a geração de URL falha.
  • Os valores desejados de c e d devem ser especificados para gerar uma URL.

Você pode esperar acertar esse problema com a rota {controller}/{action}/{id?}padrão . Este problema é raro na prática porque Url.Action sempre especifica explicitamente um controller e action valor.

Várias sobrecargas de Url.Action usam um objeto de valores de rota para fornecer valores para parâmetros de rota diferentes de controller e action. O objeto de valores de rota é freqüentemente usado com o id. Por exemplo, Url.Action("Buy", "Products", new { id = 17 }). O objeto de valores de rota:

  • Por convenção é geralmente um objeto de tipo anônimo.
  • Pode ser um IDictionary<> ou um POCO).

Quaisquer valores de rota adicionais que não correspondam aos parâmetros de rota são colocados na cadeia de caracteres de consulta.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url!);
}

O código anterior gera /Products/Buy/17?color=red.

O código a seguir gera uma URL absoluta:

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url!);
}

Para criar uma URL absoluta, use uma das seguintes opções:

  • Uma sobrecarga que aceita um protocol. Por exemplo, o código anterior.
  • LinkGenerator.GetUriByAction, que gera URIs absolutos por padrão.

Gerar URLs por rota

O código anterior demonstrou a geração de uma URL passando o controlador e o nome da ação. IUrlHelper também fornece a família de métodos Url.RouteUrl . Esses métodos são semelhantes a Url.Action, mas não copiam os valores atuais de action e controller para os valores de rota. O uso mais comum de Url.RouteUrl:

  • Especifica um nome de rota para gerar a URL.
  • Geralmente não especifica um controlador ou nome de ação.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

O seguinte Razor arquivo gera um link HTML para o Destination_Route:

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Gerar URLs em HTML e Razor

IHtmlHelper fornece os HtmlHelper métodos Html.BeginForm e Html.ActionLink para gerar <form> e <a> elementos respectivamente. Esses métodos usam o método Url.Action para gerar uma URL e aceitam argumentos semelhantes. Os Url.RouteUrl companheiros para HtmlHelper são Html.BeginRouteForm e Html.RouteLink que têm funcionalidade semelhante.

Os TagHelpers geram URLs através do form TagHelper e do <a> TagHelper. Ambos utilizam IUrlHelper na sua implementação. Consulte Auxiliares de tags em formulários para obter mais informações.

Nas vistas internas, o IUrlHelper está disponível através da propriedade Url para qualquer geração de URL ad-hoc não abordada nas instruções anteriores.

Geração de URL nos Resultados da Ação

Os exemplos anteriores mostraram o uso IUrlHelper em um controlador. O uso mais comum em um controlador é gerar uma URL como parte de um resultado de ação.

As ControllerBase classes base e Controller fornecem métodos de conveniência para resultados de ação que fazem referência a outra ação. Um uso típico é redirecionar depois de aceitar a entrada do usuário:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Os métodos de fábrica para resultados de ações, como RedirectToAction e CreatedAtAction, seguem um padrão semelhante aos métodos em IUrlHelper.

Caso especial para rotas convencionais dedicadas

O roteamento convencional pode usar um tipo especial de definição de rota chamado rota convencional dedicada. No exemplo a seguir, a rota nomeada blog é uma rota convencional dedicada:

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

Usando as definições de rota anteriores, Url.Action("Index", "Home") gera o caminho / da URL usando a default rota, mas por quê? Você pode supor que os valores { controller = Home, action = Index } de rota seriam suficientes para gerar uma URL usando blog, e o resultado seria /blog?action=Index&controller=Home.

Rotas convencionais dedicadas dependem de um comportamento especial de valores padrão que não têm um parâmetro de rota correspondente que impede que a rota seja muito gananciosa com a geração de URL. Neste caso, os valores padrão são { controller = Blog, action = Article }, e nem controller nem action aparecem como parâmetros de rota. Quando o roteamento executa a geração de URL, os valores fornecidos devem corresponder aos valores padrão. A geração de URL usando blog falha porque os valores { controller = Home, action = Index } não correspondem ao { controller = Blog, action = Article }. O roteamento então reverte para testar default, o que é bem-sucedido.

Areas

As áreas são um recurso MVC usado para organizar a funcionalidade relacionada em um grupo como um separado:

  • Espaço de nomes de roteamento para ações do controlador.
  • Estrutura de pastas para visões.

O uso de áreas permite que um aplicativo tenha vários controladores com o mesmo nome, desde que tenham áreas diferentes. O uso de áreas cria uma hierarquia com a finalidade de roteamento, adicionando outro parâmetro de rota, area para controller e action. Esta seção discute como o roteamento interage com as áreas. Consulte Áreas para obter detalhes sobre como as áreas são usadas com vistas.

O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota area para um area chamada Blog:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{    
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

No código anterior, MapAreaControllerRoute é chamado para criar o "blog_route". O segundo parâmetro, "Blog", é o nome da área.

Ao corresponder a um caminho de URL como /Manage/Users/AddUser, a rota "blog_route" gera os valores de rota { area = Blog, controller = Users, action = AddUser }. O valor da rota area é gerado por um valor padrão para area. A rota criada por MapAreaControllerRoute é equivalente ao seguinte:

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

MapAreaControllerRoute cria uma rota usando um valor padrão e uma restrição para area usar o nome da área fornecida, neste caso Blog. O valor padrão garante que a rota sempre produza { area = Blog, ... }, a restrição requer o valor { area = Blog, ... } para geração de URL.

O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas devem ser colocadas mais cedo, pois são mais específicas do que as rotas sem área.

Usando o exemplo anterior, os valores { area = Blog, controller = Users, action = AddUser } de rota correspondem à seguinte ação:

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

O atributo [Area] é o que denota um controlador como parte de uma área. Este controlador está na Blog área. Os controladores sem um [Area] atributo não são membros de nenhuma área e não correspondem quando o valor da area rota é fornecido pelo roteamento. No exemplo a seguir, apenas o primeiro controlador listado pode corresponder aos valores de rota { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

O namespace de cada controlador é mostrado aqui para integridade. Se os controladores anteriores usassem o mesmo namespace, um erro de compilador seria gerado. Os namespaces de classe não têm efeito no roteamento do MVC.

Os dois primeiros controladores são membros de áreas e só correspondem quando seu respetivo nome de área é fornecido pelo valor da area rota. O terceiro controlador não é membro de nenhuma área e só faz correspondência quando nenhum valor é fornecido pelo roteio para area.

Em termos de correspondência sem valor, a ausência do valor é a mesma como se o valor para area fosse nulo ou a cadeia vaziaarea.

Ao executar uma ação dentro de uma área, o valor da rota para area está disponível como um valor ambiente para o roteamento ser usado na geração de URL. Isso significa que, por padrão, as áreas agem de forma adesiva para a geração de URL, conforme demonstrado pelo exemplo a seguir.

app.MapAreaControllerRoute(name: "duck_route",
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
                             pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

O código a seguir gera uma URL para /Zebra/Users/AddUser:

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Definição da ação

Os métodos públicos em um controlador, exceto aqueles com o atributo NonAction , são ações.

Código de exemplo

Diagnóstico de depuração

Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Controladores do ASP.NET Core usam o Routing middleware para corresponder às URLs das solicitações de entrada e os associar a ações. Modelos de rota:

  • São definidos em código de inicialização ou atributos.
  • Descreva como os caminhos de URL são correspondidos às ações.
  • São usados para gerar URLs para links. Os links gerados normalmente são retornados em respostas.

As ações são roteadas convencionalmente ou roteadas por atributos. Adicionar uma rota no controlador ou ação torna a rota roteada por atributos. Consulte Roteamento misto para obter mais informações.

Este documento:

  • Explica as interações entre MVC e roteamento:
    • Como os aplicativos MVC típicos usam os recursos de roteamento.
    • Abrange ambos:
    • Consulte Roteamento para obter detalhes avançados de roteamento.
  • Refere-se ao sistema de roteamento padrão adicionado no ASP.NET Core 3.0, chamado roteamento de ponto final. É possível usar controladores com a versão anterior do roteamento para fins de compatibilidade. Consulte o guia de migração 2.2-3.0 para obter instruções. Consulte a versão 2.2 deste documento para obter material de referência sobre o sistema de roteamento herdado.

Configurar rota convencional

Startup.Configure normalmente tem um código semelhante ao seguinte ao usar o roteamento convencional:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Dentro da chamada para UseEndpoints, MapControllerRoute é usado para criar uma única rota. A única rota é chamada rota default. A maioria dos aplicativos com controladores e modos de exibição usa um modelo de rota semelhante à default rota. REST As APIs devem usar roteamento de atributos.

O modelo "{controller=Home}/{action=Index}/{id?}"de rota :

  • Corresponde a um caminho de URL como /Products/Details/5

  • Extrai os valores { controller = Products, action = Details, id = 5 } de rota tokenizando o caminho. A extração de valores de rota resulta em uma correspondência se o aplicativo tiver um controlador nomeado ProductsController e uma Details ação:

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfo é fornecido pelo pacote Rick.Docs.Samples.RouteInfo NuGet e exibe informações de rota.

  • /Products/Details/5 associa o valor de id = 5 para definir o parâmetro id como 5. Consulte Vinculação de modelo para obter mais detalhes.

  • {controller=Home} define Home como padrão controller.

  • {action=Index} define Index como padrão action.

  • O ? caractere em {id?} define id como opcional.

  • Os parâmetros de rota padrão e opcionais não precisam estar presentes no caminho da URL para corresponder. Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe do modelo de rota.

  • Corresponde ao caminho /da URL .

  • Produz os valores de rota { controller = Home, action = Index }.

Os valores para controller e action fazem uso dos valores padrão. id não produz um valor, pois não há nenhum segmento correspondente no caminho da URL. / só corresponde se houver uma ação de HomeController e Index

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

Usando a definição de controlador e o modelo de rota anteriores, a ação é executada HomeController.Index para os seguintes caminhos de URL:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

O caminho / da URL usa os controladores do modelo de rota padrão Home e a ação Index. O caminho /Home da URL usa a ação padrão Index do modelo de rota.

O método MapDefaultControllerRoutede conveniência :

endpoints.MapDefaultControllerRoute();

Replaces:

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Important

O roteamento é configurado usando os middlewares UseRouting, MapControllerRoute e MapAreaControllerRoute. Para usar controladores:

Roteamento convencional

O roteamento convencional é usado com controladores e visualizações. O default percurso:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

O precedente é um exemplo de uma rota convencional. É chamado de roteamento convencional porque estabelece uma convenção para caminhos de URL:

  • O primeiro segmento do caminho, {controller=Home}, corresponde ao nome do controlador.
  • O segundo segmento, {action=Index}, corresponde ao nome da ação.
  • O terceiro segmento, {id?}, é utilizado para algo opcional id. O ? in {id?} torna-o opcional. id é usado para mapear para uma entidade modelo.

Usando esta default rota, o caminho da URL:

  • /Products/List mapeia a ação ProductsController.List.
  • /Blog/Article/17 mapeia para BlogController.Article e normalmente o modelo vincula o id parâmetro a 17.

Este mapeamento:

  • Baseia-se apenas nos nomes do controlador e da ação.
  • Não se baseia em namespaces, locais de arquivos de origem ou parâmetros de método.

Usar o roteamento convencional com a rota padrão permite criar o aplicativo sem ter que criar um novo padrão de URL para cada ação. Para uma aplicação com ações no estilo CRUD, garantir a consistência das URLs entre os controladores:

  • Ajuda a simplificar o código.
  • Torna a interface do usuário mais previsível.

Warning

No código anterior, o id é definido como opcional pelo modelo de rota. As ações podem ser executadas sem a ID opcional fornecida como parte da URL. Geralmente, quando id é omitido do URL:

  • id é definido como 0 por vinculação de modelo.
  • Nenhuma entidade é encontrada no banco de dados correspondente id == 0.

O roteamento de atributos fornece controle refinado para tornar a ID necessária para algumas ações e não para outras. Por convenção, a documentação inclui parâmetros opcionais, como id quando é provável que eles apareçam no uso correto.

A maioria dos aplicativos deve escolher um esquema de roteamento básico e descritivo para que as URLs sejam legíveis e significativas. A rota convencional padrão {controller=Home}/{action=Index}/{id?}:

  • Suporta um esquema de roteamento básico e descritivo.
  • É um ponto de partida útil para aplicativos baseados em interface do usuário.
  • É o único modelo de rota necessário para muitos aplicativos de interface do usuário da Web. Para aplicações Web com interfaces de utilizador maiores, outra rota usando Áreas é frequentemente tudo o que é necessário.

MapControllerRoute e MapAreaRoute :

  • Atribua automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados.

Roteamento de endpoints no ASP.NET Core 3.0 ou posterior:

  • Não tem um conceito de rotas.
  • Não fornece garantias de encomenda para a execução de extensibilidade, todos os pontos finais são processados de uma só vez.

Habilite o registro em log para ver como as implementações de roteamento internas, como Route, correspondem às solicitações.

O roteamento de atributos é explicado posteriormente neste documento.

Várias rotas convencionais

Várias rotas convencionais podem ser adicionadas ao interior UseEndpoints adicionando mais chamadas para MapControllerRoute e MapAreaControllerRoute. Isso permite definir várias convenções ou adicionar rotas convencionais dedicadas a uma ação específica, como:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

A blog rota no código anterior é uma rota convencional dedicada. É chamada de rota convencional dedicada porque:

Porque controller e action não aparecem no modelo "blog/{*article}" de rota como parâmetros:

  • Eles só podem ter os valores { controller = "Blog", action = "Article" }padrão.
  • Esta rota sempre corresponde à ação BlogController.Article.

/Blog, /Blog/Articlee /Blog/{any-string} são os únicos caminhos de URL que correspondem à rota do blog.

O exemplo anterior:

  • blog rota tem uma prioridade maior para correspondências do que default rota porque é adicionada primeiro.
  • É um exemplo de roteamento no estilo Slug em que é típico ter um nome de artigo como parte da URL.

Warning

No ASP.NET Core 3.0 ou posterior, o roteamento não:

  • Defina um conceito chamado rota. UseRouting Adiciona mapeamento de rotas ao pipeline de middleware. O UseRouting middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência de ponto de extremidade com base na solicitação.
  • Fornecer garantias sobre a ordem de execução de extensões como IRouteConstraint ou IActionConstraint.

Consulte Roteamento para obter material de referência sobre roteamento.

Ordem de roteamento convencional

O roteamento convencional corresponde apenas a uma combinação de ação e controlador que são definidos pelo aplicativo. Pretende-se, assim, simplificar os casos em que as rotas convencionais se sobrepõem. Adicionar rotas usando MapControllerRoute, MapDefaultControllerRoute, e MapAreaControllerRoute atribui automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. As correspondências de uma rota que aparece mais cedo têm uma prioridade maior. O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas devem ser colocadas mais cedo, pois são mais específicas do que as rotas sem área. Rotas convencionais dedicadas com parâmetros de rota abrangentes como {*article} podem tornar uma rota demasiado excessiva, o que significa que ela corresponde a URLs que você pretendia que fossem correspondidas por outras rotas. Coloque as rotas gananciosas mais tarde na tabela de rotas para evitar partidas gananciosas.

Warning

Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:

  • Uma rota abrangente, por exemplo, {**slug}"
  • A rota de abrangência total não processa as solicitações que deveria processar.
  • A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.

Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.

Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Resolução de ações ambíguas

Quando dois endpoints combinam através do roteamento, o roteamento deve fazer uma das seguintes ações:

  • Escolha o melhor candidato.
  • Lance uma exceção.

Por exemplo:

    public class Products33Controller : Controller
    {
        public IActionResult Edit(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpPost]
        public IActionResult Edit(int id, Product product)
        {
            return ControllerContext.MyDisplayRouteInfo(id, product.name);
        }
    }
}

O controlador anterior define duas ações que correspondem:

  • O caminho da URL /Products33/Edit/17
  • Dados de rota { controller = Products33, action = Edit, id = 17 }.

Este é um padrão típico para controladores MVC:

  • Edit(int) Exibe um formulário para editar um produto.
  • Edit(int, Product) processa o formulário publicado.

Para resolver a rota correta:

  • Edit(int, Product) é selecionado quando a solicitação é um HTTP POST.
  • Edit(int) é selecionado quando o verbo HTTP é qualquer outra coisa. Edit(int) é geralmente chamada via GET.

O HttpPostAttribute, [HttpPost], é fornecido ao roteamento para que ele possa escolher com base no método HTTP da solicitação. O HttpPostAttribute faz Edit(int, Product) uma partida melhor do que Edit(int).

É importante entender o papel de atributos como HttpPostAttribute. Atributos semelhantes são definidos para outros verbos HTTP. No roteamento convencional, é comum que as ações usem o mesmo nome de ação quando fazem parte de um fluxo de trabalho de formulário de exibição, envio de formulário. Por exemplo, consulte Examinar os dois métodos de ação Editar.

Se o roteamento não puder escolher o melhor candidato, um AmbiguousMatchException será lançado, listando os vários pontos de extremidade correspondentes.

Nomes de rotas convencionais

As strings "blog" e "default" nos exemplos seguintes são nomes de rotas convencionais.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Os nomes de rota dão à rota um nome lógico. A rota nomeada pode ser usada para geração de URL. O uso de uma rota nomeada simplifica a criação de URL quando a ordenação de rotas pode tornar a geração de URL complicada. Os nomes de rota devem ser exclusivos em todo o aplicativo.

Nomes das rotas:

  • Não têm impacto na correspondência de URL ou no tratamento de solicitações.
  • São usados apenas para geração de URL.

O conceito de nome de rota é representado no roteamento como IEndpointNameMetadata. Os termos nome da rota e nome do ponto final:

  • São intercambiáveis.
  • Qual deles é usado na documentação e no código depende da API que está sendo descrita.

Roteamento de atributos para REST APIs

REST As APIs devem usar o roteamento de atributos para modelar a funcionalidade do aplicativo como um conjunto de recursos onde as operações são representadas por verbos HTTP.

O roteamento de atributos usa um conjunto de atributos para mapear ações diretamente para modelos de rota. O código a seguir StartUp.Configure é típico de uma REST API e é usado no próximo exemplo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

No código anterior, MapControllers é chamado dentro UseEndpoints para mapear controladores roteados de atributo.

No exemplo a seguir:

  • HomeController corresponde a um conjunto de URLs semelhante ao que a rota convencional padrão {controller=Home}/{action=Index}/{id?} abrange.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

A ação HomeController.Index é executada para qualquer um dos caminhos de URL /, /Home, /Home/Index ou /Home/Index/3.

Este exemplo destaca uma diferença de programação fundamental entre o roteamento de atributos e o roteamento convencional. O roteamento de atributos requer mais entrada para especificar uma rota. A rota padrão convencional lida com rotas de forma mais sucinta. No entanto, o roteamento de atributos permite e requer um controle preciso de quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributos, os nomes do controlador e da ação não têm influência na correspondência da ação, a menos que a substituição de token seja usada. O exemplo a seguir corresponde às mesmas URLs do exemplo anterior:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

O código a seguir usa a substituição de token para action e controller:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O seguinte código aplica-se no controlador:

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

No código anterior, os modelos de método Index devem adicionar / ou ~/ aos modelos de rota. Modelos de rota aplicados a uma ação que começam com / ou ~/ não são combinados com modelos de rota aplicados ao controlador.

Consulte Precedência do modelo de rota para obter informações sobre a seleção do modelo de rota.

Nomes de roteamento reservados

As palavras-chave a seguir são nomes de parâmetros de rota reservados ao usar Controladores ou Razor Páginas:

  • action
  • area
  • controller
  • handler
  • page

Usar page como parâmetro de rota no roteamento de atributos é um erro comum. Fazer isso resulta em um comportamento inconsistente e confuso com a geração de URL.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

Os nomes de parâmetros especiais são usados pela geração de URL para determinar se uma operação de geração de URL se refere a uma Razor Página ou a um Controlador.

As seguintes palavras-chave são reservadas no contexto de uma Razor vista ou de uma Razor página.

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Essas palavras-chave não devem ser usadas para gerações de links, parâmetros vinculados ao modelo ou propriedades de nível superior.

Modelos de verbo HTTP

ASP.NET Core tem os seguintes modelos de verbos HTTP:

Modelos de rota

ASP.NET Core tem os seguintes modelos de rota:

Roteamento de atributos com atributos verbais Http

Considere o seguinte controlador:

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior:

  • Cada ação contém o atributo, que restringe a [HttpGet] correspondência apenas com solicitações HTTP GET.
  • A ação GetProduct inclui o modelo "{id}", portanto, id é anexado ao modelo "api/[controller]" no controlador. O modelo de métodos é "api/[controller]/{id}". Portanto, esta ação só corresponde a solicitações GET para o formulário /api/test2/xyz,/api/test2/123,/api/test2/{any string}, etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • A GetIntProduct ação contém o "int/{id:int}" modelo. A :int parte do modelo restringe os id valores de rota a cadeias de caracteres que podem ser convertidas em um inteiro. Um pedido GET para /api/test2/int/abc:
    • Não corresponde a esta ação.
    • Retorna um erro 404 Não encontrado .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • A GetInt2Product ação contém {id} no modelo, mas não restringe id a valores que podem ser convertidos em um inteiro. Um pedido GET para /api/test2/int2/abc:
    • Coincide com este percurso.
    • A model binding falha em converter abc a um inteiro. O id parâmetro do método é inteiro.
    • Retorna uma solicitação incorreta de 400 porque a vinculação de modelo não conseguiu converter abc em um inteiro.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

O roteamento de atributos pode usar HttpMethodAttribute atributos como HttpPostAttribute, HttpPutAttributee HttpDeleteAttribute. Todos os atributos HTTP verbais aceitam um modelo de rota. O exemplo a seguir mostra duas ações que correspondem ao mesmo modelo de rota:

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Usando o caminho da URL /products3:

  • A MyProductsController.ListProducts ação é executada quando o verbo HTTP é GET.
  • A MyProductsController.CreateProduct ação é executada quando o verbo HTTP é POST.

Ao criar uma REST API, é raro que você precise usar [Route(...)] em um método de ação porque a ação aceita todos os métodos HTTP. É melhor usar o atributo verbo HTTP mais específico para ser preciso sobre o que a sua API suporta. Espera-se que os clientes de REST APIs saibam quais caminhos e verbos HTTP mapeiam para operações lógicas específicas.

REST As APIs devem usar o roteamento de atributos para modelar a funcionalidade do aplicativo como um conjunto de recursos onde as operações são representadas por verbos HTTP. Isso significa que muitas operações, por exemplo, GET e POST no mesmo recurso lógico usam a mesma URL. O roteamento de atributos fornece um nível de controle necessário para projetar cuidadosamente o layout de ponto de extremidade público de uma API.

Como uma rota de atributo se aplica a uma ação específica, é fácil definir parâmetros como obrigatórios como parte da definição do template de rota. No exemplo a seguir, id é necessário como parte do caminho da URL:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

A Products2ApiController.GetProduct(int) ação:

  • É executado com caminho de URL como /products2/3
  • Não é executado com o caminho /products2da URL .

O atributo [Consumes] permite que uma ação limite os tipos de conteúdo de solicitação suportados. Para obter mais informações, consulte Definir tipos de conteúdo de solicitação com suporte com o atributo Consumes.

Consulte Roteamento para obter uma descrição completa dos modelos de rota e opções relacionadas.

Para obter mais informações sobre [ApiController]o , consulte Atributo ApiController.

Nome da rota

O código a seguir define um nome de rota de Products_List:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Os nomes de rota podem ser usados para gerar uma URL com base em uma rota específica. Nomes das rotas:

  • Não têm impacto no comportamento de correspondência de URL do roteamento.
  • São usados apenas para geração de URL.

Os nomes de rota devem ser exclusivos em todo o aplicativo.

Compare o código anterior com a rota padrão convencional, que define o id parâmetro como opcional ({id?}). A capacidade de especificar APIs com precisão tem vantagens, como permitir /products e /products/5 ser despachado para diferentes ações.

Combinando rotas de atributos

Para tornar o roteamento de atributos menos repetitivo, os atributos de rota no controlador são combinados com atributos de rota nas ações individuais. Todos os modelos de rota definidos no controlador são preparados para rotear modelos nas ações. Colocar um atributo route no controlador faz com que todas as ações no controlador usem o roteamento de atributos.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No exemplo anterior:

  • O caminho de URL /products pode corresponder a ProductsApi.ListProducts
  • O caminho da URL /products/5 pode corresponder a ProductsApi.GetProduct(int).

Ambas as ações só correspondem ao HTTP GET porque estão marcadas com o [HttpGet] atributo.

Modelos de rota aplicados a uma ação que começam com / ou ~/ não são combinados com modelos de rota aplicados ao controlador. O exemplo a seguir corresponde a um conjunto de caminhos de URL semelhante à rota padrão.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

A tabela a seguir explica os [Route] atributos no código anterior:

Attribute Combina com [Route("Home")] Define o modelo de rota
[Route("")] Yes "Home"
[Route("Index")] Yes "Home/Index"
[Route("/")] No ""
[Route("About")] Yes "Home/About"

Ordem de atribuição de rota

O roteamento cria uma árvore e corresponde a todos os pontos de extremidade simultaneamente:

  • As entradas de rota se comportam como se colocadas em uma ordem ideal.
  • As rotas mais específicas têm a oportunidade de serem executadas antes das rotas mais gerais.

Por exemplo, uma rota de atributo como blog/search/{topic} é mais específica do que uma rota de atributo como blog/{*article}. A blog/search/{topic} rota tem maior prioridade, por padrão, porque é mais específica. Usando o roteamento convencional, o desenvolvedor é responsável por colocar as rotas na ordem desejada.

As rotas de atributo podem configurar uma ordem usando a Order propriedade. Todos os atributos de rota fornecidos pela estrutura incluem Order . As rotas são processadas de acordo com um tipo ascendente da Order propriedade. A ordem padrão é 0. Definir uma rota usando Order = -1 executa antes de rotas que não definem uma ordem. Definir uma rota usando Order = 1 execuções após a ordem de rota padrão.

Evite depender de . Se o espaço de URL de um aplicativo exigir valores de ordem explícitos para rotear corretamente, é provável que também seja confuso para os clientes. Em geral, o roteamento de atributos seleciona a rota correta com correspondência de URL. Se a ordem padrão usada para a geração de URL não estiver funcionando, usar um nome de rota como uma substituição geralmente é mais simples do que aplicar a Order propriedade.

Considere os dois controladores seguintes que definem a rota correspondente /home:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Solicitar /home com o código anterior gera uma exceção semelhante à seguinte:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

Adicionar Order a um dos atributos de rota resolve a ambiguidade:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

Com o código anterior, /home executa o endpoint HomeController.Index. Para chegar ao MyDemoController.MyIndex, solicite /home/MyIndex. Note:

  • O código anterior é um exemplo ou um design de roteamento incorreto. A propriedade Order foi usada para ilustrar.
  • A Order propriedade apenas resolve a ambiguidade, esse modelo não pode ser correspondido. Seria melhor remover o [Route("Home")] modelo.

Consulte Razor Convenções de rota e aplicativo de páginas: ordem de rota para obter informações sobre a ordem de rota com Razor o Pages.

Em alguns casos, um erro HTTP 500 é retornado com rotas ambíguas. Use registo para ver quais endpoints causaram o AmbiguousMatchException.

Substituição de símbolo em modelos de rota [controller], [action], [area]

Por conveniência, as rotas de atributos suportam a substituição de token colocando um token entre colchetes quadrados ([, ]). Os tokens [action], [area], e [controller] são substituídos pelos valores do nome da ação, nome da área e nome do controlador da ação em que a rota está definida:

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior:

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • Jogos /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Jogos /Products0/Edit/{id}

A substituição de token ocorre como a última etapa da criação das rotas de atributo. O exemplo anterior se comporta da mesma forma que o código a seguir:

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Se você estiver lendo isso em um idioma diferente do inglês, informe-nos nesta questão de discussão do GitHub se quiser ver os comentários de código em seu idioma nativo.

As rotas de atributos também podem ser combinadas com herança. A combinação com a substituição de tokens torna isso poderoso. A substituição de token também se aplica a nomes de rota definidos por rotas de atributo. [Route("[controller]/[action]", Name="[controller]_[action]")]Gera um nome de rota exclusivo para cada ação:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Para corresponder aos delimitadores [ ou ] de substituição de token literal, escape-os repetindo o caractere correspondente ([[ ou ]]).

Usar um transformador de parâmetros para personalizar a substituição de token

A substituição de token pode ser personalizada usando um transformador de parâmetros. Um transformador de parâmetros implementa IOutboundParameterTransformer e transforma o valor dos parâmetros. Por exemplo, um transformador de parâmetro personalizado SlugifyParameterTransformer altera o valor de rota SubscriptionManagement para subscription-management:

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString(),
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

Trata-se RouteTokenTransformerConvention de uma convenção de modelo de aplicação que:

  • Aplica um transformador de parâmetro a todas as rotas de atributos em um aplicativo.
  • Personaliza os valores do token de rota de atributo à medida que são substituídos.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O método anterior ListAll corresponde a /subscription-management/list-all.

O RouteTokenTransformerConvention está registrado como uma opção em ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

Consulte documentos web do MDN sobre Slug para a definição de Slug.

Warning

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.

Várias rotas de atributos

O roteamento de atributos suporta a definição de várias rotas que alcançam a mesma ação. O uso mais comum disso é imitar o comportamento da rota convencional padrão, conforme mostrado no exemplo a seguir:

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

Colocar vários atributos de rota no controlador significa que cada um deles combina com cada um dos atributos de rota nos métodos de ação:

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Todas as restrições de rota do verbo HTTP implementam IActionConstraint.

Quando múltiplos atributos de rota que implementam IActionConstraint são aplicados a uma ação:

  • Cada restrição de ação é combinada com o modelo de rota aplicado ao controlador.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Usar várias rotas em ações pode parecer útil e poderoso, é melhor manter o espaço de URL do seu aplicativo básico e bem definido. Use várias rotas em ações apenas onde necessário, por exemplo, para dar suporte a clientes existentes.

Especificando parâmetros opcionais de rota de atributo, valores padrão e restrições

As rotas de atributo suportam a mesma sintaxe embutida que as rotas convencionais para especificar parâmetros opcionais, valores padrão e restrições.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

No código anterior, [HttpPost("product14/{id:int}")] aplica uma restrição de rota. A Products14Controller.ShowProduct ação é correspondida apenas por caminhos de URL como /product14/3. A parte {id:int} do modelo de rota restringe esse segmento a apenas inteiros.

Consulte Referência de modelo de rota para obter uma descrição detalhada da sintaxe do modelo de rota.

Atributos de rota personalizados usando IRouteTemplateProvider

Todos os atributos de rota implementam IRouteTemplateProvider. O tempo de execução do ASP.NET Core:

  • Procura atributos em classes de controlador e métodos de ação quando o aplicativo é iniciado.
  • Usa os atributos que implementam IRouteTemplateProvider para criar o conjunto inicial de rotas.

Implementar IRouteTemplateProvider para definir atributos de rota personalizados. Cada IRouteTemplateProvider um permite que você defina uma única rota com um modelo, ordem e nome de rota personalizados:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; }
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

O método anterior Get retorna Order = 2, Template = api/MyTestApi.

Usar modelo de aplicativo para personalizar rotas de atributos

O modelo de aplicação:

  • É um modelo de objeto criado na inicialização.
  • Contém todos os metadados usados pelo ASP.NET Core para rotear e executar as ações em um aplicativo.

O modelo de aplicativo inclui todos os dados coletados de atributos de rota. Os dados dos atributos de rota são fornecidos pela IRouteTemplateProvider implementação. Conventions:

  • Pode ser escrito para modificar o modelo de aplicativo para personalizar como o roteamento se comporta.
  • São lidos na inicialização do aplicativo.

Esta seção mostra um exemplo básico de personalização do roteamento usando o modelo de aplicativo. O código a seguir faz com que as rotas se alinhem aproximadamente com a estrutura de pastas do projeto.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

O código a seguir impede que a namespace convenção seja aplicada a controladores que são roteados por atributos:

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

Por exemplo, o controlador a seguir não usa NamespaceRoutingConvention:

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

O método NamespaceRoutingConvention.Apply:

  • Não faz nada se o controlador for roteado por atributos.
  • Define o modelo dos controladores baseado em namespace, com a base namespace removida.

O NamespaceRoutingConvention pode ser aplicado em Startup.ConfigureServices:

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

Por exemplo, considere o seguinte controlador:

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

No código anterior:

  • A base namespace é My.Application.
  • O nome completo do controlador anterior é My.Application.Admin.Controllers.UsersController.
  • O NamespaceRoutingConvention define o modelo de controladores como Admin/Controllers/Users/[action]/{id?.

O NamespaceRoutingConvention também pode ser aplicado como um atributo em um controlador:

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Roteamento misto: roteamento de atributos versus roteamento convencional

ASP.NET aplicativos principais podem combinar o uso de roteamento convencional e roteamento de atributos. É comum usar rotas convencionais para controladores que servem páginas HTML para navegadores e roteamento de atributos para controladores que servem REST APIs.

As ações são roteadas convencionalmente ou roteadas por atributos. Colocar uma rota no controlador ou na ação faz com que o atributo seja roteado. As ações que definem rotas de atributos não podem ser alcançadas através das rotas convencionais e vice-versa. Qualquer atributo de rota no controlador faz com que todas as ações no atributo do controlador sejam roteadas.

O roteamento de atributos e o roteamento convencional usam o mesmo mecanismo de roteamento.

Geração de URL e valores ambientais

Os aplicativos podem usar recursos de geração de URL de roteamento para gerar links de URL para ações. A geração de URLs elimina URLs de codificação, tornando o código mais robusto e sustentável. Esta seção se concentra nos recursos de geração de URL fornecidos pelo MVC e aborda apenas noções básicas de como a geração de URL funciona. Consulte Roteamento para obter uma descrição detalhada da geração de URL.

A IUrlHelper interface é o elemento subjacente da infraestrutura entre MVC e roteamento para geração de URL. Uma instância de IUrlHelper está disponível através da propriedade Url em controladores, visualizações e componentes de visualização.

No exemplo a seguir, a IUrlHelper interface é usada por meio da Controller.Url propriedade para gerar uma URL para outra ação.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Se a aplicação estiver a usar a rota convencional padrão, o valor da variável url será a cadeia de caracteres /UrlGeneration/Destination do caminho da URL. Esse caminho de URL é criado pelo roteamento combinando:

  • Os valores de rota da solicitação atual, que são chamados valores ambientais.
  • Os valores passados para Url.Action e a substituição desses valores no modelo de rota:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

Cada parâmetro de rota no modelo de rota tem seu valor substituído pela correspondência de nomes com os valores e valores ambientais. Um parâmetro de rota que não tem um valor pode:

  • Use um valor padrão se ele tiver um.
  • Pode ser ignorado se for opcional. Por exemplo, o id do template de rota {controller}/{action}/{id?}.

A geração de URL falhará se qualquer parâmetro de rota necessário não tiver um valor correspondente. Se a geração de URL falhar para uma rota, a próxima rota será tentada até que todas as rotas tenham sido tentadas ou uma correspondência seja encontrada.

O exemplo anterior Url.Action assume roteamento convencional. A geração de URL funciona de forma semelhante com o roteamento de atributos, embora os conceitos sejam diferentes. Com roteamento convencional:

  • Os valores de rota são usados para expandir um modelo.
  • Os valores de rota para controller e action geralmente aparecem nesse modelo. Isso funciona porque as URLs correspondidas pelo roteamento aderem a uma convenção.

O exemplo a seguir usa roteamento de atributos:

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

A Source ação no código anterior gera custom/url/to/destination.

LinkGenerator foi adicionado no ASP.NET Core 3.0 como alternativa ao IUrlHelper. LinkGenerator oferece funcionalidade semelhante, mas mais flexível. Cada método em IUrlHelper tem uma família correspondente de métodos em LinkGenerator também.

Gerando URLs por nome de ação

Url.Action, LinkGenerator.GetPathByAction e todas as sobrecargas relacionadas são projetadas para gerar o ponto de extremidade de destino especificando um nome de controlador e um nome de ação.

Ao usar Url.Actiono , os valores de rota atuais para controller e action são fornecidos pelo tempo de execução:

  • O valor de controller e action fazem parte de ambos os valores ambientais e valores. O método Url.Action sempre usa os valores atuais de e action e gera um caminho de controller URL que roteia para a ação atual.

O roteamento tenta usar os valores em valores ambientais para preencher informações que não foram fornecidas ao gerar uma URL. Considere uma rota como {a}/{b}/{c}/{d} com valores { a = Alice, b = Bob, c = Carol, d = David }ambientais:

  • O roteamento tem informações suficientes para gerar uma URL sem valores adicionais.
  • O roteamento tem informações suficientes porque todos os parâmetros de rota têm um valor.

Se o valor { d = Donovan } for acrescentado:

  • O valor { d = David } é ignorado.
  • O caminho da URL gerada é Alice/Bob/Carol/Donovan.

Aviso: Os caminhos de URL são hierárquicos. No exemplo anterior, se o valor { c = Cheryl } for adicionado:

  • Ambos os valores { c = Carol, d = David } são ignorados.
  • Não há mais um valor para d e a geração de URL falha.
  • Os valores desejados de c e d devem ser especificados para gerar uma URL.

Você pode esperar acertar esse problema com a rota {controller}/{action}/{id?}padrão . Este problema é raro na prática porque Url.Action sempre especifica explicitamente um controller e action valor.

Várias sobrecargas de Url.Action usam um objeto de valores de rota para fornecer valores para parâmetros de rota diferentes de controller e action. O objeto de valores de rota é freqüentemente usado com o id. Por exemplo, Url.Action("Buy", "Products", new { id = 17 }). O objeto de valores de rota:

  • Por convenção é geralmente um objeto de tipo anônimo.
  • Pode ser um IDictionary<> ou um POCO).

Quaisquer valores de rota adicionais que não correspondam aos parâmetros de rota são colocados na cadeia de caracteres de consulta.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url);
}

O código anterior gera /Products/Buy/17?color=red.

O código a seguir gera uma URL absoluta:

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url);
}

Para criar uma URL absoluta, use uma das seguintes opções:

  • Uma sobrecarga que aceita um protocol. Por exemplo, o código anterior.
  • LinkGenerator.GetUriByAction, que gera URIs absolutos por padrão.

Gerar URLs por rota

O código anterior demonstrou a geração de uma URL passando o controlador e o nome da ação. IUrlHelper também fornece a família de métodos Url.RouteUrl . Esses métodos são semelhantes a Url.Action, mas não copiam os valores atuais de action e controller para os valores de rota. O uso mais comum de Url.RouteUrl:

  • Especifica um nome de rota para gerar a URL.
  • Geralmente não especifica um controlador ou nome de ação.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

O seguinte Razor arquivo gera um link HTML para o Destination_Route:

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Gerar URLs em HTML e Razor

IHtmlHelper fornece os HtmlHelper métodos Html.BeginForm e Html.ActionLink para gerar <form> e <a> elementos respectivamente. Esses métodos usam o método Url.Action para gerar uma URL e aceitam argumentos semelhantes. Os Url.RouteUrl companheiros para HtmlHelper são Html.BeginRouteForm e Html.RouteLink que têm funcionalidade semelhante.

Os TagHelpers geram URLs através do form TagHelper e do <a> TagHelper. Ambos utilizam IUrlHelper na sua implementação. Consulte Auxiliares de tags em formulários para obter mais informações.

Nas vistas internas, o IUrlHelper está disponível através da propriedade Url para qualquer geração de URL ad-hoc não abordada nas instruções anteriores.

Geração de URL nos Resultados da Ação

Os exemplos anteriores mostraram o uso IUrlHelper em um controlador. O uso mais comum em um controlador é gerar uma URL como parte de um resultado de ação.

As ControllerBase classes base e Controller fornecem métodos de conveniência para resultados de ação que fazem referência a outra ação. Um uso típico é redirecionar depois de aceitar a entrada do usuário:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

Os métodos de fábrica para resultados de ações, como RedirectToAction e CreatedAtAction, seguem um padrão semelhante aos métodos em IUrlHelper.

Caso especial para rotas convencionais dedicadas

O roteamento convencional pode usar um tipo especial de definição de rota chamado rota convencional dedicada. No exemplo a seguir, a rota nomeada blog é uma rota convencional dedicada:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

Usando as definições de rota anteriores, Url.Action("Index", "Home") gera o caminho / da URL usando a default rota, mas por quê? Você pode supor que os valores { controller = Home, action = Index } de rota seriam suficientes para gerar uma URL usando blog, e o resultado seria /blog?action=Index&controller=Home.

Rotas convencionais dedicadas dependem de um comportamento especial de valores padrão que não têm um parâmetro de rota correspondente que impede que a rota seja muito gananciosa com a geração de URL. Neste caso, os valores padrão são { controller = Blog, action = Article }, e nem controller nem action aparecem como parâmetros de rota. Quando o roteamento executa a geração de URL, os valores fornecidos devem corresponder aos valores padrão. A geração de URL usando blog falha porque os valores { controller = Home, action = Index } não correspondem ao { controller = Blog, action = Article }. O roteamento então reverte para testar default, o que é bem-sucedido.

Areas

As áreas são um recurso MVC usado para organizar a funcionalidade relacionada em um grupo como um separado:

  • Espaço de nomes de roteamento para ações do controlador.
  • Estrutura de pastas para visões.

O uso de áreas permite que um aplicativo tenha vários controladores com o mesmo nome, desde que tenham áreas diferentes. O uso de áreas cria uma hierarquia com a finalidade de roteamento, adicionando outro parâmetro de rota, area para controller e action. Esta seção discute como o roteamento interage com as áreas. Consulte Áreas para obter detalhes sobre como as áreas são usadas com vistas.

O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota area para um area chamada Blog:

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

No código anterior, MapAreaControllerRoute é chamado para criar o "blog_route". O segundo parâmetro, "Blog", é o nome da área.

Ao corresponder a um caminho de URL como /Manage/Users/AddUser, a rota "blog_route" gera os valores de rota { area = Blog, controller = Users, action = AddUser }. O valor da rota area é gerado por um valor padrão para area. A rota criada por MapAreaControllerRoute é equivalente ao seguinte:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaControllerRoute cria uma rota usando um valor padrão e uma restrição para area usar o nome da área fornecida, neste caso Blog. O valor padrão garante que a rota sempre produza { area = Blog, ... }, a restrição requer o valor { area = Blog, ... } para geração de URL.

O roteamento convencional é dependente da ordem. Em geral, as rotas com áreas devem ser colocadas mais cedo, pois são mais específicas do que as rotas sem área.

Usando o exemplo anterior, os valores { area = Blog, controller = Users, action = AddUser } de rota correspondem à seguinte ação:

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

O atributo [Area] é o que denota um controlador como parte de uma área. Este controlador está na Blog área. Os controladores sem um [Area] atributo não são membros de nenhuma área e não correspondem quando o valor da area rota é fornecido pelo roteamento. No exemplo a seguir, apenas o primeiro controlador listado pode corresponder aos valores de rota { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

O namespace de cada controlador é mostrado aqui para integridade. Se os controladores anteriores usassem o mesmo namespace, um erro de compilador seria gerado. Os namespaces de classe não têm efeito no roteamento do MVC.

Os dois primeiros controladores são membros de áreas e só correspondem quando seu respetivo nome de área é fornecido pelo valor da area rota. O terceiro controlador não é membro de nenhuma área e só faz correspondência quando nenhum valor é fornecido pelo roteio para area.

Em termos de correspondência sem valor, a ausência do valor é a mesma como se o valor para area fosse nulo ou a cadeia vaziaarea.

Ao executar uma ação dentro de uma área, o valor da rota para area está disponível como um valor ambiente para o roteamento ser usado na geração de URL. Isso significa que, por padrão, as áreas agem de forma adesiva para a geração de URL, conforme demonstrado pelo exemplo a seguir.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(name: "duck_route", 
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute(name: "default",
                                 pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

O código a seguir gera uma URL para /Zebra/Users/AddUser:

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

Definição da ação

Os métodos públicos em um controlador, exceto aqueles com o atributo NonAction , são ações.

Código de exemplo

Diagnóstico de depuração

Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}