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

De Ryan Nowak, Kirk Larkin e Rick Anderson

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

O ASP.NET Core MVC usa o middleware de Roteamento para fazer as correspondências das URLs de solicitações de entrada e mapeá-las para ações. Modelos de rota:

  • São definidos na inicialização em Program.cs ou em atributos.
  • Descrever 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. A colocação de uma rota no controlador ou na ação faz com que ela seja roteada por atributos. Para obter mais informações, consulte Roteamento misto.

Este documento:

  • Explica as interações entre MVC e roteamento:
    • Como aplicativos MVC típicos usam recursos de roteamento.
    • Abrange ambos:
    • Consulte Roteamento para obter detalhes sobre o roteamento avançado.
  • Refere-se ao sistema de roteamento padrão chamado roteamento de ponto de extremidade. É 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 ASP.NET Core MVC gera um 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 rota única é chamada default de rota. A maioria dos aplicativos com controladores e exibições usa um modelo de rota semelhante à rota default. As RESTAPIs devem usar o roteamento de atributo.

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

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

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

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

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

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

  • O /Products/Details/5modelo associa o valor de id = 5 para definir o id parâmetro como 5. Consulte Model binding para obter mais detalhes.

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

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

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

    • Parâmetros de rota opcionais e padrão não precisam estar presentes no caminho da URL para que haja uma correspondência. 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á um segmento correspondente no caminho do URL. / só corresponderá se houver uma HomeController ação e Index:

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

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

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

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

O método de conveniência MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Substitui:

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

Importante

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

Normalmente, os aplicativos não precisam chamar UseRouting ou UseEndpoints. WebApplicationBuilder configura um pipeline de middleware, que encapsula o middleware adicionado em Program.cs com UseRouting e UseEndpoints. Saiba mais em Roteamento no ASP.NET Core.

Roteamento convencional

O roteamento convencional é usado com controladores e exibições. A rota default:

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

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

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

Usando essa rota default, o caminho da URL:

  • /Products/List mapeia para a ação ProductsController.List.
  • /Blog/Article/17 mapeia para BlogController.Article e normalmente o modelo associa 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 arquivo de origem ou parâmetros de método.

O uso do roteamento convencional com a rota padrão permite a criação do aplicativo sem a necessidade de criar um novo padrão de URL para cada ação. Para um aplicativo com ações de estilo CRUD, ter consistência para as URLs entre controladores:

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

Aviso

O id no código anterior é 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 da URL:

  • id é definido como 0 por model binding.
  • Nenhuma entidade é encontrada no banco de dados correspondente a id == 0.

O roteamento de atributos oferece controle refinado para tornar o ID necessário 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?}:

  • Dá suporte a 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 aplicativos de interface do usuário web maiores, outra rota usando Áreas é frequentemente tudo o que é necessário.

MapControllerRoute e MapAreaRoute :

  • Atribuem automaticamente um valor de ordem aos 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 ordenação para a execução da extensibilidade, todos os pontos de extremidade são processados de uma só vez.

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

O roteamento de atributo é explicado posteriormente neste documento.

Várias rotas convencionais

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

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 { controller = "Blog", action = "Article" } valores padrão.
  • Essa rota sempre é mapeada para a ação BlogController.Article.

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

No exemplo anterior:

  • blog A rota tem uma prioridade mais alta para correspondências do que a default rota porque ela é adicionada primeiro.
  • É um exemplo de roteamento de estilo Slug em que é comum ter um nome de artigo como parte da URL.

Aviso

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

  • Defina um conceito chamado rota. UseRouting adiciona a correspondência 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.
  • Forneça garantias sobre a ordem de execução de extensibilidade como IRouteConstraint ou IActionConstraint.

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

Ordem de roteamento convencional

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

Aviso

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

  • Uma rota catch-all, por exemplo, {**slug}"
  • A rota catch-all não corresponde às solicitações que deveria corresponder.
  • Remover outras rotas faz com que a rota catch-all comece a funcionar.

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

Uma correção de aceitação para esse bug está contida no SDK do .NET Core 3.1.301 e posterior. O código a seguir define um comutador interno 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.

Resolvendo ações ambíguas

Quando dois pontos de extremidade correspondem ao roteamento, o roteamento deve fazer um dos seguintes procedimentos:

  • Escolha o melhor candidato.
  • Gera 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 do URL /Products33/Edit/17
  • Rotear dados { controller = Products33, action = Edit, id = 17 }.

Esse é 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 postado.

Para resolve 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 é chamado por meio de GET.

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

É importante entender a função 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 do tipo mostrar formulário, enviar formulário. Por exemplo, consulte Examinar os dois métodos de ação Editar.

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

Nomes de rotas convencionais

As cadeias de caracteres "blog" e "default" nos exemplos a seguir 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 URLs quando a ordenação das rotas pode complicar a geração de URLs. Nomes de rotas devem ser exclusivos no nível do aplicativo.

Nomes de rotas:

  • Não tenha nenhum 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 de extremidade:

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

Roteamento de atributo para REST APIs

RESTAs APIs da Web devem usar o roteamento de atributo para modelar a funcionalidade do aplicativo como um conjunto de recursos em que as operações são representadas por verbos HTTP.

O roteamento de atributo usa um conjunto de atributos para mapear ações diretamente para modelos de rota. O código a seguir é típico para uma API REST 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?} corresponde.
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 importante de programação entre o roteamento de atributo e o roteamento convencional. O roteamento de atributo requer mais entrada para especificar uma rota. A rota padrão convencional manipula rotas de forma mais sucinta. No entanto, o roteamento de atributo permite (e exige) o controle preciso de quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributo, os nomes do controlador e da ação não desempenham nenhum papel no qual a ação é correspondida, 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 código a seguir aplica [Route("[controller]/[action]")] ao 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 Index modelos de método devem acrescentar / 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 de modelo de rota para obter informações sobre a seleção de modelo de rota.

Nomes reservados de roteamento

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

  • action
  • area
  • controller
  • handler
  • page

Usar page como parâmetro de rota com roteamento de atributo é 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 exibição 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 vínculo, parâmetros associados ao modelo ou propriedades de nível superior.

Modelos de verbo HTTP

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

Modelos de rota

ASP.NET Core tem os seguintes modelos de rota:

Roteamento de atributos com atributos de verbo 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 [HttpGet], que restringe a correspondência somente a solicitações HTTP GET.
  • A ação GetProduct inclui o modelo "{id}" e, portanto id, é acrescentada ao modelo "api/[controller]" no controlador. O modelo de métodos é "api/[controller]/{id}". Portanto, essa ação corresponde apenas às 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 ação GetIntProduct contém o modelo "int/{id:int}". A parte :int do modelo restringe os id valores de rota para cadeias de caracteres que podem ser convertidas em um inteiro. Uma solicitação GET para /api/test2/int/abc:
    • Não corresponde a essa 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 ação GetInt2Product contém {id} no modelo, mas não restringe id a valores que podem ser convertidos em um inteiro. Uma solicitação GET para /api/test2/int2/abc:
    • Corresponde a essa rota.
    • A associação de modelo falha ao converter abc em um inteiro. O parâmetro id do método é um número inteiro.
    • Retorna uma 400 Solicitação Inválida porque a associação de modelo falhou ao converter abc em um inteiro.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

O roteamento por atributos pode usar HttpMethodAttribute atributos, como HttpPostAttribute, HttpPutAttribute e HttpDeleteAttribute. Todos os atributos de verbo HTTP 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 ação MyProductsController.ListProducts é executada quando o verbo HTTP é GET.
  • A ação MyProductsController.CreateProduct é executada quando o verbo HTTP é POST.

Ao criar uma API REST, é 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 de verbo HTTP mais específico para ser preciso sobre o que 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 da Web devem usar o roteamento de atributo para modelar a funcionalidade do aplicativo como um conjunto de recursos em que 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 o mesmo URL. O roteamento de atributo 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 fazer com que parâmetros sejam obrigatórios como parte da definição do modelo de rota. No exemplo a seguir, id é necessário como parte do caminho do URL:

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

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

  • É executado com o caminho da URL, como /products2/3
  • Não é executado com o caminho da URL /products2.

O atributo [Consome] permite que uma ação limite os tipos de conteúdo de solicitação com suporte. 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 de modelos de rota e as opções relacionadas.

Para obter mais informações sobre [ApiController], 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);
    }
}

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

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

Nomes de rotas devem ser exclusivos no nível do aplicativo.

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

Combinando rotas de atributo

Para tornar o roteamento de atributo menos repetitivo, os atributos de rota no controlador são combinados com atributos de rota nas ações individuais. Modelos de rota definidos no controlador precedem modelos de rota nas ações. Colocar um atributo de rota no controlador foz com que todas as ações no controlador usem o roteamento de atributo.

[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 da URL /products pode corresponder a ProductsApi.ListProducts
  • O caminho da URL /products/5 pode corresponder a ProductsApi.GetProduct(int).

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

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 atributos [Route] no código anterior:

Atributo Combina com [Route("Home")] Define o modelo de rota
[Route("")] Sim "Home"
[Route("Index")] Sim "Home/Index"
[Route("/")] Não ""
[Route("About")] Sim "Home/About"

Ordem de rota de atributo

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 ordenação ideal.
  • As rotas mais específicas têm a chance de ser 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 rota blog/search/{topic} tem prioridade mais alta, 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 um pedido usando a propriedade Order. Todos os atributos de rota fornecidos pela estrutura incluem Order. As rotas são processadas segundo uma classificação crescente da propriedade Order. A ordem padrão é 0. A definição de uma rota usando Order = -1 é executada antes das rotas que não definem uma ordem. A definição de uma rota usando Order = 1 é executada após a ordem de rota padrão.

Evite depender de Order. Se o espaço 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 por atributos seleciona a rota correta com correspondência de URL. Se a ordem padrão usada para a geração de URLs não estiver funcionando, usar um nome de rota como uma substituição geralmente é mais simples do que aplicar a propriedade Order.

Considere os dois controladores a seguir que definem a correspondência de rota /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);
    }
}

A solicitação /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 HomeController.Index ponto de extremidade. Para acessar o MyDemoController.MyIndex, solicite /home/MyIndex. Observação:

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

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

Em alguns casos, um erro HTTP 500 é retornado com rotas ambíguas. Use o registro em log para ver quais pontos de extremidade causaram o AmbiguousMatchException.

Substituição de token em modelos de rota [controlador], [ação], [área]

Para conveniência, as rotas de atributo dão suporte à substituição de token colocando um token entre chaves quadradas ([, ]). Os tokens [action], [area] e [controller] são substituídos pelos valores do nome da ação, do nome da área e do nome do controlador da ação em que a rota é 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();
}
  • Correspondências /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Correspondências /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 neste problema de discussão do GitHub se quiser ver os comentários de código em seu idioma nativo.

Rotas de atributo também podem ser combinadas com herança. Isso é poderoso quando combinado com a substituição de tokens. 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 ao delimitador de substituição de token literal [ ou ], faça seu escape repetindo o caractere ([[ ou ]]).

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

A substituição do token pode ser personalizada usando um transformador de parâmetro. Um transformador de parâmetro implementa IOutboundParameterTransformer e transforma o valor dos parâmetros. Por exemplo, um transformador de parâmetro SlugifyParameterTransformer personalizado muda 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();
    }
}

O RouteTokenTransformerConvention é uma convenção de modelo de aplicativo que:

  • Aplica um transformador de parâmetro a todas as rotas de atributo em um aplicativo.
  • Personaliza os valores de token de rota de atributo conforme eles 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:

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 da Web do MDN no Slug para obter a definição de Slug.

Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para RegularExpressions, causando um ataque de negação de serviço. APIs ASP.NET Core Framework que usam RegularExpressions passam um tempo limite.

Várias rotas de atributo

O roteamento de atributo dá suporte à definição de várias rotas que atingem a mesma ação. O uso mais comum desse recurso é para simular 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 se 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 de verbo HTTP implementam IActionConstraint.

Quando vários atributos de rota que implementam IActionConstraint são colocados em 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 aplicativo básico e bem definido. Use várias rotas em ações somente quando for necessário; por exemplo, para dar suporte a clientes existentes.

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

Rotas de atributo dão suporte à 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 somente por caminhos de URL como /product14/3. A parte do modelo de rota {id:int} 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 runtime 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.

Implemente IRouteTemplateProvider para definir atributos de rota personalizados. Cada IRouteTemplateProvider permite definir uma única rota com um nome, uma ordem e um modelo de rota personalizado:

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 Get anterior retorna Order = 2, Template = api/MyTestApi.

Usar o modelo de aplicativo para personalizar rotas de atributo

O modelo de aplicativo:

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

O modelo do aplicativo inclui todos os dados coletados dos atributos da rota. Os dados de atributos de rota são fornecidos pela IRouteTemplateProvider implementação. Convenções:

  • Pode ser gravado para modificar o modelo de aplicativo para personalizar o comportamento do roteamento.
  • 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 torna as rotas aproximadamente alinhadas 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 convenção namespace seja aplicada a controladores que são roteado por atributo:

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 fará nada se o controlador for roteado pelo atributo.
  • Define o modelo de controladores com base no 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 atributo versus roteamento convencional

Os aplicativos ASP.NET Core 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 segundo os atributos. Colocar uma rota no controlador ou na ação faz com que ela seja roteada segundo o atributo. Ações que definem rotas de atributo não podem ser acessadas por meio das rotas convencionais e vice-versa. Qualquer atributo de rota no controlador faz com que todas as ações no atributo de controlador sejam roteadas.

O roteamento de atributo 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. Esse problema poderá ser resolvido no futuro. Para obter mais informações, confira este tópico do GitHub;

Geração de URL e valores de ambiente

Os aplicativos podem usar os recursos de geração de URL de roteamento para gerar links de URL para ações. A geração de URLs elimina a codificação de URLs, tornando o código mais robusto e de fácil manutenção. Esta seção se concentra nos recursos de geração de URL fornecidos pelo MVC e aborda apenas os aspectos básicos do funcionamento da geração de URL. Consulte Roteamento para obter uma descrição detalhada da geração de URL.

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

No exemplo a seguir, a interface IUrlHelper é usada por meio da propriedade Controller.Url para gerar um 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 o aplicativo estiver usando a rota convencional padrão, o valor da variável url será a cadeia de caracteres do caminho do URL /UrlGeneration/Destination. Esse caminho de URL é criado pelo roteamento combinando:

  • Os valores de rota da solicitação atual, que são chamados valores de ambiente.
  • Os valores passados para Url.Action e substituindo esses 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 por nomes correspondentes com os valores e os valores de ambiente. Um parâmetro de rota que não tem um valor pode:

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

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

O exemplo anterior de Url.Action pressupõe o roteamento convencional. A geração de URL funciona de forma semelhante ao roteamento de atributos, embora os conceitos sejam diferentes. Com o 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 correspondentes ao roteamento seguem uma convenção.

O exemplo a seguir usa o roteamento de atributo:

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 uma alternativa a IUrlHelper. LinkGenerator oferece funcionalidade semelhante, mas mais flexível. Cada método em IUrlHelper também tem uma família correspondente de métodos LinkGenerator.

Gerando URLs pelo nome da ação

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

Ao usar Url.Action, os valores de rota atuais para controller e action são fornecidos pelo runtime:

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

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

  • 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 adicionado:

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

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

  • Ambos os valores são ignorados { c = Carol, d = David }.
  • 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.

Talvez você espere atingir esse problema com a rota padrão {controller}/{action}/{id?}. Esse problema é raro na prática porque Url.Action sempre especifica explicitamente um controller valor e action .

Várias sobrecargas de Url.Action recebem 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 é usado com frequência com 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).

Qualquer valor de rota adicional que não corresponder aos parâmetros de rota será colocado 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 um URL passando o nome do controlador e 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 da 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 arquivo a seguir Razor 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 um URL e aceitam argumentos semelhantes. O complementos Url.RouteUrl para HtmlHelper são Html.BeginRouteForm e Html.RouteLink, que têm uma funcionalidade semelhante.

TagHelpers geram URLs por meio do TagHelper form e do TagHelper <a>. Ambos usam IUrlHelper para sua implementação. Consulte Auxiliares de marcação em formulários para obter mais informações.

Nos modos de exibição, o IUrlHelper está disponível por meio da propriedade Url para qualquer geração de URL ad hoc não abordada acima.

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 um URL como parte do resultado de uma ação.

As classes base ControllerBase 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 é para redirecionar após 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 de resultados de ação, como RedirectToAction e CreatedAtAction, seguem um padrão semelhante ao dos 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 denominada 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ê poderia imaginar que os valores de rota { controller = Home, action = Index } 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 impeça que a rota seja muito ambiciosa com a geração de URLs. Nesse caso, os valores padrão são { controller = Blog, action = Article } e nem controller ou action aparece como um parâmetro 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 a { controller = Blog, action = Article }. O roteamento, então, faz o fallback para tentar default, que é bem-sucedido.

Áreas

As áreas são um recurso do MVC usado para organizar funcionalidades relacionadas em um grupo separado:

  • Namespace de roteamento para ações do controlador.
  • Estrutura de pastas para exibições.

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

O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota area para um area chamado 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 "blog_route" rota gera os valores de rota { area = Blog, controller = Users, action = AddUser }. O area valor da rota é produzido por um valor padrão para area. A rota criada por MapAreaControllerRoute é equivalente à 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 a restrição para area usando o nome da área fornecido, nesse 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 antes, pois são mais específicas do que as rotas sem área.

Usando o exemplo anterior, os valores de rota { area = Blog, controller = Users, action = AddUser } 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. Esse controlador está na área Blog. Os controladores sem um atributo [Area] não são membros de nenhuma área e não correspondem quando o valor da rota area é fornecido pelo roteamento. No exemplo a seguir, somente 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 fins de integridade. Se os controladores anteriores usassem o mesmo namespace, um erro do compilador seria gerado. Namespaces de classe não têm efeito sobre o roteamento do MVC.

Os primeiros dois controladores são membros de áreas e correspondem somente quando seus respectivos nomes de área são fornecidos pelo valor de rota area. O terceiro controlador não é um membro de nenhuma área e só pode corresponder quando nenhum valor para area for fornecido pelo roteamento.

Em termos de não corresponder a nenhum valor, a ausência do valor de area é equivalente ao valor de area ser nulo ou uma cadeia de caracteres vazia.

Ao executar uma ação dentro de uma área, o valor de rota para area está disponível como um valor de ambiente para o roteamento a ser usado na geração de URL. Isso significa que, por padrão, as áreas atuam como se fossem autoadesivas para a geração de URL, como demonstrado no 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 um 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 de ação

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

Código de exemplo

Depurar diagnóstico

Para obter a 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"
    }
  }
}

O ASP.NET Core MVC usa o middleware de Roteamento para fazer as correspondências das URLs de solicitações de entrada e mapeá-las para ações. Modelos de rota:

  • São definidos no código de inicialização ou nos atributos.
  • Descrever 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. A colocação de uma rota no controlador ou na ação faz com que ela seja roteada por atributos. Para obter mais informações, consulte Roteamento misto.

Este documento:

  • Explica as interações entre MVC e roteamento:
    • Como aplicativos MVC típicos usam recursos de roteamento.
    • Abrange ambos:
    • Consulte Roteamento para obter detalhes sobre o roteamento avançado.
  • Refere-se ao sistema de roteamento padrão adicionado no ASP.NET Core 3.0, chamado roteamento de ponto de extremidade. É 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 no 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 rota única é chamada default de rota. A maioria dos aplicativos com controladores e exibições usa um modelo de rota semelhante à rota default. As RESTAPIs devem usar o roteamento de atributo.

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

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

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

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

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

  • O /Products/Details/5modelo associa o valor de id = 5 para definir o id parâmetro como 5. Consulte Model binding para obter mais detalhes.

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

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

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

  • Parâmetros de rota opcionais e padrão não precisam estar presentes no caminho da URL para que haja uma correspondência. 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á um segmento correspondente no caminho do URL. / só corresponderá se houver uma HomeController ação e Index:

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

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

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

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

O método de conveniência MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Substitui:

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

Importante

O roteamento é configurado usando o UseRouting, MapControllerRoute e MapAreaControllerRoute middleware. Para usar controladores:

Roteamento convencional

O roteamento convencional é usado com controladores e exibições. A rota default:

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

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

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

Usando essa rota default, o caminho da URL:

  • /Products/List mapeia para a ação ProductsController.List.
  • /Blog/Article/17 mapeia para BlogController.Article e normalmente o modelo associa 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 arquivo de origem ou parâmetros de método.

O uso do roteamento convencional com a rota padrão permite a criação do aplicativo sem a necessidade de criar um novo padrão de URL para cada ação. Para um aplicativo com ações de estilo CRUD, ter consistência para as URLs entre controladores:

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

Aviso

O id no código anterior é 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 da URL:

  • id é definido como 0 por model binding.
  • Nenhuma entidade é encontrada no banco de dados correspondente a id == 0.

O roteamento de atributos oferece controle refinado para tornar o ID necessário 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?}:

  • Dá suporte a 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 aplicativos de interface do usuário web maiores, outra rota usando Áreas é frequentemente tudo o que é necessário.

MapControllerRoute e MapAreaRoute :

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

Roteamento de ponto de extremidade no ASP.NET Core 3.0 e posterior:

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

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

O roteamento de atributo é explicado posteriormente neste documento.

Várias rotas convencionais

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

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 { controller = "Blog", action = "Article" } valores padrão.
  • Essa rota sempre é mapeada para a ação BlogController.Article.

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

No exemplo anterior:

  • blog A rota tem uma prioridade mais alta para correspondências do que a default rota porque ela é adicionada primeiro.
  • É um exemplo de roteamento de estilo Slug em que é comum ter um nome de artigo como parte da URL.

Aviso

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

  • Defina um conceito chamado rota. UseRouting adiciona a correspondência 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.
  • Forneça garantias sobre a ordem de execução de extensibilidade como IRouteConstraint ou IActionConstraint.

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

Ordem de roteamento convencional

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

Aviso

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

  • Uma rota catch-all, por exemplo, {**slug}"
  • A rota catch-all não corresponde às solicitações que deveria corresponder.
  • Remover outras rotas faz com que a rota catch-all comece a funcionar.

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

Uma correção de aceitação para esse bug está contida no SDK do .NET Core 3.1.301 e posterior. O código a seguir define um comutador interno 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.

Resolvendo ações ambíguas

Quando dois pontos de extremidade correspondem ao roteamento, o roteamento deve fazer um dos seguintes procedimentos:

  • Escolha o melhor candidato.
  • Gera 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 do URL /Products33/Edit/17
  • Rotear dados { controller = Products33, action = Edit, id = 17 }.

Esse é 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 postado.

Para resolve 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 é chamado por meio de GET.

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

É importante entender a função 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 do tipo mostrar formulário, enviar formulário. Por exemplo, consulte Examinar os dois métodos de ação Editar.

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

Nomes de rotas convencionais

As cadeias de caracteres "blog" e "default" nos exemplos a seguir 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 URLs quando a ordenação das rotas pode complicar a geração de URLs. Nomes de rotas devem ser exclusivos no nível do aplicativo.

Nomes de rotas:

  • Não tenha nenhum 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 de extremidade:

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

Roteamento de atributo para REST APIs

RESTAs APIs da Web devem usar o roteamento de atributo para modelar a funcionalidade do aplicativo como um conjunto de recursos em que as operações são representadas por verbos HTTP.

O roteamento de atributo usa um conjunto de atributos para mapear ações diretamente para modelos de rota. O código a seguir StartUp.Configure é típico para 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 de 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?} corresponde.
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 importante de programação entre o roteamento de atributo e o roteamento convencional. O roteamento de atributo requer mais entrada para especificar uma rota. A rota padrão convencional manipula rotas de forma mais sucinta. No entanto, o roteamento de atributo permite (e exige) o controle preciso de quais modelos de rota se aplicam a cada ação.

Com o roteamento de atributo, os nomes do controlador e da ação não desempenham nenhum papel no qual a ação é correspondida, 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 código a seguir aplica [Route("[controller]/[action]")] ao 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 Index modelos de método devem acrescentar / 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 de modelo de rota para obter informações sobre a seleção de modelo de rota.

Nomes reservados de roteamento

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

  • action
  • area
  • controller
  • handler
  • page

Usar page como parâmetro de rota com roteamento de atributo é 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 exibição 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 vínculo, parâmetros associados ao modelo ou propriedades de nível superior.

Modelos de verbo HTTP

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

Modelos de rota

ASP.NET Core tem os seguintes modelos de rota:

Roteamento de atributos com atributos de verbo 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 [HttpGet], que restringe a correspondência somente a solicitações HTTP GET.
  • A ação GetProduct inclui o modelo "{id}" e, portanto id, é acrescentada ao modelo "api/[controller]" no controlador. O modelo de métodos é "api/[controller]/{id}". Portanto, essa ação corresponde apenas às 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 ação GetIntProduct contém o modelo "int/{id:int}". A parte :int do modelo restringe os id valores de rota para cadeias de caracteres que podem ser convertidas em um inteiro. Uma solicitação GET para /api/test2/int/abc:
    • Não corresponde a essa 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 ação GetInt2Product contém {id} no modelo, mas não restringe id a valores que podem ser convertidos em um inteiro. Uma solicitação GET para /api/test2/int2/abc:
    • Corresponde a essa rota.
    • A associação de modelo falha ao converter abc em um inteiro. O parâmetro id do método é um número inteiro.
    • Retorna uma 400 Solicitação Inválida porque a associação de modelo falhou ao converter abc em um inteiro.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

O roteamento por atributos pode usar HttpMethodAttribute atributos, como HttpPostAttribute, HttpPutAttribute e HttpDeleteAttribute. Todos os atributos de verbo HTTP 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 ação MyProductsController.ListProducts é executada quando o verbo HTTP é GET.
  • A ação MyProductsController.CreateProduct é executada quando o verbo HTTP é POST.

Ao criar uma API REST, é 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 de verbo HTTP mais específico para ser preciso sobre o que 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 da Web devem usar o roteamento de atributo para modelar a funcionalidade do aplicativo como um conjunto de recursos em que 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 o mesmo URL. O roteamento de atributo 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 fazer com que parâmetros sejam obrigatórios como parte da definição do modelo de rota. No exemplo a seguir, id é necessário como parte do caminho do URL:

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

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

  • É executado com o caminho da URL, como /products2/3
  • Não é executado com o caminho da URL /products2.

O atributo [Consome] permite que uma ação limite os tipos de conteúdo de solicitação com suporte. 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 de modelos de rota e as opções relacionadas.

Para obter mais informações sobre [ApiController], 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);
    }
}

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

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

Nomes de rotas devem ser exclusivos no nível do aplicativo.

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

Combinando rotas de atributo

Para tornar o roteamento de atributo menos repetitivo, os atributos de rota no controlador são combinados com atributos de rota nas ações individuais. Modelos de rota definidos no controlador precedem modelos de rota nas ações. Colocar um atributo de rota no controlador foz com que todas as ações no controlador usem o roteamento de atributo.

[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 da URL /products pode corresponder a ProductsApi.ListProducts
  • O caminho da URL /products/5 pode corresponder a ProductsApi.GetProduct(int).

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

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 atributos [Route] no código anterior:

Atributo Combina com [Route("Home")] Define o modelo de rota
[Route("")] Sim "Home"
[Route("Index")] Sim "Home/Index"
[Route("/")] Não ""
[Route("About")] Sim "Home/About"

Ordem de rota de atributo

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 ordenação ideal.
  • As rotas mais específicas têm a chance de ser 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 rota blog/search/{topic} tem prioridade mais alta, 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 um pedido usando a propriedade Order. Todos os atributos de rota fornecidos pela estrutura incluem Order. As rotas são processadas segundo uma classificação crescente da propriedade Order. A ordem padrão é 0. A definição de uma rota usando Order = -1 é executada antes das rotas que não definem uma ordem. A definição de uma rota usando Order = 1 é executada após a ordem de rota padrão.

Evite depender de Order. Se o espaço 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 por atributos seleciona a rota correta com correspondência de URL. Se a ordem padrão usada para a geração de URLs não estiver funcionando, usar um nome de rota como uma substituição geralmente é mais simples do que aplicar a propriedade Order.

Considere os dois controladores a seguir que definem a correspondência de rota /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);
    }
}

A solicitação /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 HomeController.Index ponto de extremidade. Para acessar o MyDemoController.MyIndex, solicite /home/MyIndex. Observação:

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

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

Em alguns casos, um erro HTTP 500 é retornado com rotas ambíguas. Use o registro em log para ver quais pontos de extremidade causaram o AmbiguousMatchException.

Substituição de token em modelos de rota [controlador], [ação], [área]

Para conveniência, as rotas de atributo dão suporte à substituição de token colocando um token entre chaves quadradas ([, ]). Os tokens [action], [area] e [controller] são substituídos pelos valores do nome da ação, do nome da área e do nome do controlador da ação em que a rota é 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();
}
  • Correspondências /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Correspondências /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 neste problema de discussão do GitHub se quiser ver os comentários de código em seu idioma nativo.

Rotas de atributo também podem ser combinadas com herança. Isso é poderoso quando combinado com a substituição de tokens. 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 ao delimitador de substituição de token literal [ ou ], faça seu escape repetindo o caractere ([[ ou ]]).

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

A substituição do token pode ser personalizada usando um transformador de parâmetro. Um transformador de parâmetro implementa IOutboundParameterTransformer e transforma o valor dos parâmetros. Por exemplo, um transformador de parâmetro SlugifyParameterTransformer personalizado muda 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();
    }
}

O RouteTokenTransformerConvention é uma convenção de modelo de aplicativo que:

  • Aplica um transformador de parâmetro a todas as rotas de atributo em um aplicativo.
  • Personaliza os valores de token de rota de atributo conforme eles 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 da Web do MDN no Slug para obter a definição de Slug.

Aviso

Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para RegularExpressions, causando um ataque de negação de serviço. APIs ASP.NET Core Framework que usam RegularExpressions passam um tempo limite.

Várias rotas de atributo

O roteamento de atributo dá suporte à definição de várias rotas que atingem a mesma ação. O uso mais comum desse recurso é para simular 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 se 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 de verbo HTTP implementam IActionConstraint.

Quando vários atributos de rota que implementam IActionConstraint são colocados em 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 aplicativo básico e bem definido. Use várias rotas em ações somente quando for necessário; por exemplo, para dar suporte a clientes existentes.

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

Rotas de atributo dão suporte à 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 somente por caminhos de URL como /product14/3. A parte do modelo de rota {id:int} 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 runtime 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.

Implemente IRouteTemplateProvider para definir atributos de rota personalizados. Cada IRouteTemplateProvider permite definir uma única rota com um nome, uma ordem e um modelo de rota personalizado:

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 Get anterior retorna Order = 2, Template = api/MyTestApi.

Usar o modelo de aplicativo para personalizar rotas de atributo

O modelo de aplicativo:

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

O modelo do aplicativo inclui todos os dados coletados dos atributos da rota. Os dados de atributos de rota são fornecidos pela IRouteTemplateProvider implementação. Convenções:

  • Pode ser gravado para modificar o modelo de aplicativo para personalizar o comportamento do roteamento.
  • 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 torna as rotas aproximadamente alinhadas 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 convenção namespace seja aplicada a controladores que são roteado por atributo:

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 fará nada se o controlador for roteado pelo atributo.
  • Define o modelo de controladores com base no 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 atributo versus roteamento convencional

Os aplicativos ASP.NET Core 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 segundo os atributos. Colocar uma rota no controlador ou na ação faz com que ela seja roteada segundo o atributo. Ações que definem rotas de atributo não podem ser acessadas por meio das rotas convencionais e vice-versa. Qualquer atributo de rota no controlador faz com que todas as ações no atributo de controlador sejam roteadas.

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

Geração de URL e valores de ambiente

Os aplicativos podem usar os recursos de geração de URL de roteamento para gerar links de URL para ações. A geração de URLs elimina a codificação de URLs, tornando o código mais robusto e de fácil manutenção. Esta seção se concentra nos recursos de geração de URL fornecidos pelo MVC e aborda apenas os aspectos básicos do funcionamento da geração de URL. Consulte Roteamento para obter uma descrição detalhada da geração de URL.

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

No exemplo a seguir, a interface IUrlHelper é usada por meio da propriedade Controller.Url para gerar um 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 o aplicativo estiver usando a rota convencional padrão, o valor da variável url será a cadeia de caracteres do caminho do URL /UrlGeneration/Destination. Esse caminho de URL é criado pelo roteamento combinando:

  • Os valores de rota da solicitação atual, que são chamados valores de ambiente.
  • Os valores passados para Url.Action e substituindo esses 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 por nomes correspondentes com os valores e os valores de ambiente. Um parâmetro de rota que não tem um valor pode:

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

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

O exemplo anterior de Url.Action pressupõe o roteamento convencional. A geração de URL funciona de forma semelhante ao roteamento de atributos, embora os conceitos sejam diferentes. Com o 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 correspondentes ao roteamento seguem uma convenção.

O exemplo a seguir usa o roteamento de atributo:

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 uma alternativa a IUrlHelper. LinkGenerator oferece funcionalidade semelhante, mas mais flexível. Cada método em IUrlHelper também tem uma família correspondente de métodos LinkGenerator.

Gerando URLs pelo nome da ação

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

Ao usar Url.Action, os valores de rota atuais para controller e action são fornecidos pelo runtime:

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

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

  • 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 adicionado:

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

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

  • Ambos os valores são ignorados { c = Carol, d = David }.
  • 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.

Talvez você espere atingir esse problema com a rota padrão {controller}/{action}/{id?}. Esse problema é raro na prática porque Url.Action sempre especifica explicitamente um controller valor e action .

Várias sobrecargas de Url.Action recebem 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 é usado com frequência com 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).

Qualquer valor de rota adicional que não corresponder aos parâmetros de rota será colocado 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 um URL passando o nome do controlador e 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 da 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 arquivo a seguir Razor 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 um URL e aceitam argumentos semelhantes. O complementos Url.RouteUrl para HtmlHelper são Html.BeginRouteForm e Html.RouteLink, que têm uma funcionalidade semelhante.

TagHelpers geram URLs por meio do TagHelper form e do TagHelper <a>. Ambos usam IUrlHelper para sua implementação. Consulte Auxiliares de marcação em formulários para obter mais informações.

Nos modos de exibição, o IUrlHelper está disponível por meio da propriedade Url para qualquer geração de URL ad hoc não abordada acima.

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 um URL como parte do resultado de uma ação.

As classes base ControllerBase 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 é para redirecionar após 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 de resultados de ação, como RedirectToAction e CreatedAtAction, seguem um padrão semelhante ao dos 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 denominada 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ê poderia imaginar que os valores de rota { controller = Home, action = Index } 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 impeça que a rota seja muito ambiciosa com a geração de URLs. Nesse caso, os valores padrão são { controller = Blog, action = Article } e nem controller ou action aparece como um parâmetro 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 a { controller = Blog, action = Article }. O roteamento, então, faz o fallback para tentar default, que é bem-sucedido.

Áreas

As áreas são um recurso do MVC usado para organizar funcionalidades relacionadas em um grupo separado:

  • Namespace de roteamento para ações do controlador.
  • Estrutura de pastas para exibições.

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

O exemplo a seguir configura o MVC para usar a rota convencional padrão e uma rota area para um area chamado 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 "blog_route" rota gera os valores de rota { area = Blog, controller = Users, action = AddUser }. O area valor da rota é produzido por um valor padrão para area. A rota criada por MapAreaControllerRoute é equivalente à 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 a restrição para area usando o nome da área fornecido, nesse 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 antes, pois são mais específicas do que as rotas sem área.

Usando o exemplo anterior, os valores de rota { area = Blog, controller = Users, action = AddUser } 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. Esse controlador está na área Blog. Os controladores sem um atributo [Area] não são membros de nenhuma área e não correspondem quando o valor da rota area é fornecido pelo roteamento. No exemplo a seguir, somente 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 fins de integridade. Se os controladores anteriores usassem o mesmo namespace, um erro do compilador seria gerado. Namespaces de classe não têm efeito sobre o roteamento do MVC.

Os primeiros dois controladores são membros de áreas e correspondem somente quando seus respectivos nomes de área são fornecidos pelo valor de rota area. O terceiro controlador não é um membro de nenhuma área e só pode corresponder quando nenhum valor para area for fornecido pelo roteamento.

Em termos de não corresponder a nenhum valor, a ausência do valor de area é equivalente ao valor de area ser nulo ou uma cadeia de caracteres vazia.

Ao executar uma ação dentro de uma área, o valor de rota para area está disponível como um valor de ambiente para o roteamento a ser usado na geração de URL. Isso significa que, por padrão, as áreas atuam como se fossem autoadesivas para a geração de URL, como demonstrado no 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 um 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 de ação

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

Código de exemplo

Depurar diagnóstico

Para obter a 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"
    }
  }
}