Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Por Ryan Nowak, Kirk Larkin e Rick Anderson
Note
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Warning
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
O roteamento é responsável por corresponder às solicitações HTTP de entrada e enviar essas solicitações para os pontos de extremidade executáveis do aplicativo. Os pontos de extremidade são as unidades de código executável de tratamento de solicitações do aplicativo. Os pontos de extremidade são definidos na aplicação e configurados quando a aplicação é iniciada. O processo de correspondência de ponto de extremidade pode extrair valores da URL da solicitação e fornecer esses valores para processamento de solicitação. Usando informações de endpoint do aplicativo, o roteamento também é capaz de gerar URLs que correspondem a endpoints.
Os aplicativos podem configurar o roteamento usando:
- Controllers
- Razor Páginas
- SignalR
- Serviços gRPC
- Middleware compatível com endpoint, como Verificações de Integridade.
- Delegados e lambdas registados com encaminhamento.
Este artigo aborda detalhes de baixo nível do roteamento ASP.NET Core. Para obter informações sobre como configurar o roteamento:
- Para controladores, consulte Roteamento para ações do controlador no ASP.NET Core.
- Para Razor convenções do Pages, consulte Razor Rotas de páginas e convenções de aplicativos no ASP.NET Core.
- Para obter Blazor orientação de roteamento, que adiciona ou substitui a orientação deste artigo, consulte roteamento e navegação do ASP.NET CoreBlazor.
Noções básicas de roteamento
O código a seguir mostra um exemplo básico de roteamento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O exemplo anterior inclui um único ponto de extremidade usando o MapGet método:
- Quando uma solicitação HTTP
GETé enviada para a URL/raiz :- O delegado da solicitação realiza a execução.
-
Hello World!é escrito na resposta HTTP.
- Se o método de solicitação não for
GETou a URL raiz não for/, nenhuma rota corresponderá e um HTTP 404 será retornado.
O roteamento utiliza um par de middleware, configurado por UseRouting e UseEndpoints:
-
UseRoutingAdiciona mapeamento de rotas ao pipeline de middleware. Esse middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência com base na solicitação. -
UseEndpointsAdiciona a execução de endpoints ao pipeline de middleware. Executa o delegado associado ao ponto final selecionado.
Normalmente, as aplicações não necessitam de chamar UseRouting ou UseEndpoints.
WebApplicationBuilder configura um pipeline de middleware que envolve o middleware adicionado em Program.cs com UseRouting e UseEndpoints. No entanto, os aplicativos podem alterar a ordem em que UseRouting e UseEndpoints executar chamando esses métodos explicitamente. Por exemplo, o código a seguir faz uma chamada explícita para UseRouting:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
No código anterior:
- A chamada para
app.Useimplementa um middleware personalizado que inicia o processo no início do pipeline. - A chamada para
UseRoutingconfigura o middleware de correspondência de rota para ser executado depois de o middleware personalizado. - O ponto de extremidade registrado com
MapGeté executado no final do pipeline.
Se o exemplo anterior não incluísse uma chamada para UseRouting, o middleware personalizado seria executado após o middleware correspondente à rota.
Nota: Rotas adicionadas diretamente ao WebApplication são executadas no final do pipeline.
Endpoints
O MapGet método é usado para definir um ponto de extremidade. Um endpoint é algo que pode ser:
- Selecionado, combinando o URL e o método HTTP.
- Executado, ao correr o delegado.
Os pontos de extremidade que podem ser correspondidos e executados pela aplicação são configurados em UseEndpoints. Por exemplo, MapGet, MapPost, e métodos semelhantes conectam delegados de solicitação ao sistema de roteamento. Métodos adicionais podem ser usados para conectar os recursos do framework ASP.NET Core ao sistema de roteamento.
- MapRazorPages para Razor Páginas
- MapControllers para controladores
- MapHub<THub> para SignalR
- MapGrpcService<TService> para gRPC
O exemplo a seguir mostra o roteamento com um modelo de rota mais sofisticado:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
A cadeia de caracteres /hello/{name:alpha} é um modelo de rota. Um modelo de rota é usado para configurar como o ponto de extremidade é correspondido. Neste caso, o modelo corresponde:
- Um URL como
/hello/Docs - Qualquer caminho de URL que comece com
/hello/seguido por uma sequência de caracteres alfabéticos.:alphaAplica uma restrição de rota que corresponde apenas a caracteres alfabéticos. As restrições de rota são explicadas mais adiante neste artigo.
O segundo segmento do caminho da URL, {name:alpha}:
- Está vinculado ao
nameparâmetro. - É capturado e armazenado em HttpRequest.RouteValues.
O exemplo a seguir mostra o roteamento com verificações de integridade e autorização:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
O exemplo anterior demonstra como:
- O middleware de autorização pode ser usado com roteamento.
- Os pontos de extremidade podem ser usados para configurar o comportamento de autorização.
A MapHealthChecks chamada adiciona um endpoint de verificação de saúde. O encadeamento de RequireAuthorization nesta chamada anexa uma política de autorização ao endpoint.
Chamando UseAuthentication e UseAuthorization adiciona o middleware de autenticação e autorização. Esses middleware são colocados entre UseRouting e UseEndpoints para que possam:
- Veja qual ponto de extremidade foi selecionado pela
UseRouting. - Aplique uma política de autorização antes de UseEndpoints enviar para o destino.
Metadados do ponto final
No exemplo anterior, há dois pontos de extremidade, mas apenas o ponto de extremidade de verificação de integridade tem uma política de autorização anexada. Se a solicitação corresponder ao ponto de extremidade de verificação de integridade, /healthz, uma verificação de autorização será executada. Isso demonstra que os pontos de extremidade podem ter dados extras anexados a eles. Esses dados extra são chamados metadados de ponto final:
- Os metadados podem ser processados por middleware com reconhecimento de roteamento.
- Os metadados podem ser de qualquer tipo .NET.
Conceitos de roteamento
O sistema de roteamento se baseia no pipeline de middleware adicionando o poderoso conceito de ponto final . Os pontos de extremidade representam unidades da funcionalidade do aplicativo que são distintas entre si em termos de roteamento, autorização e dos vários sistemas do ASP.NET Core.
Definição de Endpoint no ASP.NET Core
Um ponto de extremidade ASP.NET Core é:
- Executável: Possui um RequestDelegate.
- Extensível: Tem uma coleção de metadados .
- Selecionável: Opcionalmente, tem informações de roteamento.
- Enumerável: A coleção de pontos de extremidade pode ser listada ao recuperar o EndpointDataSource de DI.
O código a seguir mostra como recuperar e inspecionar o ponto de extremidade correspondente à solicitação atual:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
O ponto de extremidade, se selecionado, pode ser obtido a partir do HttpContext. Suas propriedades podem ser inspecionadas. Os objetos de ponto de extremidade são imutáveis e não podem ser modificados após a criação. O tipo mais comum de ponto de extremidade é o RouteEndpoint.
RouteEndpoint Inclui informações que permitem que ele seja selecionado pelo sistema de roteamento.
No código anterior, app. Use configura um middleware embutido.
O código a seguir mostra que, dependendo de onde app.Use é chamado no pipeline, pode não haver um ponto de extremidade:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
O exemplo anterior adiciona Console.WriteLine instruções que exibem se um ponto de extremidade foi selecionado ou não. Para maior clareza, o exemplo atribui um nome de exibição ao ponto de extremidade fornecido / .
O exemplo anterior também inclui chamadas para UseRouting e UseEndpoints para controlar exatamente quando esses middleware são executados dentro do pipeline.
Executar este código com um URL de / exibe:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Executar este código com qualquer outro URL exibe:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Esta saída demonstra que:
- O ponto de extremidade é sempre nulo antes
UseRoutingde ser chamado. - Se uma correspondência for encontrada, o ponto de extremidade não será nulo entre
UseRoutinge UseEndpoints. - O
UseEndpointsmiddleware é terminal quando uma correspondência é encontrada. O middleware do terminal é definido posteriormente neste artigo. - O middleware após
UseEndpointsexecuta somente quando nenhuma correspondência é encontrada.
O UseRouting middleware utiliza o método SetEndpoint para anexar o endpoint ao contexto atual. É possível substituir o UseRouting middleware por lógica personalizada e ainda obter os benefícios do uso de endpoints. Os pontos de extremidade são uma primitiva de baixo nível, como o middleware, e não são acoplados à implementação de roteamento. A maioria dos aplicativos não precisa substituir UseRouting por lógica personalizada.
O UseEndpoints middleware foi projetado para ser usado em conjunto com o UseRouting middleware. A lógica principal para executar um endpoint é simples. Utilize GetEndpoint para recuperar o ponto de extremidade e, em seguida, invoque a propriedade RequestDelegate.
O código a seguir demonstra como o middleware pode influenciar ou reagir ao roteamento:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
O exemplo anterior demonstra dois conceitos importantes:
- O middleware pode ser executado antes
UseRoutingpara modificar os dados nos quais o roteamento opera.- Normalmente, o middleware que aparece antes do roteamento modifica alguma propriedade da solicitação, como UseRewriter, UseHttpMethodOverrideou UsePathBase.
- O middleware pode ser executado entre
UseRoutinge UseEndpoints para processar os resultados do roteamento antes que o ponto de extremidade seja executado.- Middleware executado entre
UseRoutingeUseEndpoints:- Geralmente inspeciona metadados para entender os endereços finais.
- Muitas vezes toma decisões de segurança, como fazem
UseAuthorizationeUseCors.
- A combinação de middleware e metadados permite configurar políticas por endpoint.
- Middleware executado entre
O código anterior mostra um exemplo de um middleware personalizado que oferece suporte a políticas por ponto de extremidade. O middleware grava um log de auditoria de acesso a dados confidenciais no console. O middleware pode ser configurado para auditar um endpoint com os RequiresAuditAttribute metadados. Este exemplo demonstra um padrão de aceitação em que apenas os pontos de extremidade marcados como confidenciais são auditados. É possível definir essa lógica ao contrário, auditando tudo o que não está marcado como seguro, por exemplo. O sistema de metadados do endpoint é flexível. Esta lógica pode ser desenhada de qualquer forma que se adeque ao caso de uso.
O código de exemplo anterior destina-se a demonstrar os conceitos básicos de pontos de extremidade. A amostra não se destina a utilização em produção. Uma versão mais completa de um middleware de registo de auditoria seria:
- Faça login em um arquivo ou banco de dados.
- Inclua detalhes como o utilizador, endereço IP, nome do ponto de extremidade confidencial e muito mais.
Os metadados RequiresAuditAttribute da política de auditoria são definidos como um Attribute para facilitar o uso com estruturas baseadas em classe, como controladores e SignalR. Ao usar caminho para codificar:
- Os metadados são anexados a uma API do construtor.
- As frameworks baseados em classe incluem todos os atributos no método e na classe correspondentes ao criar endpoints.
As práticas recomendadas para tipos de metadados são defini-los como interfaces ou atributos. Interfaces e atributos permitem a reutilização de código. O sistema de metadados é flexível e não impõe quaisquer limitações.
Compare o middleware do terminal com o roteamento
O exemplo a seguir demonstra o middleware e o roteamento do terminal:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
O estilo de middleware mostrado com Approach 1: é middleware terminal. Denomina-se middleware de terminal porque realiza uma operação de correspondência:
- A operação de correspondência no exemplo anterior é
Path == "/"para o middleware ePath == "/Routing"para o roteamento. - Quando uma correspondência é bem-sucedida, executa uma funcionalidade e retorna, em vez de invocar o middleware
next.
É chamado de middleware de terminal porque encerra a pesquisa, executa algumas funcionalidades e, em seguida, retorna.
A lista a seguir compara o middleware do terminal com o roteamento:
- Ambas as abordagens permitem finalizar o fluxo de processamento.
- O middleware encerra o pipeline retornando em vez de invocar
next. - Os endpoints são sempre terminais.
- O middleware encerra o pipeline retornando em vez de invocar
- O middleware do terminal permite posicionar o middleware em um local arbitrário no pipeline:
- Os pontos de extremidade são executados na posição de UseEndpoints.
- O middleware do terminal permite que o código arbitrário determine quando o middleware corresponde:
- O código personalizado para correspondência de rotas pode ser extenso e difícil de escrever corretamente.
- O roteamento fornece soluções diretas para aplicativos típicos. A maioria das aplicações não requer código de correspondência de rota personalizado.
- Os endpoints interagem com o middleware, como
UseAuthorizationeUseCors.- Usar um middleware de terminal com
UseAuthorizationouUseCorsrequer interface manual com o sistema de autorização.
- Usar um middleware de terminal com
Um ponto de extremidade define ambos:
- Um delegado para processar solicitações.
- Uma coleção de metadados arbitrários. Os metadados são utilizados para implementar preocupações abrangentes com base em políticas e configurações anexadas a cada endpoint.
O middleware de terminal pode ser uma ferramenta eficaz, mas pode exigir:
- Uma quantidade significativa de codificação e testes.
- Integração manual com outros sistemas para alcançar o nível desejado de flexibilidade.
Considere a integração com o roteamento antes de escrever um middleware de terminal.
Middleware de terminal existente que se integra com o Map ou MapWhen geralmente pode ser transformado em um ponto de extremidade com reconhecimento de roteamento. MapHealthChecks demonstra o padrão para router-ware:
- Escreva um método de extensão em IEndpointRouteBuilder.
- Crie um pipeline de middleware aninhado usando CreateApplicationBuilder.
- Anexe o middleware ao novo pipeline. Neste caso, UseHealthChecks.
- Build o pipeline de middleware em RequestDelegate.
- Chame
Mape forneça o novo pipeline de middleware. - Retorne o objeto builder fornecido pelo método de extensão
Map.
O código a seguir mostra o uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
O exemplo anterior mostra por que retornar o objeto construtor é importante. Permitir que o objeto builder seja retornado para o desenvolvedor da aplicação possibilita a configuração de políticas, como autorização, para o ponto de extremidade. Neste exemplo, o middleware de verificações de integridade não tem integração direta com o sistema de autorização.
O sistema de metadados foi criado em resposta aos problemas encontrados pelos autores de extensibilidade usando middleware de terminal. É problemático para cada middleware implementar sua própria integração com o sistema de autorização.
Correspondência de URL
- É o processo pelo qual o encaminhamento associa uma solicitação de entrada a um ponto de extremidade.
- Baseia-se em dados no caminho e cabeçalhos da URL.
- Pode ser estendido para considerar quaisquer dados na solicitação.
Quando um middleware de roteamento é executado, ele define um Endpoint e uns valores de rota para um recurso de solicitação na HttpContext da solicitação atual.
- Chamar HttpContext.GetEndpoint obtém o ponto de extremidade.
-
HttpRequest.RouteValuesObtém a coleção de valores de rota.
O middleware que corre após o middleware de encaminhamento pode inspecionar o endpoint e tomar medidas. Por exemplo, um middleware de autorização pode interrogar os metadados do endpoint para uma política de autorização. Depois de todo o middleware na linha de processamento de solicitação ser executado, o delegado do ponto de extremidade selecionado é invocado.
O sistema de roteamento no endpoint é responsável por todas as decisões de despacho. Como o middleware aplica políticas com base no ponto de extremidade selecionado, é importante que:
- Qualquer decisão que possa afetar o despacho ou a aplicação de políticas de segurança é tomada dentro do sistema de roteamento.
Warning
Para compatibilidade com versões anteriores, quando um delegado de ponto de extremidade Controller ou Razor Pages é executado, as propriedades de RouteContext.RouteData são definidas como valores apropriados com base no processamento da solicitação realizado até agora.
O RouteContext tipo será marcado como obsoleto em uma versão futura:
- Migrar
RouteData.ValuesparaHttpRequest.RouteValues. - Migre
RouteData.DataTokenspara recuperar IDataTokensMetadata dos metadados do ponto final.
A correspondência de URL opera em um conjunto configurável de fases. Em cada fase, a saída é um conjunto de correspondências. O conjunto de jogos pode ser reduzido ainda mais na próxima fase. A implementação de roteamento não garante uma ordem de processamento para pontos de extremidade correspondentes. Todas as correspondências possíveis são processadas de uma só vez. As fases de correspondência de URL ocorrem na seguinte ordem. ASP.NET Núcleo:
- Processa o caminho da URL em relação ao conjunto de pontos de extremidade e seus modelos de rota, coletando todas as correspondências.
- Pega a lista anterior e remove correspondências que falham com restrições de rota aplicadas.
- Pega a lista anterior e remove itens que não correspondem ao conjunto de instâncias MatcherPolicy.
- Usa o EndpointSelector para tomar uma decisão final da lista anterior.
A lista de pontos finais é priorizada de acordo com:
Todos os pontos de extremidade correspondentes são processados em cada fase até que o EndpointSelector seja alcançado. A EndpointSelector é a fase final. Ele escolhe o ponto final de prioridade mais alta entre as correspondências como a melhor opção. Se houver outras partidas com a mesma prioridade da melhor partida, uma exceção de partida ambígua é lançada.
A precedência de rota é calculada com base em um modelo de rota mais específico recebendo uma prioridade mais alta. Por exemplo, considere os modelos /hello e /{message}:
- Ambos correspondem ao caminho da URL
/hello. -
/helloé mais específica e, por conseguinte, mais prioritária.
Em geral, a precedência de rota faz um bom trabalho ao escolher a melhor opção para os tipos de esquemas de URL usados na prática. Use Order apenas quando necessário para evitar uma ambiguidade.
Devido aos tipos de extensibilidade fornecidos pelo roteamento, não é possível para o sistema de roteamento calcular antecipadamente as rotas ambíguas. Considere um exemplo como os modelos de rota /{message:alpha} e /{message:int}:
- A
alpharestrição corresponde apenas a caracteres alfabéticos. - A
intrestrição corresponde apenas a números. - Esses modelos têm a mesma precedência de rota, mas não há um único URL com o qual ambos correspondam.
- Se o sistema de roteamento relatasse um erro de ambiguidade na inicialização, bloquearia esse caso de uso válido.
Warning
A ordem das operações internas UseEndpoints não influencia o comportamento do roteamento, com uma exceção. MapControllerRoute e MapAreaRoute atribuem automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. Isso simula o comportamento de longo prazo dos controladores sem que o sistema de roteamento forneça as mesmas garantias que as implementações de roteamento mais antigas.
Roteamento de ponto de extremidade no ASP.NET Core:
- Não tem o conceito de rotas.
- Não fornece garantias de encomenda. Todos os endpoints são processados de uma só vez.
Precedência do modelo de rota e ordem de seleção do ponto final
A precedência do modelo de rota é um sistema que atribui a cada modelo de rota um valor com base em quão específico ele é. Precedência do modelo de rota:
- Evita a necessidade de ajustar a ordem dos pontos finais em casos comuns.
- Tenta corresponder às expectativas de senso comum do comportamento de encaminhamento.
Por exemplo, considere modelos /Products/List e /Products/{id}. Seria razoável supor que /Products/List é uma correspondência melhor do que /Products/{id} para o caminho /Products/List da URL. Isso funciona porque o segmento /List literal é considerado como tendo melhor precedência do que o segmento /{id}de parâmetro.
Os detalhes de como a precedência funciona são acoplados à forma como os modelos de rota são definidos:
- Modelos com mais segmentos são considerados mais específicos.
- Um segmento com texto literal é considerado mais específico do que um segmento de parâmetro.
- Um segmento de parâmetro com uma restrição é considerado mais específico do que um sem.
- Um segmento complexo é considerado tão específico quanto um segmento de parâmetro com uma restrição.
- Os parâmetros "catch-all" são os menos específicos. Consulte catch-all na seção Modelos de rota para obter informações importantes sobre rotas catch-all.
Conceitos de geração de URL
Geração de URL:
- É o processo pelo qual o roteamento pode criar um caminho de URL com base em um conjunto de valores de rota.
- Permite uma separação lógica entre endpoints e os URLs que lhes acedem.
O roteamento de ponto de extremidade inclui a API LinkGenerator.
LinkGenerator é um serviço singleton disponível na DI. A LinkGenerator API pode ser usada fora do contexto de uma solicitação em execução. Mvc.IUrlHelper e cenários que dependem do IUrlHelper, como Tag Helpers, HTML Helpers e Action Results, usam a API internamente para fornecer recursos de geração de links.
O gerador de links é apoiado pelo conceito de um endereço e esquemas de endereço. Um esquema de endereços é uma forma de determinar os pontos finais que devem ser considerados para a geração de links. Por exemplo, os cenários de nome de rota e valores de rota com os quais muitos usuários estão familiarizados a partir de controladores e Razor Páginas são implementados como um esquema de endereço.
O gerador de links pode vincular a controladores e Razor páginas através dos seguintes métodos de extensão:
Sobrecargas desses métodos aceitam argumentos que incluem o HttpContext. Esses métodos são funcionalmente equivalentes a Url.Action e Url.Page, mas oferecem flexibilidade e opções adicionais.
Os GetPath* métodos são mais semelhantes a Url.Action e Url.Page, na medida em que geram um URI contendo um caminho absoluto. Os GetUri* métodos sempre geram um URI absoluto contendo um esquema e host. Os métodos que aceitam um HttpContext geram um URI no contexto da solicitação em execução. Os valores de rota ambiente , caminho base da URL, esquema e host da solicitação em execução são usados, a menos que sejam substituídos.
LinkGenerator é chamado com um endereço. A geração de um URI ocorre em duas etapas:
- Um endereço está associado a uma lista de terminais que correspondem ao endereço.
- Cada RoutePattern de ponto de extremidade é avaliado até que um padrão de rota que corresponda aos valores fornecidos seja encontrado. A saída resultante é combinada com as outras peças de URI fornecidas ao gerador de link e retornadas.
Os métodos fornecidos por LinkGenerator suportam capacidades padrão de geração de links para qualquer tipo de endereço. A maneira mais conveniente de usar o gerador de links é através de métodos de extensão que executam operações para um tipo de endereço específico:
| Método de extensão | Description |
|---|---|
| GetPathByAddress | Gera um URI com um caminho absoluto com base nos valores fornecidos. |
| GetUriByAddress | Gera um URI absoluto com base nos valores fornecidos. |
Warning
Preste atenção às seguintes implicações dos métodos de chamada LinkGenerator :
Use
GetUri*métodos de extensão com cuidado em uma configuração de aplicativo que não valida oHostcabeçalho das solicitações de entrada. Se o cabeçalhoHostdas solicitações de entrada não for validado, entradas de solicitações não confiáveis poderão ser enviadas de volta ao cliente em URIs em uma vista ou página. Recomendamos que todas as aplicações de produção configurem o seu servidor para validar o cabeçalho doHostcom valores válidos conhecidos.Use LinkGenerator com cuidado em middleware em combinação com
MapouMapWhen.Map*Altera o caminho base da solicitação em execução, o que afeta a saída da geração de link. Todas as LinkGenerator APIs permitem especificar um caminho base. Especifique um caminho base vazio para desfazer o efeito deMap*na geração de links.
Exemplo de middleware
No exemplo a seguir, um middleware usa a LinkGenerator API para criar um link para um método de ação que lista produtos de armazenamento. Ao usar o gerador de links, injetando-o numa classe e chamando GenerateLink, qualquer classe numa aplicação terá acesso:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Modelos de rotas
Os tokens dentro dos {} definem parâmetros de rota que são vinculados se houver uma correspondência na rota. Mais de um parâmetro de rota pode ser definido em um segmento de rota, mas os parâmetros de rota devem ser separados por um valor literal. Por exemplo:
{controller=Home}{action=Index}
não é uma rota válida, porque não há valor literal entre {controller} e {action}. Os parâmetros de rota devem ter um nome e podem ter atributos adicionais especificados.
O texto literal diferente dos parâmetros de rota (por exemplo, {id}) e o separador / de caminho devem corresponder ao texto na URL. A correspondência de texto é insensível a maiúsculas e minúsculas e baseia-se na representação decodificada do caminho da URL. Para corresponder a um delimitador literal de parâmetro de rota { ou }, escape o delimitador repetindo o caractere. Por exemplo {{ ou }}.
Asterisco * ou duplo asterisco **:
- Pode ser usado como um prefixo para um parâmetro de rota para vincular ao restante do URI.
- São chamados de parâmetros catch-all . Por exemplo:
blog/{**slug}- Corresponde a qualquer URI que começa com
blog/e tem qualquer valor após ele. - O valor seguinte
blog/é atribuído ao valor de rota slug.
- Corresponde a qualquer URI que começa com
Warning
Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:
- Uma rota abrangente, por exemplo,
{**slug}" - A rota de abrangência total não processa as solicitações que deveria processar.
- A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.
Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.
Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz escape dos caracteres apropriados quando a rota é utilizada para gerar uma URL, incluindo os caracteres / separadores de caminho. Por exemplo, a rota foo/{*path} com valores de rota { path = "my/path" } gera foo/my%2Fpath. Observe a barra para a frente escapada. Para separar caracteres de caminho de ida e volta, use o prefixo do parâmetro de rota **. A rota foo/{**path} com { path = "my/path" } gera foo/my/path.
Os padrões de URL que tentam capturar um nome de arquivo com uma extensão de arquivo opcional têm considerações adicionais. Por exemplo, considere o modelo files/{filename}.{ext?}. Quando os valores para ambos filename e ext existem, ambos os valores são preenchidos. Se houver apenas um valor para filename na URL, a rota corresponderá porque o . final é opcional. Os seguintes URLs correspondem a esta rota:
/files/myFile.txt/files/myFile
Os parâmetros de rota podem ter valores padrão designados especificando o valor padrão após o nome do parâmetro separado por um sinal de igual (=). Por exemplo, {controller=Home} define Home como o valor padrão para controller. O valor padrão será usado se nenhum valor estiver presente na URL do parâmetro. Os parâmetros de rota são tornados opcionais anexando um ponto de interrogação (?) ao final do nome do parâmetro. Por exemplo, id?. A diferença entre valores opcionais e parâmetros de rota padrão é:
- Um parâmetro de rota com um valor padrão sempre produz um valor.
- Um parâmetro opcional tem um valor somente quando um valor é fornecido pela URL da solicitação.
Os parâmetros de rota podem ter restrições que devem corresponder ao valor de rota ligado à URL. Adicionar : e o nome da restrição após o nome do parâmetro de rota especifica uma restrição embutida em um parâmetro de rota. Se a restrição exigir argumentos, eles serão colocados entre parênteses (...) após o nome da restrição. Várias restrições embutidas podem ser especificadas ao anexar outro nome de restrição.
O nome da restrição e os argumentos são passados para o serviço IInlineConstraintResolver para criar uma instância de IRouteConstraint a ser usada no processamento de URLs. Por exemplo, o modelo blog/{article:minlength(10)} de rota especifica uma minlength restrição com o argumento 10. Para obter mais informações sobre restrições de rota e uma lista das restrições fornecidas pela estrutura, consulte a seção Restrições de rota .
Os parâmetros de rota também podem ter transformadores de parâmetros. Os transformadores de parâmetros transformam o valor de um parâmetro ao gerar links e fazer a correspondência de ações e páginas com URLs. Como as restrições, os transformadores de parâmetros podem ser adicionados em linha a um parâmetro de rota adicionando um : e nome do transformador após o nome do parâmetro de rota. Por exemplo, o modelo blog/{article:slugify} de rota especifica um slugify transformador. Para obter mais informações sobre transformadores de parâmetro, consulte a seção Transformadores de parâmetro .
A tabela a seguir demonstra modelos de rota de exemplo e seu comportamento:
| Modelo de rota | Exemplo de URI correspondente | A URI do pedido… |
|---|---|---|
hello |
/hello |
Corresponde apenas ao único caminho /hello. |
{Page=Home} |
/ |
Combina e configura Page para Home. |
{Page=Home} |
/Contact |
Combina e configura Page para Contact. |
{controller}/{action}/{id?} |
/Products/List |
Mapeia para o Products controlador e List ação. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapeia para o Products controlador e a Details ação com id definido para 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapeia para o Home controlador e o Index método.
id é ignorado. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapeia para o Products controlador e o Index método.
id é ignorado. |
Usar um modelo é geralmente a abordagem mais simples para roteamento. Restrições e padrões também podem ser especificados fora do modelo de rota.
Segmentos complexos
Segmentos complexos são processados combinando delimitadores literais da direita para a esquerda de uma forma não gananciosa . Por exemplo, [Route("/a{b}c{d}")] é um segmento complexo.
Segmentos complexos funcionam de uma maneira particular que deve ser entendida para usá-los com sucesso. O exemplo nesta seção demonstra por que segmentos complexos só funcionam realmente bem quando o texto do delimitador não aparece dentro dos valores dos parâmetros. Usar um regex e, em seguida, extrair manualmente os valores é necessário para casos mais complexos.
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Este é um resumo das etapas que o roteamento executa com o modelo /a{b}c{d} e o caminho /abcdda URL. O | é usado para ajudar a visualizar como o algoritmo funciona:
- O primeiro literal, da direita para a esquerda, é
c. Assim/abcdé pesquisado a partir da direita e encontra/ab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/ab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Não há texto restante e nenhum modelo de rota restante, então esta é uma correspondência.
Aqui está um exemplo de um caso negativo usando o mesmo modelo /a{b}c{d} e o caminho da URL /aabcd. O | é usado para ajudar a visualizar como o algoritmo funciona. Este caso não é uma correspondência, o que é explicado pelo mesmo algoritmo:
- O primeiro literal, da direita para a esquerda, é
c. Assim/aabcdé pesquisado a partir da direita e encontra/aab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/aab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/a|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Neste ponto, há texto
arestante, mas o algoritmo ficou sem modelo de rota para analisar, então isso não é uma correspondência.
Uma vez que o algoritmo de correspondência não é ganancioso:
- Ele corresponde à menor quantidade de texto possível em cada etapa.
- Qualquer caso em que o valor do delimitador apareça dentro dos valores dos parâmetros resulta em não correspondência.
As expressões regulares fornecem muito mais controle sobre seu comportamento correspondente.
A correspondência gananciosa, também conhecida como correspondência máxima, tenta encontrar a correspondência mais longa possível no texto de entrada que satisfaça o padrão regex. A correspondência não gananciosa, também conhecida como correspondência preguiçosa, busca a correspondência mais curta possível no texto de entrada que satisfaça o padrão regex.
Roteamento com caracteres especiais
O roteamento com caracteres especiais pode levar a resultados inesperados. Por exemplo, considere um controlador com o seguinte método de ação:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Quando string id contém os seguintes valores codificados, podem ocorrer resultados inesperados:
| ASCII | Encoded |
|---|---|
/ |
%2F |
|
+ |
Os parâmetros de rota nem sempre são decodificados por URL. Este problema poderá ser resolvido no futuro. Para obter mais informações, consulte este problema do GitHub;
Restrições de rota
As restrições de rota são executadas quando ocorre uma correspondência com a URL de entrada e o caminho da URL é tokenizado em valores de rota. As restrições de rota geralmente inspecionam o valor de rota associado por meio do modelo de rota e tomam uma decisão verdadeira ou falsa sobre se o valor é aceitável. Algumas restrições de rota usam dados fora do valor da rota para considerar se a solicitação pode ser roteada. Por exemplo, o HttpMethodRouteConstraint pode aceitar ou rejeitar uma solicitação com base no seu verbo HTTP. As restrições são usadas em solicitações de roteamento e geração de links.
Warning
Não use restrições para validação de entrada. Se as restrições forem usadas para validação de entrada, a entrada inválida resultará em uma 404 resposta Não encontrada. A entrada inválida deve produzir uma 400 solicitação incorreta com uma mensagem de erro apropriada. As restrições de rota são usadas para desambiguar rotas semelhantes, não para validar as entradas para uma rota específica.
A tabela a seguir demonstra as restrições de rota padrão e seu comportamento esperado:
| restrição | Example | Exemplos de combinações | Notes |
|---|---|---|---|
int |
{id:int} |
123456789, -123456789 |
Corresponde a qualquer número inteiro |
bool |
{active:bool} |
true, FALSE |
Combina true ou false. Case-insensitive |
datetime |
{dob:datetime} |
2016-12-31, 2016-12-31 7:32pm |
Corresponde a um valor válido DateTime na cultura invariante. Ver advertência anterior |
decimal |
{price:decimal} |
49.99, -1,000.01 |
Corresponde a um valor válido decimal na cultura invariante. Ver advertência anterior |
double |
{weight:double} |
1.234, -1,001.01e8 |
Corresponde a um valor válido double na cultura invariante. Ver advertência anterior |
float |
{weight:float} |
1.234, -1,001.01e8 |
Corresponde a um valor válido float na cultura invariante. Ver advertência anterior |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Corresponde a um valor válido Guid |
long |
{ticks:long} |
123456789, -123456789 |
Corresponde a um valor válido long |
minlength(value) |
{username:minlength(4)} |
Rick |
String deve ter pelo menos 4 caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
String não deve ter mais de 8 caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
A cadeia de caracteres deve ter exatamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
A cadeia de caracteres deve ter no mínimo 8 e não mais de 16 caracteres |
min(value) |
{age:min(18)} |
19 |
O valor inteiro deve ser pelo menos 18 |
max(value) |
{age:max(120)} |
91 |
O valor inteiro não deve ser superior a 120 |
range(min,max) |
{age:range(18,120)} |
91 |
O valor inteiro deve ser pelo menos 18, mas não superior a 120 |
alpha |
{name:alpha} |
Rick |
A cadeia de caracteres deve consistir em um ou mais caracteres alfabéticos a-z sem distinção entre maiúsculas e minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
String deve corresponder à expressão regular. Veja dicas sobre como definir uma expressão regular |
required |
{name:required} |
Rick |
Usado para impor que um valor não parâmetro esteja presente durante a geração de URL |
file |
{filename:file} |
myfile.txt |
String pode conter segmentos de caminho, mas seu último segmento deve ter um ponto (.) e ser seguido por um ou mais caracteres que não sejam pontos |
nonfile |
{page:nonfile} |
PageName |
String não deve ter um ponto em seu último segmento de caminho que é seguido por um ou mais caracteres não-ponto (.) |
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Várias restrições delimitadas por dois pontos podem ser aplicadas a um único parâmetro. Por exemplo, a restrição a seguir restringe um parâmetro a um valor inteiro igual ou superior a 1:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warning
As restrições de rota que verificam a URL e são convertidas para um tipo CLR sempre usam a cultura invariante. Por exemplo, a conversão para o tipo CLR int ou DateTime. Essas restrições pressupõem que a URL não é localizável. As restrições de rota fornecidas pela estrutura não modificam os valores armazenados nos valores de rota. Todos os valores de rota analisados a partir da URL são armazenados como cadeias de caracteres. Por exemplo, a float restrição tenta converter o valor da rota em um float, mas o valor convertido é usado apenas para verificar se ele pode ser convertido em float.
Expressões regulares em restrições
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Expressões regulares podem ser especificadas como restrições embutidas usando a regex(...) restrição de rota. Os métodos na MapControllerRoute família também aceitam um objeto literal de restrições. Se esse formulário for usado, os valores de cadeia de caracteres serão interpretados como expressões regulares.
O código a seguir usa uma restrição de regex embutida:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
O código a seguir usa um literal de objeto para especificar uma restrição de regex:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
A estrutura ASP.NET Core adiciona RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão regular. Consulte RegexOptions para obter uma descrição desses membros.
As expressões regulares usam delimitadores e tokens semelhantes aos usados pelo roteamento e pela linguagem C#. Os tokens de expressão regular precisam de ser escapados. Para usar a expressão regular ^\d{3}-\d{2}-\d{4}$ numa restrição inline, use uma das seguintes opções:
- Substitua os caracteres
\fornecidos na cadeia de caracteres por caracteres\\no arquivo de origem C# para fazer o escape do caractere de escape da cadeia de caracteres\. - Literais de string verbatim.
Para escapar dos caracteres delimitadores do parâmetro de roteamento {, }, [, ], dobre os caracteres na expressão, por exemplo, {{, }}, [[, ]]. A tabela a seguir mostra uma expressão regular e sua versão com escape:
| Expressão regular | Expressão regular escapada |
|---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
As expressões regulares usadas no roteamento geralmente começam com o ^ caractere e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente terminam com o $ caractere e correspondem ao final da cadeia de caracteres. Os ^ caracteres e $ garantem que a expressão regular corresponda ao valor do parâmetro route inteiro. Sem os ^ caracteres e $ , a expressão regular corresponde a qualquer substring dentro da cadeia de caracteres, o que geralmente é indesejável. A tabela a seguir fornece exemplos e explica por que eles correspondem ou não correspondem:
| Expression | String | Match | Comment |
|---|---|---|---|
[a-z]{2} |
hello | Yes | Correspondências de substring |
[a-z]{2} |
123abc456 | Yes | Correspondências de substring |
[a-z]{2} |
mz | Yes | Expressão de correspondências |
[a-z]{2} |
MZ | Yes | Não diferencia maiúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Ver ^ e $ acima |
^[a-z]{2}$ |
123abc456 | No | Ver ^ e $ acima |
Para obter mais informações sobre sintaxe de expressão regular, consulte Expressões regulares do .NET Framework.
Para restringir um parâmetro a um conjunto conhecido de valores possíveis, use uma expressão regular. Por exemplo, {action:regex(^(list|get|create)$)} só corresponde ao valor da action rota para list, get, ou create. Se passada para o dicionário de restrições, a cadeia de caracteres ^(list|get|create)$ é equivalente. As restrições passadas no dicionário de restrições que não correspondem a uma das restrições conhecidas também são tratadas como expressões regulares. As restrições passadas dentro de um modelo que não correspondem a uma das restrições conhecidas não são tratadas como expressões regulares.
Restrições de rota personalizadas
Restrições de rota personalizadas podem ser criadas implementando a IRouteConstraint interface. A IRouteConstraint interface contém Match, que retorna true se a restrição for satisfeita e false de outra forma.
Restrições de rota personalizadas raramente são necessárias. Antes de implementar uma restrição de rota personalizada, considere alternativas, como vinculação de modelo.
A pasta ASP.NET Core Constraints fornece bons exemplos de criação de restrições. Por exemplo, GuidRouteConstraint.
Para utilizar um IRouteConstraint personalizado, o tipo de restrição de rota deve ser registado com o ConstraintMap da aplicação no contêiner de serviços. A ConstraintMap é um dicionário que mapeia chaves de restrição de rota para as implementações IRouteConstraint que validam essas restrições.
ConstraintMap de um aplicativo pode ser atualizado em Program.cs como parte de uma chamada de AddRouting ou configurando RouteOptions diretamente com builder.Services.Configure<RouteOptions>. Por exemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
A restrição anterior é aplicada no seguinte código:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
A implementação de NoZeroesRouteConstraint impede que 0 seja usado em um parâmetro de rota.
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
O código anterior:
- Previne
0no{id}segmento da rota. - É mostrado para fornecer um exemplo básico de implementação de uma restrição personalizada. Ele não deve ser usado em um aplicativo de produção.
O código a seguir é uma abordagem melhor para evitar que um id contendo a 0 seja processado:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
O código anterior tem as seguintes vantagens em relação à NoZeroesRouteConstraint abordagem:
- Não requer uma restrição personalizada.
- Ele retorna um erro mais descritivo quando o parâmetro route inclui
0.
Transformadores de parâmetros
Transformadores de parâmetros:
- Execute ao gerar um link usando LinkGenerator.
- Implementar Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
- São configurados usando ConstraintMap.
- Pegue o valor de rota do parâmetro e transforme-o em um novo valor de cadeia de caracteres.
- O resultado é usar o valor transformado no link gerado.
Por exemplo, um transformador de parâmetro personalizado slugify no padrão de rota blog\{article:slugify} com Url.Action(new { article = "MyTestArticle" }) gera blog\my-test-article.
Considere a seguinte IOutboundParameterTransformer implementação:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Para usar um transformador de parâmetro em um padrão de rota, configure-o usando ConstraintMap em Program.cs:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
A estrutura ASP.NET Core usa transformadores de parâmetro para transformar o(s) URI(s) onde um ponto final é resolvido. Por exemplo, transformadores de parâmetros transformam os valores de rota usados para corresponder a area, controller, action e page.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Com o modelo de rota anterior, a ação SubscriptionManagementController.GetAll é correspondida com o URI /subscription-management/get-all. Um transformador de parâmetros não altera os valores de rota usados para gerar um link. Por exemplo, Url.Action("GetAll", "SubscriptionManagement") produz /subscription-management/get-all.
O ASP.NET Core fornece convenções de API para o uso de transformadores de parâmetros com rotas geradas:
- A Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention convenção MVC aplica um transformador de parâmetro especificado a todas as rotas de atributos no aplicativo. O transformador de parâmetros transforma os tokens da rota do atributo conforme são substituídos. Para obter mais informações, consulte Usar um transformador de parâmetro para personalizar a substituição de token.
- Razor O Pages usa a convenção da PageRouteTransformerConvention API. Esta convenção aplica um transformador de parâmetros especificado a todas as Páginas descobertas Razor automaticamente. O transformador de parâmetros transforma os segmentos de pasta e nome de arquivo das rotas Pages Razor . Para obter mais informações, consulte Usar um transformador de parâmetros para personalizar rotas de página.
Referência de geração de URL
Esta seção contém uma referência para o algoritmo implementado pela geração de URL. Na prática, os exemplos de geração de URL mais complexos utilizam controladores ou Páginas Razor. Consulte Roteamento em controladores para obter informações adicionais.
O processo de geração de URL começa com uma chamada para LinkGenerator.GetPathByAddress ou um método semelhante. O método é fornecido com um endereço, um conjunto de valores de rota e, opcionalmente, informações sobre a solicitação atual da HttpContext.
O primeiro passo é utilizar o endereço para resolver um conjunto de pontos de extremidade candidatos, usando um IEndpointAddressScheme<TAddress> que corresponda ao tipo do endereço.
Uma vez que o conjunto de candidatos é encontrado pelo esquema de endereçamento, os pontos de extremidade são ordenados e processados iterativamente até que uma operação de geração de URL seja bem-sucedida. A geração de URL não verifica ambiguidades, o primeiro resultado retornado é o resultado final.
Solução de problemas de geração de URL com registro em log
A primeira etapa na solução de problemas de geração de URL é definir o nível de log de Microsoft.AspNetCore.Routing como TRACE.
LinkGenerator registra muitos detalhes sobre seu processamento, o que pode ser útil para solucionar problemas.
Consulte Referência de geração de URL para obter detalhes sobre a geração de URL.
Addresses
Endereços são o conceito na geração de URLs usado para associar uma chamada no gerador de ligações a um conjunto de endpoints candidatos.
Os endereços são um conceito extensível que vem com duas implementações por padrão:
- Usando o nome do ponto de extremidade (
string) como endereço:- Fornece funcionalidade semelhante ao nome da rota do MVC.
- Usa o tipo de metadados IEndpointNameMetadata.
- Resolve a cadeia de caracteres fornecida em relação aos metadados de todos os pontos de extremidade registrados.
- Lança uma exceção na inicialização se vários endpoints usarem o mesmo nome.
- Recomendado para uso geral fora de controladores e Razor Páginas.
- Usando valores de rota (RouteValuesAddress) como endereço:
- Fornece funcionalidade semelhante à dos controladores e do Pages para a geração de URLs herdadas.
- Muito complexo para expandir e depurar.
- Fornece a implementação usada por
IUrlHelper, Tag Helpers, HTML Helpers, Action Results, etc.
A função do esquema de endereços é fazer a associação entre o endereço e os pontos de extremidade correspondentes por critérios arbitrários:
- O esquema de nomes de ponto de extremidade executa uma pesquisa básica de dicionário.
- O esquema de valores de rota tem um algoritmo de melhor subconjunto de conjunto complexo.
Valores ambientais e valores explícitos
A partir da solicitação atual, o roteamento acessa os valores de rota da solicitação HttpContext.Request.RouteValuesatual. Os valores associados à solicitação atual são chamados de valores ambientais. Para fins de clareza, a documentação refere-se aos valores de rota passados para os métodos como valores explícitos.
O exemplo a seguir mostra valores ambientais e valores explícitos. Ele fornece valores de ambiente da solicitação atual e valores explícitos:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
O código anterior:
- Devoluções
/Widget/Index/17 - Obtém LinkGenerator via DI.
O código a seguir fornece apenas valores explícitos e nenhum valor ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
O método anterior retorna /Home/Subscribe/17
O seguinte código no WidgetController retorna /Widget/Subscribe/17:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
O código a seguir fornece o controlador a partir de valores ambientais na solicitação atual e valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
No código anterior:
-
/Gadget/Edit/17é retornado. - Url obtém o IUrlHelper.
-
Action gera uma URL com um caminho absoluto para um método de ação. A URL contém o nome especificado
actione os valoresroute.
O código a seguir fornece valores de ambiente da solicitação atual e valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
O código anterior define url para /Edit/17 quando a página Editar Razor contém a seguinte diretiva de página:
@page "{id:int}"
Se a página Editar não contiver o "{id:int}" modelo de rota, url será /Edit?id=17.
O comportamento dos MVCs IUrlHelper adiciona uma camada de complexidade, além das regras descritas aqui:
-
IUrlHelpersempre fornece os valores de rota da solicitação atual como valores ambientais. -
IUrlHelper.Action sempre copia os valores atuais
actionecontrollerde rota como valores explícitos, a menos que sejam substituídos pelo desenvolvedor. -
IUrlHelper.Page sempre copia o valor da rota atual
pagecomo um valor explícito, a menos que seja substituído. -
IUrlHelper.Pagesempre substitui o valor da rota atualhandlerpornullvalores explícitos, a menos que sejam substituídos.
Os usuários muitas vezes são surpreendidos pelos detalhes comportamentais dos valores ambientais, porque o MVC parece não seguir suas próprias regras. Por razões históricas e de compatibilidade, certos valores de rota, como action, controller, page, e handler têm seu próprio comportamento de caso especial.
A funcionalidade equivalente fornecida por LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica essas anomalias de IUrlHelper para compatibilidade.
Processo de geração de URL
Uma vez encontrado o conjunto de pontos de extremidade candidatos, o algoritmo de geração de URL:
- Processa os extremos iterativamente.
- Devolve o primeiro resultado bem-sucedido.
A primeira etapa deste processo é chamada de invalidação de valores de rota. A invalidação do valor de rota é o processo pelo qual o roteamento decide quais valores de rota dos valores ambientais devem ser usados e quais devem ser ignorados. Cada valor ambiental é considerado e combinado com os valores explícitos ou ignorado.
A melhor maneira de pensar sobre o papel dos valores de ambiente é que eles tentam reduzir o número de teclas que os desenvolvedores de aplicativos precisam pressionar, em alguns casos comuns. Tradicionalmente, os cenários em que os valores ambientais são úteis estão relacionados ao MVC:
- Ao vincular a outra ação no mesmo controlador, o nome do controlador não precisa ser especificado.
- Ao vincular a outro controlador na mesma área, o nome da área não precisa ser especificado.
- Ao vincular ao mesmo método de ação, os valores de rota não precisam ser especificados.
- Ao vincular a outra parte do aplicativo, você não deseja transferir valores de rota que não têm significado nessa parte do aplicativo.
As chamadas para LinkGenerator ou IUrlHelper que retornam null geralmente são causadas por não entender a invalidação do valor da rota. Solucione problemas de invalidação de valor de rota especificando claramente mais valores de rota para ver se isso resolve o problema.
A invalidação do valor de rota funciona com base na suposição de que o esquema de URL do aplicativo é hierárquico, com uma hierarquia formada da esquerda para a direita. Considere o modelo {controller}/{action}/{id?} de rota do controlador básico para ter uma noção intuitiva de como isso funciona na prática. Uma alteração em um valor invalida todos os valores de rota que aparecem à direita. Isso reflete o pressuposto sobre hierarquia. Se a aplicação tiver um valor de ambiente para id, e a operação especificar um valor diferente para o controller:
-
idnão será reutilizado porque{controller}está à esquerda de{id?}.
Alguns exemplos que demonstram este princípio:
- Se os valores explícitos contiverem um valor para
id, o valor de ambiente paraidserá ignorado. Os valores ambientais paracontrollereactionpodem ser usados. - Se os valores explícitos contiverem um valor para
action, qualquer valor ambiente paraactionserá ignorado. Os valores ambientais paracontrollerpodem ser usados. Se o valor explícito paraactionfor diferente do valor ambiente paraaction, oidvalor não será usado. Se o valor explícito paraactionfor o mesmo que o valor ambiente paraaction, oidvalor pode ser usado. - Se os valores explícitos contiverem um valor para
controller, qualquer valor ambiente paracontrollerserá ignorado. Se o valor explícito paracontrollerfor diferente do valor ambiente paracontroller, os valoresactioneidnão serão usados. Se o valor explícito paracontrollerfor o mesmo que o valor ambiente paracontroller, osactionvalores eidpodem ser usados.
Este processo é ainda mais complicado pela existência de rotas de atributos e rotas convencionais dedicadas. Controladores de rotas convencionais, como {controller}/{action}/{id?}, especificam uma hierarquia usando parâmetros de rota. Para rotas convencionais dedicadas e rotas de atributos para controladores e Razor páginas:
- Há uma hierarquia de valores de rota.
- Eles não aparecem no modelo.
Para esses casos, a geração de URL define o conceito de valores necessários . Os endpoints criados por controladores e Razor Pages têm valores obrigatórios especificados que permitem que a invalidação de valores de rota funcione corretamente.
Detalhes do algoritmo de invalidação do valor da rota:
- Os nomes de valor necessários são combinados com os parâmetros de rota e, em seguida, processados da esquerda para a direita.
- Para cada parâmetro, o valor ambiente e o valor explícito são comparados:
- Se o valor ambiental e o valor explícito forem os mesmos, o processo continua.
- Se o valor ambiente estiver presente e o valor explícito não, o valor ambiente será usado ao gerar a URL.
- Se o valor ambiental não estiver presente e o valor explícito estiver, rejeite o valor ambiental e todos os valores ambientais subsequentes.
- Se o valor ambiente e o valor explícito estiverem presentes e os dois valores forem diferentes, rejeite o valor ambiente e todos os valores ambientais subsequentes.
Neste ponto, a operação de geração de URL está pronta para avaliar as restrições de rota. O conjunto de valores aceitos é combinado com os valores padrão do parâmetro, que são fornecidos às restrições. Se todas as restrições passarem, a operação continuará.
Em seguida, os valores aceitos podem ser usados para expandir o modelo de rota. O modelo de rota é processado:
- Da esquerda para a direita.
- Cada parâmetro tem seu valor aceito substituído.
- Com os seguintes casos especiais:
- Se os valores aceitos estiverem faltando um valor e o parâmetro tiver um valor padrão, o valor padrão será usado.
- Se os valores aceitos estiverem faltando um valor e o parâmetro for opcional, o processamento continuará.
- Se qualquer parâmetro de rota à direita de um parâmetro opcional ausente tiver um valor, a operação falhará.
- Os parâmetros contíguos com valor padrão e os parâmetros opcionais são recolhidos sempre que possível.
Os valores explicitamente fornecidos que não correspondem a um segmento da rota são adicionados à cadeia de caracteres de consulta. A tabela a seguir mostra o resultado ao usar o modelo {controller}/{action}/{id?}de rota.
| Valores Ambientais | Valores explícitos | Result |
|---|---|---|
| controlador = "Home" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | controller = "Ordem", ação = "Sobre" | /Order/About |
| controlador = "Home", cor = "Vermelho" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | ação = "Sobre", cor = "Vermelho" | /Home/About?color=Red |
Ordem de parâmetros de rota opcional
Os parâmetros de rota opcionais devem vir depois de todos os parâmetros de rota e literais necessários. No código a seguir, os id parâmetros e name devem vir após o color parâmetro:
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers;
[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET /api/my/red/2/joe
// GET /api/my/red/2
// GET /api/my
[HttpGet("{color}/{id:int?}/{name?}")]
public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
{
return Ok($"{color} {id} {name ?? ""}");
}
}
Problemas com a invalidação de valores de rota
O código a seguir mostra um exemplo de um esquema de geração de URL que não é suportado pelo roteamento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
No código anterior, o culture parâmetro route é usado para localização. O desejo é que o culture parâmetro seja sempre aceite como um valor ambiental. No entanto, o culture parâmetro não é aceite como um valor ambiente devido à forma como os valores necessários funcionam:
- No modelo de rota
"default", o parâmetro de rotacultureestá à esquerda decontroller, portanto, alterações emcontrollernão invalidarãoculture. -
"blog"no modelo de rota, o parâmetro de rotacultureé considerado estar à direita docontroller, que aparece nos valores necessários.
Analisar caminhos de URL com LinkParser
A LinkParser classe adiciona suporte para interpretar um caminho de URL num conjunto de valores de rota. O ParsePathByEndpointName método usa um nome de ponto de extremidade e um caminho de URL e retorna um conjunto de valores de rota extraídos do caminho de URL.
No exemplo de controlador a seguir, a ação de GetProduct usa um modelo de rota de api/Products/{id} e possui um Name de GetProduct:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
Na mesma classe de controlador, a AddRelatedProduct ação espera um caminho de URL, pathToRelatedProduct, que pode ser fornecido como um parâmetro de cadeia de caracteres de consulta:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
No exemplo anterior, a ação AddRelatedProduct extrai o valor da rota id do caminho do URL. Por exemplo, com um caminho de URL de /api/Products/1, o valor relatedProductId é definido como 1. Essa abordagem permite que os clientes da API usem caminhos de URL ao se referirem a recursos, sem exigir conhecimento de como essa URL é estruturada.
Configurar metadados de ponto de extremidade
Os links a seguir disponibilizam informações sobre como configurar os metadados do ponto final:
- Habilitar Cors com roteamento de ponto final
-
Exemplo de IAuthorizationPolicyProvider usando um atributo personalizado
[MinimumAgeAuthorize] - Testar a autenticação com o atributo [Autorizar]
- RequireAuthorization
- Selecionando o esquema com o atributo [Autorizar]
- Aplicar políticas usando o atributo [Autorizar]
- Autorização baseada em função no ASP.NET Core
Correspondência de host em rotas com RequireHost
RequireHost Aplica uma restrição à rota que requer o host especificado. O RequireHost parâmetro pode ser: ou [Host]
- Anfitrião:
www.domain.com, associawww.domain.coma qualquer porta. - Host com wildcard:
*.domain.com, corresponde awww.domain.com,subdomain.domain.comouwww.subdomain.domain.comem qualquer porta. - Porta:
*:5000, corresponde à porta 5000 em qualquer host. - Host e porta:
www.domain.com:5000ou*.domain.com:5000, corresponde ao host e à porta.
Vários parâmetros podem ser especificados usando RequireHost ou [Host]. A restrição corresponde a hosts válidos para qualquer um dos parâmetros. Por exemplo, [Host("domain.com", "*.domain.com")] corresponde a domain.com, www.domain.com e subdomain.domain.com.
O código a seguir usa RequireHost para exigir o host especificado na rota:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
O código a seguir usa o [Host] atributo no controlador para exigir qualquer um dos hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Quando o [Host] atributo é aplicado ao controlador e ao método de ação:
- O atributo é utilizado na ação.
- O atributo controller é ignorado.
Warning
As APIs que dependem do cabeçalho Host, como HttpRequest.Host e RequireHost, estão sujeitas a possíveis falsificações por parte dos clientes.
Para evitar falsificação de host e porta, use uma das seguintes abordagens:
- Use HttpContext.Connection (ConnectionInfo.LocalPort) onde as portas são verificadas.
- Utilize a filtragem de host.
Grupos de rotas
O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Ele reduz o código repetitivo e permite personalizar grupos inteiros de endpoints com uma única chamada para métodos como RequireAuthorization e WithMetadata que adicionam metadados de endpoint .
Por exemplo, o código a seguir cria dois grupos semelhantes de pontos de extremidade:
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
O primeiro grupo de endpoints só corresponderá a solicitações prefixadas com /public/todos e será acessível sem quaisquer autenticações. O segundo grupo de pontos de extremidade só corresponderá a solicitações prefixadas com /private/todos e exigirá autenticação.
A fábrica de filtros de ponto de extremidade QueryPrivateTodos é uma função local que modifica os parâmetros do manipulador de rotas TodoDb para permitir o acesso e o armazenamento de dados de tarefas privadas.
Os grupos de rotas também suportam grupos aninhados e padrões de prefixo complexos com parâmetros e restrições de rota. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.
O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade sem alterar o padrão de rota.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Adicionar filtros ou metadados a um grupo comporta-se da mesma forma que adicioná-los individualmente a cada ponto de extremidade antes de adicionar quaisquer filtros ou metadados adicionais que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que ele tenha sido adicionado em segundo lugar. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. Os filtros de ordem adicionados são importantes se aplicados ao mesmo grupo ou ponto de extremidade específico.
Uma solicitação para /outer/inner/ registrará o seguinte:
/outer group filter
/inner group filter
MapGet filter
Diretrizes de desempenho para roteamento
Quando uma aplicação tem problemas de desempenho, geralmente suspeita-se do roteamento como a causa. A razão pela qual o roteamento é suspeito é que estruturas como controladores e Razor Pages relatam a quantidade de tempo gasto dentro da estrutura em suas mensagens de registro. Quando há uma diferença significativa entre o tempo relatado pelos controladores e o tempo total da solicitação:
- Os desenvolvedores eliminam o código do aplicativo como a fonte do problema.
- É comum assumir que o roteamento é a causa.
O desempenho do roteamento é testado usando milhares de endpoints. É improvável que um aplicativo típico encontre um problema de desempenho apenas por ser muito grande. A causa raiz mais comum do desempenho de roteamento lento geralmente é um middleware personalizado com mau comportamento.
Este exemplo de código a seguir demonstra uma técnica básica para restringir a origem do atraso:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Para agendar o temporização de rotas:
- Intercale cada middleware com uma cópia do middleware de temporização mostrado no código anterior.
- Adicione um identificador exclusivo para correlacionar os dados de tempo com o código.
Esta é uma maneira básica de reduzir o atraso quando ele é significativo, por exemplo, mais do que 10ms. Subtrair Time 2 de Time 1 indica o tempo gasto dentro do middleware UseRouting.
O código a seguir usa uma abordagem mais compacta para o código de tempo anterior:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Recursos de roteamento potencialmente caros
A lista a seguir fornece algumas informações sobre os recursos de roteamento que são relativamente caros em comparação com modelos de rota básicos:
- Expressões regulares: É possível escrever expressões regulares que são complexas ou têm um longo tempo de execução com uma pequena quantidade de entrada.
- Segmentos complexos (
{x}-{y}-{z}):- São significativamente mais caros do que analisar um segmento de caminho de URL regular.
- Resulte em muito mais substrings a serem alocadas.
- Acesso síncrono a dados: muitos aplicativos complexos têm acesso ao banco de dados como parte de seu roteamento. Use pontos de extensibilidade como MatcherPolicy e EndpointSelectorContext, que são assíncronos.
Orientações para grandes tabelas de rotas
Por padrão, ASP.NET Core usa um algoritmo de roteamento que troca memória por tempo de CPU. Isso tem o bom efeito de que o tempo de correspondência de rota depende apenas do comprimento do caminho a ser correspondido e não do número de rotas. No entanto, essa abordagem pode ser potencialmente problemática em alguns casos, quando o aplicativo tem um grande número de rotas (em milhares) e há uma grande quantidade de prefixos variáveis nas rotas. Por exemplo, se as rotas tiverem parâmetros nos primeiros segmentos da rota, como {parameter}/some/literal.
É improvável que um aplicativo se depare com uma situação em que isso seja um problema, a menos que:
- Há um grande número de rotas no aplicativo usando esse padrão.
- Há um grande número de rotas no aplicativo.
Como determinar se um aplicativo está a enfrentar o problema da grande tabela de rotas.
- Existem dois sintomas a procurar:
- O aplicativo é lento para iniciar na primeira solicitação.
- Observe que isso é necessário, mas não suficiente. Há muitos outros problemas que não são de rota que podem causar inicialização lenta do aplicativo. Verifique a condição abaixo para determinar com precisão que o aplicativo está se deparando com essa situação.
- A aplicação consome muita memória durante a inicialização e um dump de memória mostra um grande número de
Microsoft.AspNetCore.Routing.Matching.DfaNodeinstâncias.
- O aplicativo é lento para iniciar na primeira solicitação.
Como resolver este problema
Existem várias técnicas e otimizações que podem ser aplicadas a rotas que melhoram amplamente esse cenário:
- Aplique restrições de rota aos seus parâmetros, por exemplo
{parameter:int}, ,{parameter:guid},{parameter:regex(\\d+)}, etc. sempre que possível.- Isso permite que o algoritmo de roteamento otimize internamente as estruturas usadas para correspondência e reduza drasticamente a memória usada.
- Na grande maioria dos casos, isso será suficiente para voltar a um comportamento aceitável.
- Altere as rotas para mover parâmetros para segmentos posteriores no modelo.
- Isso reduz o número de "percursos" possíveis para corresponder a um ponto de extremidade num determinado trajeto.
- Use uma rota dinâmica e execute o mapeamento para um controlador/página dinamicamente.
- Isto pode ser conseguido usando
MapDynamicControllerRouteeMapDynamicPageRoute.
- Isto pode ser conseguido usando
Middleware de curto-circuito após o encaminhamento
Quando o encaminhamento corresponde a um ponto final, geralmente permite que o restante do pipeline de middleware seja executado antes de invocar a lógica do ponto final. Os serviços podem reduzir o uso de recursos filtrando solicitações conhecidas logo no início do pipeline. Use o método de extensão ShortCircuit para fazer com que o roteamento invoque a lógica do ponto de extremidade imediatamente e, em seguida, encerre a solicitação. Por exemplo, uma determinada rota pode não precisar passar por autenticação ou middleware CORS. O exemplo seguinte interrompe solicitações que correspondem à rota /short-circuit:
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
O ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) método pode, opcionalmente, ter um código de status.
Use o método MapShortCircuit para configurar curto-circuito para várias rotas ao mesmo tempo, passando para ele uma matriz de parâmetros de prefixos de URL. Por exemplo, navegadores e bots geralmente sondam servidores em busca de caminhos bem conhecidos, como robots.txt e favicon.ico. Se o aplicativo não tiver esses arquivos, uma linha de código poderá configurar ambas as rotas:
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
MapShortCircuit retorna IEndpointConventionBuilder para que restrições de rota adicionais, como filtragem de host, possam ser adicionadas a ele.
Os métodos ShortCircuit e MapShortCircuit não afetam o middleware colocado antes do UseRouting. Tentar usar esses métodos com endpoints que também têm [Authorize] ou [RequireCors] metadados fará com que as solicitações falhem com um InvalidOperationException. Esses metadados são aplicados por [Authorize] ou [EnableCors] atributos ou por RequireCors ou RequireAuthorization métodos.
Para ver o efeito do middleware de curto-circuito, defina a categoria de log "Microsoft" como "Informações" em appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Execute o seguinte código:
var app = WebApplication.Create();
app.UseHttpLogging();
app.MapGet("/", () => "No short-circuiting!");
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
app.Run();
O exemplo a seguir é dos logs do console produzidos pela execução do endpoint /. Inclui informações do middleware de log.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Date: Wed, 03 May 2023 21:05:59 GMT
Server: Kestrel
Alt-Svc: h3=":5182"; ma=86400
Transfer-Encoding: chunked
O exemplo a seguir é da execução do /short-circuit endpoint. Ele não tem nada do middleware de registro porque o middleware estava em curto-circuito:
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
The endpoint 'HTTP: GET /short-circuit' is being executed without running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
The endpoint 'HTTP: GET /short-circuit' has been executed without running additional middleware.
Guia de orientação para autores de bibliotecas
Esta seção contém orientações para autores de bibliotecas que se baseiam no roteamento. Esses detalhes destinam-se a garantir que os desenvolvedores de aplicativos tenham uma boa experiência usando bibliotecas e estruturas que estendem o roteamento.
Definir endpoints
Para criar uma estrutura que use roteamento para correspondência de URLs, comece por definir uma experiência de usuário que se construa sobre UseEndpoints.
CONSTRUA em cima do IEndpointRouteBuilder. Isso permite que os usuários componham sua estrutura com outros recursos ASP.NET Core sem confusão. Cada modelo ASP.NET Core inclui roteamento. Suponha que o roteamento esteja presente e seja familiar para os usuários.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DO retorne um tipo concreto selado de uma chamada para MapMyFramework(...) que implementa IEndpointConventionBuilder. A maioria dos métodos de estrutura Map... segue esse padrão. A IEndpointConventionBuilder interface:
- Permite que os metadados sejam compostos.
- É alvo de uma variedade de métodos de extensão.
Declarar o teu próprio tipo permite-te adicionar funcionalidades específicas do framework à ferramenta de construção. Não há problema em envolver um construtor declarado pela estrutura e encaminhar chamadas para ele.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE escrever o seu próprio EndpointDataSource.
EndpointDataSource é a primitiva de baixo nível para declarar e atualizar uma coleção de pontos de extremidade.
EndpointDataSource é uma API poderosa usada por controladores e Razor páginas. Para obter mais informações, consulte Roteamento dinâmico de pontos finais.
Os testes de roteamento têm um exemplo básico de uma fonte de dados não atualizada.
CONSIDERAR a implementação GetGroupedEndpoints. Isso dá controle total sobre a execução das convenções de agrupamento e os metadados finais nos endpoints agrupados. Por exemplo, isso permite que implementações personalizadas EndpointDataSource executem filtros de ponto de extremidade adicionados a grupos.
NÃO tente registrar um EndpointDataSource por padrão. Exigir que os usuários registrem sua estrutura no UseEndpoints. A filosofia do encaminhamento é que nada é incluído por padrão, e que UseEndpoints é o lugar para registrar endpoints.
Criando middleware integrado ao roteamento
CONSIDERE definir tipos de metadados como uma interface.
DO possibilitam o uso de tipos de metadados como um atributo em classes e métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks como controladores e Razor Pages suportam a aplicação de atributos de metadados a tipos e métodos. Se você declarar tipos de metadados:
- Torne-os acessíveis como atributos.
- A maioria dos usuários está familiarizada com a aplicação de atributos.
Declarar um tipo de metadados como uma interface adiciona outra camada de flexibilidade:
- As interfaces são combináveis.
- Os desenvolvedores podem declarar seus próprios tipos que combinam várias políticas.
DO possibilitam a substituição de metadados, conforme mostrado no exemplo a seguir:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
A melhor maneira de seguir estas diretrizes é evitar a definição de metadados de marcadores:
- Não procure apenas pela presença de um tipo de metadados.
- Defina uma propriedade nos metadados e verifique a propriedade.
A coleta de metadados é ordenada e suporta a substituição por prioridade. No caso dos controladores, os metadados sobre o método de ação são mais específicos.
FAÇA com que o middleware seja útil com e sem roteamento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como exemplo dessa diretriz, considere o UseAuthorization middleware. O middleware de autorização permite definir uma política de fallback.
A política de fallback, se especificada, aplica-se a:
- Pontos de extremidade sem uma política especificada.
- Solicitações que não correspondem a um endpoint.
Isso torna o middleware de autorização útil fora do contexto de roteamento. O middleware de autorização pode ser usado para programação de middleware tradicional.
Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Recursos adicionais
O roteamento é responsável por corresponder às solicitações HTTP de entrada e enviar essas solicitações para os pontos de extremidade executáveis do aplicativo. Os pontos de extremidade são as unidades de código executável de tratamento de solicitações do aplicativo. Os pontos de extremidade são definidos na aplicação e configurados quando a aplicação é iniciada. O processo de correspondência de ponto de extremidade pode extrair valores da URL da solicitação e fornecer esses valores para processamento de solicitação. Usando informações de endpoint do aplicativo, o roteamento também é capaz de gerar URLs que correspondem a endpoints.
Os aplicativos podem configurar o roteamento usando:
- Controllers
- Razor Páginas
- SignalR
- Serviços gRPC
- Middleware compatível com endpoint, como Verificações de Integridade.
- Delegados e lambdas registados com encaminhamento.
Este artigo aborda detalhes de baixo nível do roteamento ASP.NET Core. Para obter informações sobre como configurar o roteamento:
- Para controladores, consulte Roteamento para ações do controlador no ASP.NET Core.
- Para Razor convenções do Pages, consulte Razor Rotas de páginas e convenções de aplicativos no ASP.NET Core.
Noções básicas de roteamento
O código a seguir mostra um exemplo básico de roteamento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O exemplo anterior inclui um único ponto de extremidade usando o MapGet método:
- Quando uma solicitação HTTP
GETé enviada para a URL/raiz :- O delegado da solicitação realiza a execução.
-
Hello World!é escrito na resposta HTTP.
- Se o método de solicitação não for
GETou a URL raiz não for/, nenhuma rota corresponderá e um HTTP 404 será retornado.
O roteamento utiliza um par de middleware, configurado por UseRouting e UseEndpoints:
-
UseRoutingAdiciona mapeamento de rotas ao pipeline de middleware. Esse middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência com base na solicitação. -
UseEndpointsAdiciona a execução de endpoints ao pipeline de middleware. Executa o delegado associado ao ponto final selecionado.
Normalmente, as aplicações não necessitam de chamar UseRouting ou UseEndpoints.
WebApplicationBuilder configura um pipeline de middleware que envolve o middleware adicionado em Program.cs com UseRouting e UseEndpoints. No entanto, os aplicativos podem alterar a ordem em que UseRouting e UseEndpoints executar chamando esses métodos explicitamente. Por exemplo, o código a seguir faz uma chamada explícita para UseRouting:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
No código anterior:
- A chamada para
app.Useimplementa um middleware personalizado que inicia o processo no início do pipeline. - A chamada para
UseRoutingconfigura o middleware de correspondência de rota para ser executado depois de o middleware personalizado. - O ponto de extremidade registrado com
MapGeté executado no final do pipeline.
Se o exemplo anterior não incluísse uma chamada para UseRouting, o middleware personalizado seria executado após o middleware correspondente à rota.
Endpoints
O MapGet método é usado para definir um ponto de extremidade. Um endpoint é algo que pode ser:
- Selecionado, combinando o URL e o método HTTP.
- Executado, ao correr o delegado.
Os pontos de extremidade que podem ser correspondidos e executados pela aplicação são configurados em UseEndpoints. Por exemplo, MapGet, MapPost, e métodos semelhantes conectam delegados de solicitação ao sistema de roteamento. Métodos adicionais podem ser usados para conectar os recursos do framework ASP.NET Core ao sistema de roteamento.
- MapRazorPages para Razor Páginas
- MapControllers para controladores
- MapHub<THub> para SignalR
- MapGrpcService<TService> para gRPC
O exemplo a seguir mostra o roteamento com um modelo de rota mais sofisticado:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
A cadeia de caracteres /hello/{name:alpha} é um modelo de rota. Um modelo de rota é usado para configurar como o ponto de extremidade é correspondido. Neste caso, o modelo corresponde:
- Um URL como
/hello/Docs - Qualquer caminho de URL que comece com
/hello/seguido por uma sequência de caracteres alfabéticos.:alphaAplica uma restrição de rota que corresponde apenas a caracteres alfabéticos. As restrições de rota são explicadas mais adiante neste artigo.
O segundo segmento do caminho da URL, {name:alpha}:
- Está vinculado ao
nameparâmetro. - É capturado e armazenado em HttpRequest.RouteValues.
O exemplo a seguir mostra o roteamento com verificações de integridade e autorização:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
O exemplo anterior demonstra como:
- O middleware de autorização pode ser usado com roteamento.
- Os pontos de extremidade podem ser usados para configurar o comportamento de autorização.
A MapHealthChecks chamada adiciona um endpoint de verificação de saúde. O encadeamento de RequireAuthorization nesta chamada anexa uma política de autorização ao endpoint.
Chamando UseAuthentication e UseAuthorization adiciona o middleware de autenticação e autorização. Esses middleware são colocados entre UseRouting e UseEndpoints para que possam:
- Veja qual ponto de extremidade foi selecionado pela
UseRouting. - Aplique uma política de autorização antes de UseEndpoints enviar para o destino.
Metadados do ponto final
No exemplo anterior, há dois pontos de extremidade, mas apenas o ponto de extremidade de verificação de integridade tem uma política de autorização anexada. Se a solicitação corresponder ao ponto de extremidade de verificação de integridade, /healthz, uma verificação de autorização será executada. Isso demonstra que os pontos de extremidade podem ter dados extras anexados a eles. Esses dados extra são chamados metadados de ponto final:
- Os metadados podem ser processados por middleware com reconhecimento de roteamento.
- Os metadados podem ser de qualquer tipo .NET.
Conceitos de roteamento
O sistema de roteamento se baseia no pipeline de middleware adicionando o poderoso conceito de ponto final . Os pontos de extremidade representam unidades da funcionalidade do aplicativo que são distintas entre si em termos de roteamento, autorização e dos vários sistemas do ASP.NET Core.
Definição de Endpoint no ASP.NET Core
Um ponto de extremidade ASP.NET Core é:
- Executável: Possui um RequestDelegate.
- Extensível: Tem uma coleção de metadados .
- Selecionável: Opcionalmente, tem informações de roteamento.
- Enumerável: A coleção de pontos de extremidade pode ser listada ao recuperar o EndpointDataSource de DI.
O código a seguir mostra como recuperar e inspecionar o ponto de extremidade correspondente à solicitação atual:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
O ponto de extremidade, se selecionado, pode ser obtido a partir do HttpContext. Suas propriedades podem ser inspecionadas. Os objetos de ponto de extremidade são imutáveis e não podem ser modificados após a criação. O tipo mais comum de ponto de extremidade é o RouteEndpoint.
RouteEndpoint Inclui informações que permitem que ele seja selecionado pelo sistema de roteamento.
No código anterior, app. Use configura um middleware embutido.
O código a seguir mostra que, dependendo de onde app.Use é chamado no pipeline, pode não haver um ponto de extremidade:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
O exemplo anterior adiciona Console.WriteLine instruções que exibem se um ponto de extremidade foi selecionado ou não. Para maior clareza, o exemplo atribui um nome de exibição ao ponto de extremidade fornecido / .
O exemplo anterior também inclui chamadas para UseRouting e UseEndpoints para controlar exatamente quando esses middleware são executados dentro do pipeline.
Executar este código com um URL de / exibe:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Executar este código com qualquer outro URL exibe:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Esta saída demonstra que:
- O ponto de extremidade é sempre nulo antes
UseRoutingde ser chamado. - Se uma correspondência for encontrada, o ponto de extremidade não será nulo entre
UseRoutinge UseEndpoints. - O
UseEndpointsmiddleware é terminal quando uma correspondência é encontrada. O middleware do terminal é definido posteriormente neste artigo. - O middleware após
UseEndpointsexecuta somente quando nenhuma correspondência é encontrada.
O UseRouting middleware utiliza o método SetEndpoint para anexar o endpoint ao contexto atual. É possível substituir o UseRouting middleware por lógica personalizada e ainda obter os benefícios do uso de endpoints. Os pontos de extremidade são uma primitiva de baixo nível, como o middleware, e não são acoplados à implementação de roteamento. A maioria dos aplicativos não precisa substituir UseRouting por lógica personalizada.
O UseEndpoints middleware foi projetado para ser usado em conjunto com o UseRouting middleware. A lógica principal para executar um endpoint é simples. Utilize GetEndpoint para recuperar o ponto de extremidade e, em seguida, invoque a propriedade RequestDelegate.
O código a seguir demonstra como o middleware pode influenciar ou reagir ao roteamento:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
O exemplo anterior demonstra dois conceitos importantes:
- O middleware pode ser executado antes
UseRoutingpara modificar os dados nos quais o roteamento opera.- Normalmente, o middleware que aparece antes do roteamento modifica alguma propriedade da solicitação, como UseRewriter, UseHttpMethodOverrideou UsePathBase.
- O middleware pode ser executado entre
UseRoutinge UseEndpoints para processar os resultados do roteamento antes que o ponto de extremidade seja executado.- Middleware executado entre
UseRoutingeUseEndpoints:- Geralmente inspeciona metadados para entender os endereços finais.
- Muitas vezes toma decisões de segurança, como fazem
UseAuthorizationeUseCors.
- A combinação de middleware e metadados permite configurar políticas por endpoint.
- Middleware executado entre
O código anterior mostra um exemplo de um middleware personalizado que oferece suporte a políticas por ponto de extremidade. O middleware grava um log de auditoria de acesso a dados confidenciais no console. O middleware pode ser configurado para auditar um endpoint com os RequiresAuditAttribute metadados. Este exemplo demonstra um padrão de aceitação em que apenas os pontos de extremidade marcados como confidenciais são auditados. É possível definir essa lógica ao contrário, auditando tudo o que não está marcado como seguro, por exemplo. O sistema de metadados do endpoint é flexível. Esta lógica pode ser desenhada de qualquer forma que se adeque ao caso de uso.
O código de exemplo anterior destina-se a demonstrar os conceitos básicos de pontos de extremidade. A amostra não se destina a utilização em produção. Uma versão mais completa de um middleware de registo de auditoria seria:
- Faça login em um arquivo ou banco de dados.
- Inclua detalhes como o utilizador, endereço IP, nome do ponto de extremidade confidencial e muito mais.
Os metadados RequiresAuditAttribute da política de auditoria são definidos como um Attribute para facilitar o uso com estruturas baseadas em classe, como controladores e SignalR. Ao usar caminho para codificar:
- Os metadados são anexados a uma API do construtor.
- As frameworks baseados em classe incluem todos os atributos no método e na classe correspondentes ao criar endpoints.
As práticas recomendadas para tipos de metadados são defini-los como interfaces ou atributos. Interfaces e atributos permitem a reutilização de código. O sistema de metadados é flexível e não impõe quaisquer limitações.
Compare o middleware do terminal com o roteamento
O exemplo a seguir demonstra o middleware e o roteamento do terminal:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
O estilo de middleware mostrado com Approach 1: é middleware terminal. Denomina-se middleware de terminal porque realiza uma operação de correspondência:
- A operação de correspondência no exemplo anterior é
Path == "/"para o middleware ePath == "/Routing"para o roteamento. - Quando uma correspondência é bem-sucedida, executa uma funcionalidade e retorna, em vez de invocar o middleware
next.
É chamado de middleware de terminal porque encerra a pesquisa, executa algumas funcionalidades e, em seguida, retorna.
A lista a seguir compara o middleware do terminal com o roteamento:
- Ambas as abordagens permitem finalizar o fluxo de processamento.
- O middleware encerra o pipeline retornando em vez de invocar
next. - Os endpoints são sempre terminais.
- O middleware encerra o pipeline retornando em vez de invocar
- O middleware do terminal permite posicionar o middleware em um local arbitrário no pipeline:
- Os pontos de extremidade são executados na posição de UseEndpoints.
- O middleware do terminal permite que o código arbitrário determine quando o middleware corresponde:
- O código personalizado para correspondência de rotas pode ser extenso e difícil de escrever corretamente.
- O roteamento fornece soluções diretas para aplicativos típicos. A maioria das aplicações não requer código de correspondência de rota personalizado.
- Os endpoints interagem com o middleware, como
UseAuthorizationeUseCors.- Usar um middleware de terminal com
UseAuthorizationouUseCorsrequer interface manual com o sistema de autorização.
- Usar um middleware de terminal com
Um ponto de extremidade define ambos:
- Um delegado para processar solicitações.
- Uma coleção de metadados arbitrários. Os metadados são utilizados para implementar preocupações abrangentes com base em políticas e configurações anexadas a cada endpoint.
O middleware de terminal pode ser uma ferramenta eficaz, mas pode exigir:
- Uma quantidade significativa de codificação e testes.
- Integração manual com outros sistemas para alcançar o nível desejado de flexibilidade.
Considere a integração com o roteamento antes de escrever um middleware de terminal.
Middleware de terminal existente que se integra com o Map ou MapWhen geralmente pode ser transformado em um ponto de extremidade com reconhecimento de roteamento. MapHealthChecks demonstra o padrão para router-ware:
- Escreva um método de extensão em IEndpointRouteBuilder.
- Crie um pipeline de middleware aninhado usando CreateApplicationBuilder.
- Anexe o middleware ao novo pipeline. Neste caso, UseHealthChecks.
- Build o pipeline de middleware em RequestDelegate.
- Chame
Mape forneça o novo pipeline de middleware. - Retorne o objeto builder fornecido pelo método de extensão
Map.
O código a seguir mostra o uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
O exemplo anterior mostra por que retornar o objeto construtor é importante. Permitir que o objeto builder seja retornado para o desenvolvedor da aplicação possibilita a configuração de políticas, como autorização, para o ponto de extremidade. Neste exemplo, o middleware de verificações de integridade não tem integração direta com o sistema de autorização.
O sistema de metadados foi criado em resposta aos problemas encontrados pelos autores de extensibilidade usando middleware de terminal. É problemático para cada middleware implementar sua própria integração com o sistema de autorização.
Correspondência de URL
- É o processo pelo qual o encaminhamento associa uma solicitação de entrada a um ponto de extremidade.
- Baseia-se em dados no caminho e cabeçalhos da URL.
- Pode ser estendido para considerar quaisquer dados na solicitação.
Quando um middleware de roteamento é executado, ele define um Endpoint e uns valores de rota para um recurso de solicitação na HttpContext da solicitação atual.
- Chamar HttpContext.GetEndpoint obtém o ponto de extremidade.
-
HttpRequest.RouteValuesObtém a coleção de valores de rota.
O middleware é executado depois de o middleware de roteamento inspecionar o endpoint e tomar medidas. Por exemplo, um middleware de autorização pode interrogar os metadados do endpoint para uma política de autorização. Depois de todo o middleware na linha de processamento de solicitação ser executado, o delegado do ponto de extremidade selecionado é invocado.
O sistema de roteamento no endpoint é responsável por todas as decisões de despacho. Como o middleware aplica políticas com base no ponto de extremidade selecionado, é importante que:
- Qualquer decisão que possa afetar o despacho ou a aplicação de políticas de segurança é tomada dentro do sistema de roteamento.
Warning
Para compatibilidade com versões anteriores, quando um delegado de ponto de extremidade Controller ou Razor Pages é executado, as propriedades de RouteContext.RouteData são definidas como valores apropriados com base no processamento da solicitação realizado até agora.
O RouteContext tipo será marcado como obsoleto em uma versão futura:
- Migrar
RouteData.ValuesparaHttpRequest.RouteValues. - Migre
RouteData.DataTokenspara recuperar IDataTokensMetadata dos metadados do ponto final.
A correspondência de URL opera em um conjunto configurável de fases. Em cada fase, a saída é um conjunto de correspondências. O conjunto de jogos pode ser reduzido ainda mais na próxima fase. A implementação de roteamento não garante uma ordem de processamento para pontos de extremidade correspondentes. Todas as correspondências possíveis são processadas de uma só vez. As fases de correspondência de URL ocorrem na seguinte ordem. ASP.NET Núcleo:
- Processa o caminho da URL em relação ao conjunto de pontos de extremidade e seus modelos de rota, coletando todas as correspondências.
- Pega a lista anterior e remove correspondências que falham com restrições de rota aplicadas.
- Pega a lista anterior e remove itens que não correspondem ao conjunto de instâncias MatcherPolicy.
- Usa o EndpointSelector para tomar uma decisão final da lista anterior.
A lista de pontos finais é priorizada de acordo com:
Todos os pontos de extremidade correspondentes são processados em cada fase até que o EndpointSelector seja alcançado. A EndpointSelector é a fase final. Ele escolhe o ponto final de prioridade mais alta entre as correspondências como a melhor opção. Se houver outras partidas com a mesma prioridade da melhor partida, uma exceção de partida ambígua é lançada.
A precedência de rota é calculada com base em um modelo de rota mais específico recebendo uma prioridade mais alta. Por exemplo, considere os modelos /hello e /{message}:
- Ambos correspondem ao caminho da URL
/hello. -
/helloé mais específica e, por conseguinte, mais prioritária.
Em geral, a precedência de rota faz um bom trabalho ao escolher a melhor opção para os tipos de esquemas de URL usados na prática. Use Order apenas quando necessário para evitar uma ambiguidade.
Devido aos tipos de extensibilidade fornecidos pelo roteamento, não é possível para o sistema de roteamento calcular antecipadamente as rotas ambíguas. Considere um exemplo como os modelos de rota /{message:alpha} e /{message:int}:
- A
alpharestrição corresponde apenas a caracteres alfabéticos. - A
intrestrição corresponde apenas a números. - Esses modelos têm a mesma precedência de rota, mas não há um único URL com o qual ambos correspondam.
- Se o sistema de roteamento relatasse um erro de ambiguidade na inicialização, bloquearia esse caso de uso válido.
Warning
A ordem das operações internas UseEndpoints não influencia o comportamento do roteamento, com uma exceção. MapControllerRoute e MapAreaRoute atribuem automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. Isso simula o comportamento de longo prazo dos controladores sem que o sistema de roteamento forneça as mesmas garantias que as implementações de roteamento mais antigas.
Roteamento de ponto de extremidade no ASP.NET Core:
- Não tem o conceito de rotas.
- Não fornece garantias de encomenda. Todos os endpoints são processados de uma só vez.
Precedência do modelo de rota e ordem de seleção do ponto final
A precedência do modelo de rota é um sistema que atribui a cada modelo de rota um valor com base em quão específico ele é. Precedência do modelo de rota:
- Evita a necessidade de ajustar a ordem dos pontos finais em casos comuns.
- Tenta corresponder às expectativas de senso comum do comportamento de encaminhamento.
Por exemplo, considere modelos /Products/List e /Products/{id}. Seria razoável supor que /Products/List é uma correspondência melhor do que /Products/{id} para o caminho /Products/List da URL. Isso funciona porque o segmento /List literal é considerado como tendo melhor precedência do que o segmento /{id}de parâmetro.
Os detalhes de como a precedência funciona são acoplados à forma como os modelos de rota são definidos:
- Modelos com mais segmentos são considerados mais específicos.
- Um segmento com texto literal é considerado mais específico do que um segmento de parâmetro.
- Um segmento de parâmetro com uma restrição é considerado mais específico do que um sem.
- Um segmento complexo é considerado tão específico quanto um segmento de parâmetro com uma restrição.
- Os parâmetros "catch-all" são os menos específicos. Consulte catch-all na seção Modelos de rota para obter informações importantes sobre rotas catch-all.
Conceitos de geração de URL
Geração de URL:
- É o processo pelo qual o roteamento pode criar um caminho de URL com base em um conjunto de valores de rota.
- Permite uma separação lógica entre endpoints e os URLs que lhes acedem.
O roteamento de ponto de extremidade inclui a API LinkGenerator.
LinkGenerator é um serviço singleton disponível na DI. A LinkGenerator API pode ser usada fora do contexto de uma solicitação em execução. Mvc.IUrlHelper e cenários que dependem do IUrlHelper, como Tag Helpers, HTML Helpers e Action Results, usam a API internamente para fornecer recursos de geração de links.
O gerador de links é apoiado pelo conceito de um endereço e esquemas de endereço. Um esquema de endereços é uma forma de determinar os pontos finais que devem ser considerados para a geração de links. Por exemplo, os cenários de nome de rota e valores de rota com os quais muitos usuários estão familiarizados a partir de controladores e Razor Páginas são implementados como um esquema de endereço.
O gerador de links pode vincular a controladores e Razor páginas através dos seguintes métodos de extensão:
Sobrecargas desses métodos aceitam argumentos que incluem o HttpContext. Esses métodos são funcionalmente equivalentes a Url.Action e Url.Page, mas oferecem flexibilidade e opções adicionais.
Os GetPath* métodos são mais semelhantes a Url.Action e Url.Page, na medida em que geram um URI contendo um caminho absoluto. Os GetUri* métodos sempre geram um URI absoluto contendo um esquema e host. Os métodos que aceitam um HttpContext geram um URI no contexto da solicitação em execução. Os valores de rota ambiente , caminho base da URL, esquema e host da solicitação em execução são usados, a menos que sejam substituídos.
LinkGenerator é chamado com um endereço. A geração de um URI ocorre em duas etapas:
- Um endereço está associado a uma lista de terminais que correspondem ao endereço.
- Cada RoutePattern de ponto de extremidade é avaliado até que um padrão de rota que corresponda aos valores fornecidos seja encontrado. A saída resultante é combinada com as outras peças de URI fornecidas ao gerador de link e retornadas.
Os métodos fornecidos por LinkGenerator suportam capacidades padrão de geração de links para qualquer tipo de endereço. A maneira mais conveniente de usar o gerador de links é através de métodos de extensão que executam operações para um tipo de endereço específico:
| Método de extensão | Description |
|---|---|
| GetPathByAddress | Gera um URI com um caminho absoluto com base nos valores fornecidos. |
| GetUriByAddress | Gera um URI absoluto com base nos valores fornecidos. |
Warning
Preste atenção às seguintes implicações dos métodos de chamada LinkGenerator :
Use
GetUri*métodos de extensão com cuidado em uma configuração de aplicativo que não valida oHostcabeçalho das solicitações de entrada. Se o cabeçalhoHostdas solicitações de entrada não for validado, entradas de solicitações não confiáveis poderão ser enviadas de volta ao cliente em URIs em uma vista ou página. Recomendamos que todas as aplicações de produção configurem o seu servidor para validar o cabeçalho doHostcom valores válidos conhecidos.Use LinkGenerator com cuidado em middleware em combinação com
MapouMapWhen.Map*Altera o caminho base da solicitação em execução, o que afeta a saída da geração de link. Todas as LinkGenerator APIs permitem especificar um caminho base. Especifique um caminho base vazio para desfazer o efeito deMap*na geração de links.
Exemplo de middleware
No exemplo a seguir, um middleware usa a LinkGenerator API para criar um link para um método de ação que lista produtos de armazenamento. Ao usar o gerador de links, injetando-o numa classe e chamando GenerateLink, qualquer classe numa aplicação terá acesso:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Modelos de rotas
Os tokens dentro dos {} definem parâmetros de rota que são vinculados se houver uma correspondência na rota. Mais de um parâmetro de rota pode ser definido em um segmento de rota, mas os parâmetros de rota devem ser separados por um valor literal. Por exemplo:
{controller=Home}{action=Index}
não é uma rota válida, porque não há valor literal entre {controller} e {action}. Os parâmetros de rota devem ter um nome e podem ter atributos adicionais especificados.
O texto literal diferente dos parâmetros de rota (por exemplo, {id}) e o separador / de caminho devem corresponder ao texto na URL. A correspondência de texto é insensível a maiúsculas e minúsculas e baseia-se na representação decodificada do caminho da URL. Para corresponder a um delimitador literal de parâmetro de rota { ou }, escape o delimitador repetindo o caractere. Por exemplo {{ ou }}.
Asterisco * ou duplo asterisco **:
- Pode ser usado como um prefixo para um parâmetro de rota para vincular ao restante do URI.
- São chamados de parâmetros catch-all . Por exemplo:
blog/{**slug}- Corresponde a qualquer URI que começa com
blog/e tem qualquer valor após ele. - O valor seguinte
blog/é atribuído ao valor de rota slug.
- Corresponde a qualquer URI que começa com
Warning
Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:
- Uma rota abrangente, por exemplo,
{**slug}" - A rota de abrangência total não processa as solicitações que deveria processar.
- A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.
Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.
Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz escape dos caracteres apropriados quando a rota é utilizada para gerar uma URL, incluindo os caracteres / separadores de caminho. Por exemplo, a rota foo/{*path} com valores de rota { path = "my/path" } gera foo/my%2Fpath. Observe a barra para a frente escapada. Para separar caracteres de caminho de ida e volta, use o prefixo do parâmetro de rota **. A rota foo/{**path} com { path = "my/path" } gera foo/my/path.
Os padrões de URL que tentam capturar um nome de arquivo com uma extensão de arquivo opcional têm considerações adicionais. Por exemplo, considere o modelo files/{filename}.{ext?}. Quando os valores para ambos filename e ext existem, ambos os valores são preenchidos. Se houver apenas um valor para filename na URL, a rota corresponderá porque o . final é opcional. Os seguintes URLs correspondem a esta rota:
/files/myFile.txt/files/myFile
Os parâmetros de rota podem ter valores padrão designados especificando o valor padrão após o nome do parâmetro separado por um sinal de igual (=). Por exemplo, {controller=Home} define Home como o valor padrão para controller. O valor padrão será usado se nenhum valor estiver presente na URL do parâmetro. Os parâmetros de rota são tornados opcionais anexando um ponto de interrogação (?) ao final do nome do parâmetro. Por exemplo, id?. A diferença entre valores opcionais e parâmetros de rota padrão é:
- Um parâmetro de rota com um valor padrão sempre produz um valor.
- Um parâmetro opcional tem um valor somente quando um valor é fornecido pela URL da solicitação.
Os parâmetros de rota podem ter restrições que devem corresponder ao valor de rota ligado à URL. Adicionar : e o nome da restrição após o nome do parâmetro de rota especifica uma restrição embutida em um parâmetro de rota. Se a restrição exigir argumentos, eles serão colocados entre parênteses (...) após o nome da restrição. Várias restrições embutidas podem ser especificadas ao anexar outro nome de restrição.
O nome da restrição e os argumentos são passados para o serviço IInlineConstraintResolver para criar uma instância de IRouteConstraint a ser usada no processamento de URLs. Por exemplo, o modelo blog/{article:minlength(10)} de rota especifica uma minlength restrição com o argumento 10. Para obter mais informações sobre restrições de rota e uma lista das restrições fornecidas pela estrutura, consulte a seção Restrições de rota .
Os parâmetros de rota também podem ter transformadores de parâmetros. Os transformadores de parâmetros transformam o valor de um parâmetro ao gerar links e fazer a correspondência de ações e páginas com URLs. Como as restrições, os transformadores de parâmetros podem ser adicionados em linha a um parâmetro de rota adicionando um : e nome do transformador após o nome do parâmetro de rota. Por exemplo, o modelo blog/{article:slugify} de rota especifica um slugify transformador. Para obter mais informações sobre transformadores de parâmetro, consulte a seção Transformadores de parâmetro .
A tabela a seguir demonstra modelos de rota de exemplo e seu comportamento:
| Modelo de rota | Exemplo de URI correspondente | A URI do pedido… |
|---|---|---|
hello |
/hello |
Corresponde apenas ao único caminho /hello. |
{Page=Home} |
/ |
Combina e configura Page para Home. |
{Page=Home} |
/Contact |
Combina e configura Page para Contact. |
{controller}/{action}/{id?} |
/Products/List |
Mapeia para o Products controlador e List ação. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapeia para o Products controlador e a Details ação com id definido para 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapeia para o Home controlador e o Index método.
id é ignorado. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapeia para o Products controlador e o Index método.
id é ignorado. |
Usar um modelo é geralmente a abordagem mais simples para roteamento. Restrições e padrões também podem ser especificados fora do modelo de rota.
Segmentos complexos
Segmentos complexos são processados combinando delimitadores literais da direita para a esquerda de uma forma não gananciosa . Por exemplo, [Route("/a{b}c{d}")] é um segmento complexo.
Segmentos complexos funcionam de uma maneira particular que deve ser entendida para usá-los com sucesso. O exemplo nesta seção demonstra por que segmentos complexos só funcionam realmente bem quando o texto do delimitador não aparece dentro dos valores dos parâmetros. Usar um regex e, em seguida, extrair manualmente os valores é necessário para casos mais complexos.
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Este é um resumo das etapas que o roteamento executa com o modelo /a{b}c{d} e o caminho /abcdda URL. O | é usado para ajudar a visualizar como o algoritmo funciona:
- O primeiro literal, da direita para a esquerda, é
c. Assim/abcdé pesquisado a partir da direita e encontra/ab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/ab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Não há texto restante e nenhum modelo de rota restante, então esta é uma correspondência.
Aqui está um exemplo de um caso negativo usando o mesmo modelo /a{b}c{d} e o caminho da URL /aabcd. O | é usado para ajudar a visualizar como o algoritmo funciona. Este caso não é uma correspondência, o que é explicado pelo mesmo algoritmo:
- O primeiro literal, da direita para a esquerda, é
c. Assim/aabcdé pesquisado a partir da direita e encontra/aab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/aab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/a|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Neste ponto, há texto
arestante, mas o algoritmo ficou sem modelo de rota para analisar, então isso não é uma correspondência.
Uma vez que o algoritmo de correspondência não é ganancioso:
- Ele corresponde à menor quantidade de texto possível em cada etapa.
- Qualquer caso em que o valor do delimitador apareça dentro dos valores dos parâmetros resulta em não correspondência.
As expressões regulares fornecem muito mais controle sobre seu comportamento correspondente.
A correspondência gananciosa, também conhecida como correspondência não-gulosa, seleciona a maior sequência possível. Não ganancioso encontra a menor sequência de caracteres possível.
Roteamento com caracteres especiais
O roteamento com caracteres especiais pode levar a resultados inesperados. Por exemplo, considere um controlador com o seguinte método de ação:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Quando string id contém os seguintes valores codificados, podem ocorrer resultados inesperados:
| ASCII | Encoded |
|---|---|
/ |
%2F |
|
+ |
Os parâmetros de rota nem sempre são decodificados por URL. Este problema poderá ser resolvido no futuro. Para obter mais informações, consulte este problema do GitHub;
Restrições de rota
As restrições de rota são executadas quando ocorre uma correspondência com a URL de entrada e o caminho da URL é tokenizado em valores de rota. As restrições de rota geralmente inspecionam o valor de rota associado por meio do modelo de rota e tomam uma decisão verdadeira ou falsa sobre se o valor é aceitável. Algumas restrições de rota usam dados fora do valor da rota para considerar se a solicitação pode ser roteada. Por exemplo, o HttpMethodRouteConstraint pode aceitar ou rejeitar uma solicitação com base no seu verbo HTTP. As restrições são usadas em solicitações de roteamento e geração de links.
Warning
Não use restrições para validação de entrada. Se as restrições forem usadas para validação de entrada, a entrada inválida resultará em uma 404 resposta Não encontrada. A entrada inválida deve produzir uma 400 solicitação incorreta com uma mensagem de erro apropriada. As restrições de rota são usadas para desambiguar rotas semelhantes, não para validar as entradas para uma rota específica.
A tabela a seguir demonstra exemplos de restrições de rota e seu comportamento esperado:
| restrição | Example | Exemplos de combinações | Notes |
|---|---|---|---|
int |
{id:int} |
123456789, -123456789 |
Corresponde a qualquer número inteiro |
bool |
{active:bool} |
true, FALSE |
Combina true ou false. Case-insensitive |
datetime |
{dob:datetime} |
2016-12-31, 2016-12-31 7:32pm |
Corresponde a um valor válido DateTime na cultura invariante. Ver advertência anterior. |
decimal |
{price:decimal} |
49.99, -1,000.01 |
Corresponde a um valor válido decimal na cultura invariante. Ver advertência anterior. |
double |
{weight:double} |
1.234, -1,001.01e8 |
Corresponde a um valor válido double na cultura invariante. Ver advertência anterior. |
float |
{weight:float} |
1.234, -1,001.01e8 |
Corresponde a um valor válido float na cultura invariante. Ver advertência anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Corresponde a um valor válido Guid |
long |
{ticks:long} |
123456789, -123456789 |
Corresponde a um valor válido long |
minlength(value) |
{username:minlength(4)} |
Rick |
String deve ter pelo menos 4 caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
String não deve ter mais de 8 caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
A cadeia de caracteres deve ter exatamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
A cadeia de caracteres deve ter no mínimo 8 e não mais de 16 caracteres |
min(value) |
{age:min(18)} |
19 |
O valor inteiro deve ser pelo menos 18 |
max(value) |
{age:max(120)} |
91 |
O valor inteiro não deve ser superior a 120 |
range(min,max) |
{age:range(18,120)} |
91 |
O valor inteiro deve ser pelo menos 18, mas não superior a 120 |
alpha |
{name:alpha} |
Rick |
A cadeia de caracteres deve consistir em um ou mais caracteres alfabéticos, como a, - e z, e ser insensível a maiúsculas/minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
String deve corresponder à expressão regular. Veja dicas sobre como definir uma expressão regular. |
required |
{name:required} |
Rick |
Usado para impor que um valor não parâmetro esteja presente durante a geração de URL |
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Várias restrições delimitadas por dois pontos podem ser aplicadas a um único parâmetro. Por exemplo, a restrição a seguir restringe um parâmetro a um valor inteiro igual ou superior a 1:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warning
As restrições de rota que verificam a URL e são convertidas para um tipo CLR sempre usam a cultura invariante. Por exemplo, a conversão para o tipo CLR int ou DateTime. Essas restrições pressupõem que a URL não é localizável. As restrições de rota fornecidas pela estrutura não modificam os valores armazenados nos valores de rota. Todos os valores de rota analisados a partir da URL são armazenados como cadeias de caracteres. Por exemplo, a float restrição tenta converter o valor da rota em um float, mas o valor convertido é usado apenas para verificar se ele pode ser convertido em float.
Expressões regulares em restrições
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Expressões regulares podem ser especificadas como restrições embutidas usando a regex(...) restrição de rota. Os métodos na MapControllerRoute família também aceitam um objeto literal de restrições. Se esse formulário for usado, os valores de cadeia de caracteres serão interpretados como expressões regulares.
O código a seguir usa uma restrição de regex embutida:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
O código a seguir usa um literal de objeto para especificar uma restrição de regex:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
A estrutura ASP.NET Core adiciona RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão regular. Consulte RegexOptions para obter uma descrição desses membros.
As expressões regulares usam delimitadores e tokens semelhantes aos usados pelo roteamento e pela linguagem C#. Os tokens de expressão regular precisam de ser escapados. Para usar a expressão regular ^\d{3}-\d{2}-\d{4}$ numa restrição inline, use uma das seguintes opções:
- Substitua os caracteres
\fornecidos na cadeia de caracteres por caracteres\\no arquivo de origem C# para fazer o escape do caractere de escape da cadeia de caracteres\. - Literais de string verbatim.
Para escapar dos caracteres delimitadores do parâmetro de roteamento {, }, [, ], dobre os caracteres na expressão, por exemplo, {{, }}, [[, ]]. A tabela a seguir mostra uma expressão regular e sua versão com escape:
| Expressão regular | Expressão regular escapada |
|---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
As expressões regulares usadas no roteamento geralmente começam com o ^ caractere e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente terminam com o $ caractere e correspondem ao final da cadeia de caracteres. Os ^ caracteres e $ garantem que a expressão regular corresponda ao valor do parâmetro route inteiro. Sem os ^ caracteres e $ , a expressão regular corresponde a qualquer substring dentro da cadeia de caracteres, o que geralmente é indesejável. A tabela a seguir fornece exemplos e explica por que eles correspondem ou não correspondem:
| Expression | String | Match | Comment |
|---|---|---|---|
[a-z]{2} |
hello | Yes | Correspondências de substring |
[a-z]{2} |
123abc456 | Yes | Correspondências de substring |
[a-z]{2} |
mz | Yes | Expressão de correspondências |
[a-z]{2} |
MZ | Yes | Não diferencia maiúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Ver ^ e $ acima |
^[a-z]{2}$ |
123abc456 | No | Ver ^ e $ acima |
Para obter mais informações sobre sintaxe de expressão regular, consulte Expressões regulares do .NET Framework.
Para restringir um parâmetro a um conjunto conhecido de valores possíveis, use uma expressão regular. Por exemplo, {action:regex(^(list|get|create)$)} só corresponde ao valor da action rota para list, get, ou create. Se passada para o dicionário de restrições, a cadeia de caracteres ^(list|get|create)$ é equivalente. As restrições passadas no dicionário de restrições que não correspondem a uma das restrições conhecidas também são tratadas como expressões regulares. As restrições passadas dentro de um modelo que não correspondem a uma das restrições conhecidas não são tratadas como expressões regulares.
Restrições de rota personalizadas
Restrições de rota personalizadas podem ser criadas implementando a IRouteConstraint interface. A IRouteConstraint interface contém Match, que retorna true se a restrição for satisfeita e false de outra forma.
Restrições de rota personalizadas raramente são necessárias. Antes de implementar uma restrição de rota personalizada, considere alternativas, como vinculação de modelo.
A pasta ASP.NET Core Constraints fornece bons exemplos de criação de restrições. Por exemplo, GuidRouteConstraint.
Para utilizar um IRouteConstraint personalizado, o tipo de restrição de rota deve ser registado com o ConstraintMap da aplicação no contêiner de serviços. A ConstraintMap é um dicionário que mapeia chaves de restrição de rota para as implementações IRouteConstraint que validam essas restrições.
ConstraintMap de um aplicativo pode ser atualizado em Program.cs como parte de uma chamada de AddRouting ou configurando RouteOptions diretamente com builder.Services.Configure<RouteOptions>. Por exemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
A restrição anterior é aplicada no seguinte código:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
A implementação de NoZeroesRouteConstraint impede que 0 seja usado em um parâmetro de rota.
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
O código anterior:
- Previne
0no{id}segmento da rota. - É mostrado para fornecer um exemplo básico de implementação de uma restrição personalizada. Ele não deve ser usado em um aplicativo de produção.
O código a seguir é uma abordagem melhor para evitar que um id contendo a 0 seja processado:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
O código anterior tem as seguintes vantagens em relação à NoZeroesRouteConstraint abordagem:
- Não requer uma restrição personalizada.
- Ele retorna um erro mais descritivo quando o parâmetro route inclui
0.
Transformadores de parâmetros
Transformadores de parâmetros:
- Execute ao gerar um link usando LinkGenerator.
- Implementar Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
- São configurados usando ConstraintMap.
- Pegue o valor de rota do parâmetro e transforme-o em um novo valor de cadeia de caracteres.
- O resultado é usar o valor transformado no link gerado.
Por exemplo, um transformador de parâmetro personalizado slugify no padrão de rota blog\{article:slugify} com Url.Action(new { article = "MyTestArticle" }) gera blog\my-test-article.
Considere a seguinte IOutboundParameterTransformer implementação:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Para usar um transformador de parâmetro em um padrão de rota, configure-o usando ConstraintMap em Program.cs:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
A estrutura ASP.NET Core usa transformadores de parâmetro para transformar o(s) URI(s) onde um ponto final é resolvido. Por exemplo, transformadores de parâmetros transformam os valores de rota usados para corresponder a area, controller, action e page.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Com o modelo de rota anterior, a ação SubscriptionManagementController.GetAll é correspondida com o URI /subscription-management/get-all. Um transformador de parâmetros não altera os valores de rota usados para gerar um link. Por exemplo, Url.Action("GetAll", "SubscriptionManagement") produz /subscription-management/get-all.
O ASP.NET Core fornece convenções de API para o uso de transformadores de parâmetros com rotas geradas:
- A Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention convenção MVC aplica um transformador de parâmetro especificado a todas as rotas de atributos no aplicativo. O transformador de parâmetros transforma os tokens da rota do atributo conforme são substituídos. Para obter mais informações, consulte Usar um transformador de parâmetro para personalizar a substituição de token.
- Razor O Pages usa a convenção da PageRouteTransformerConvention API. Esta convenção aplica um transformador de parâmetros especificado a todas as Páginas descobertas Razor automaticamente. O transformador de parâmetros transforma os segmentos de pasta e nome de arquivo das rotas Pages Razor . Para obter mais informações, consulte Usar um transformador de parâmetros para personalizar rotas de página.
Referência de geração de URL
Esta seção contém uma referência para o algoritmo implementado pela geração de URL. Na prática, os exemplos de geração de URL mais complexos utilizam controladores ou Páginas Razor. Consulte Roteamento em controladores para obter informações adicionais.
O processo de geração de URL começa com uma chamada para LinkGenerator.GetPathByAddress ou um método semelhante. O método é fornecido com um endereço, um conjunto de valores de rota e, opcionalmente, informações sobre a solicitação atual da HttpContext.
O primeiro passo é utilizar o endereço para resolver um conjunto de pontos de extremidade candidatos, usando um IEndpointAddressScheme<TAddress> que corresponda ao tipo do endereço.
Uma vez que o conjunto de candidatos é encontrado pelo esquema de endereçamento, os pontos de extremidade são ordenados e processados iterativamente até que uma operação de geração de URL seja bem-sucedida. A geração de URL não verifica ambiguidades, o primeiro resultado retornado é o resultado final.
Solução de problemas de geração de URL com registro em log
A primeira etapa na solução de problemas de geração de URL é definir o nível de log de Microsoft.AspNetCore.Routing como TRACE.
LinkGenerator registra muitos detalhes sobre seu processamento, o que pode ser útil para solucionar problemas.
Consulte Referência de geração de URL para obter detalhes sobre a geração de URL.
Addresses
Endereços são o conceito na geração de URLs usado para associar uma chamada no gerador de ligações a um conjunto de endpoints candidatos.
Os endereços são um conceito extensível que vem com duas implementações por padrão:
- Usando o nome do ponto de extremidade (
string) como endereço:- Fornece funcionalidade semelhante ao nome da rota do MVC.
- Usa o tipo de metadados IEndpointNameMetadata.
- Resolve a cadeia de caracteres fornecida em relação aos metadados de todos os pontos de extremidade registrados.
- Lança uma exceção na inicialização se vários endpoints usarem o mesmo nome.
- Recomendado para uso geral fora de controladores e Razor Páginas.
- Usando valores de rota (RouteValuesAddress) como endereço:
- Fornece funcionalidade semelhante à dos controladores e do Pages para a geração de URLs herdadas.
- Muito complexo para expandir e depurar.
- Fornece a implementação usada por
IUrlHelper, Tag Helpers, HTML Helpers, Action Results, etc.
A função do esquema de endereços é fazer a associação entre o endereço e os pontos de extremidade correspondentes por critérios arbitrários:
- O esquema de nomes de ponto de extremidade executa uma pesquisa básica de dicionário.
- O esquema de valores de rota tem um algoritmo de melhor subconjunto de conjunto complexo.
Valores ambientais e valores explícitos
A partir da solicitação atual, o roteamento acessa os valores de rota da solicitação HttpContext.Request.RouteValuesatual. Os valores associados à solicitação atual são chamados de valores ambientais. Para fins de clareza, a documentação refere-se aos valores de rota passados para os métodos como valores explícitos.
O exemplo a seguir mostra valores ambientais e valores explícitos. Ele fornece valores de ambiente da solicitação atual e valores explícitos:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
O código anterior:
- Devoluções
/Widget/Index/17 - Obtém LinkGenerator via DI.
O código a seguir fornece apenas valores explícitos e nenhum valor ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
O método anterior retorna /Home/Subscribe/17
O seguinte código no WidgetController retorna /Widget/Subscribe/17:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
O código a seguir fornece o controlador a partir de valores ambientais na solicitação atual e valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
No código anterior:
-
/Gadget/Edit/17é retornado. - Url obtém o IUrlHelper.
-
Action gera uma URL com um caminho absoluto para um método de ação. A URL contém o nome especificado
actione os valoresroute.
O código a seguir fornece valores de ambiente da solicitação atual e valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
O código anterior define url para /Edit/17 quando a página Editar Razor contém a seguinte diretiva de página:
@page "{id:int}"
Se a página Editar não contiver o "{id:int}" modelo de rota, url será /Edit?id=17.
O comportamento dos MVCs IUrlHelper adiciona uma camada de complexidade, além das regras descritas aqui:
-
IUrlHelpersempre fornece os valores de rota da solicitação atual como valores ambientais. -
IUrlHelper.Action sempre copia os valores atuais
actionecontrollerde rota como valores explícitos, a menos que sejam substituídos pelo desenvolvedor. -
IUrlHelper.Page sempre copia o valor da rota atual
pagecomo um valor explícito, a menos que seja substituído. -
IUrlHelper.Pagesempre substitui o valor da rota atualhandlerpornullvalores explícitos, a menos que sejam substituídos.
Os usuários muitas vezes são surpreendidos pelos detalhes comportamentais dos valores ambientais, porque o MVC parece não seguir suas próprias regras. Por razões históricas e de compatibilidade, certos valores de rota, como action, controller, page, e handler têm seu próprio comportamento de caso especial.
A funcionalidade equivalente fornecida por LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica essas anomalias de IUrlHelper para compatibilidade.
Processo de geração de URL
Uma vez encontrado o conjunto de pontos de extremidade candidatos, o algoritmo de geração de URL:
- Processa os extremos iterativamente.
- Devolve o primeiro resultado bem-sucedido.
A primeira etapa deste processo é chamada de invalidação de valores de rota. A invalidação do valor de rota é o processo pelo qual o roteamento decide quais valores de rota dos valores ambientais devem ser usados e quais devem ser ignorados. Cada valor ambiental é considerado e combinado com os valores explícitos ou ignorado.
A melhor maneira de pensar sobre o papel dos valores de ambiente é que eles tentam reduzir o número de teclas que os desenvolvedores de aplicativos precisam pressionar, em alguns casos comuns. Tradicionalmente, os cenários em que os valores ambientais são úteis estão relacionados ao MVC:
- Ao vincular a outra ação no mesmo controlador, o nome do controlador não precisa ser especificado.
- Ao vincular a outro controlador na mesma área, o nome da área não precisa ser especificado.
- Ao vincular ao mesmo método de ação, os valores de rota não precisam ser especificados.
- Ao vincular a outra parte do aplicativo, você não deseja transferir valores de rota que não têm significado nessa parte do aplicativo.
As chamadas para LinkGenerator ou IUrlHelper que retornam null geralmente são causadas por não entender a invalidação do valor da rota. Solucione problemas de invalidação de valor de rota especificando claramente mais valores de rota para ver se isso resolve o problema.
A invalidação do valor de rota funciona com base na suposição de que o esquema de URL do aplicativo é hierárquico, com uma hierarquia formada da esquerda para a direita. Considere o modelo {controller}/{action}/{id?} de rota do controlador básico para ter uma noção intuitiva de como isso funciona na prática. Uma alteração em um valor invalida todos os valores de rota que aparecem à direita. Isso reflete o pressuposto sobre hierarquia. Se a aplicação tiver um valor de ambiente para id, e a operação especificar um valor diferente para o controller:
-
idnão será reutilizado porque{controller}está à esquerda de{id?}.
Alguns exemplos que demonstram este princípio:
- Se os valores explícitos contiverem um valor para
id, o valor de ambiente paraidserá ignorado. Os valores ambientais paracontrollereactionpodem ser usados. - Se os valores explícitos contiverem um valor para
action, qualquer valor ambiente paraactionserá ignorado. Os valores ambientais paracontrollerpodem ser usados. Se o valor explícito paraactionfor diferente do valor ambiente paraaction, oidvalor não será usado. Se o valor explícito paraactionfor o mesmo que o valor ambiente paraaction, oidvalor pode ser usado. - Se os valores explícitos contiverem um valor para
controller, qualquer valor ambiente paracontrollerserá ignorado. Se o valor explícito paracontrollerfor diferente do valor ambiente paracontroller, os valoresactioneidnão serão usados. Se o valor explícito paracontrollerfor o mesmo que o valor ambiente paracontroller, osactionvalores eidpodem ser usados.
Este processo é ainda mais complicado pela existência de rotas de atributos e rotas convencionais dedicadas. Controladores de rotas convencionais, como {controller}/{action}/{id?}, especificam uma hierarquia usando parâmetros de rota. Para rotas convencionais dedicadas e rotas de atributos para controladores e Razor páginas:
- Há uma hierarquia de valores de rota.
- Eles não aparecem no modelo.
Para esses casos, a geração de URL define o conceito de valores necessários . Os endpoints criados por controladores e Razor Pages têm valores obrigatórios especificados que permitem que a invalidação de valores de rota funcione corretamente.
Detalhes do algoritmo de invalidação do valor da rota:
- Os nomes de valor necessários são combinados com os parâmetros de rota e, em seguida, processados da esquerda para a direita.
- Para cada parâmetro, o valor ambiente e o valor explícito são comparados:
- Se o valor ambiental e o valor explícito forem os mesmos, o processo continua.
- Se o valor ambiente estiver presente e o valor explícito não, o valor ambiente será usado ao gerar a URL.
- Se o valor ambiental não estiver presente e o valor explícito estiver, rejeite o valor ambiental e todos os valores ambientais subsequentes.
- Se o valor ambiente e o valor explícito estiverem presentes e os dois valores forem diferentes, rejeite o valor ambiente e todos os valores ambientais subsequentes.
Neste ponto, a operação de geração de URL está pronta para avaliar as restrições de rota. O conjunto de valores aceitos é combinado com os valores padrão do parâmetro, que são fornecidos às restrições. Se todas as restrições passarem, a operação continuará.
Em seguida, os valores aceitos podem ser usados para expandir o modelo de rota. O modelo de rota é processado:
- Da esquerda para a direita.
- Cada parâmetro tem seu valor aceito substituído.
- Com os seguintes casos especiais:
- Se os valores aceitos estiverem faltando um valor e o parâmetro tiver um valor padrão, o valor padrão será usado.
- Se os valores aceitos estiverem faltando um valor e o parâmetro for opcional, o processamento continuará.
- Se qualquer parâmetro de rota à direita de um parâmetro opcional ausente tiver um valor, a operação falhará.
- Os parâmetros contíguos com valor padrão e os parâmetros opcionais são recolhidos sempre que possível.
Os valores explicitamente fornecidos que não correspondem a um segmento da rota são adicionados à cadeia de caracteres de consulta. A tabela a seguir mostra o resultado ao usar o modelo {controller}/{action}/{id?}de rota.
| Valores Ambientais | Valores explícitos | Result |
|---|---|---|
| controlador = "Home" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | controller = "Ordem", ação = "Sobre" | /Order/About |
| controlador = "Home", cor = "Vermelho" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | ação = "Sobre", cor = "Vermelho" | /Home/About?color=Red |
Ordem de parâmetros de rota opcional
Os parâmetros de rota opcionais devem vir depois de todos os parâmetros de rota necessários. No código a seguir, os id parâmetros e name devem vir após o color parâmetro:
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers;
[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET /api/my/red/2/joe
// GET /api/my/red/2
// GET /api/my
[HttpGet("{color}/{id:int?}/{name?}")]
public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
{
return Ok($"{color} {id} {name ?? ""}");
}
}
Problemas com a invalidação de valores de rota
O código a seguir mostra um exemplo de um esquema de geração de URL que não é suportado pelo roteamento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
No código anterior, o culture parâmetro route é usado para localização. O desejo é que o culture parâmetro seja sempre aceite como um valor ambiental. No entanto, o culture parâmetro não é aceite como um valor ambiente devido à forma como os valores necessários funcionam:
- No modelo de rota
"default", o parâmetro de rotacultureestá à esquerda decontroller, portanto, alterações emcontrollernão invalidarãoculture. -
"blog"no modelo de rota, o parâmetro de rotacultureé considerado estar à direita docontroller, que aparece nos valores necessários.
Analisar caminhos de URL com LinkParser
A LinkParser classe adiciona suporte para interpretar um caminho de URL num conjunto de valores de rota. O ParsePathByEndpointName método usa um nome de ponto de extremidade e um caminho de URL e retorna um conjunto de valores de rota extraídos do caminho de URL.
No exemplo de controlador a seguir, a ação de GetProduct usa um modelo de rota de api/Products/{id} e possui um Name de GetProduct:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
Na mesma classe de controlador, a AddRelatedProduct ação espera um caminho de URL, pathToRelatedProduct, que pode ser fornecido como um parâmetro de cadeia de caracteres de consulta:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
No exemplo anterior, a ação AddRelatedProduct extrai o valor da rota id do caminho do URL. Por exemplo, com um caminho de URL de /api/Products/1, o valor relatedProductId é definido como 1. Essa abordagem permite que os clientes da API usem caminhos de URL ao se referirem a recursos, sem exigir conhecimento de como essa URL é estruturada.
Configurar metadados de ponto de extremidade
Os links a seguir disponibilizam informações sobre como configurar os metadados do ponto final:
- Habilitar Cors com roteamento de ponto final
-
Exemplo de IAuthorizationPolicyProvider usando um atributo personalizado
[MinimumAgeAuthorize] - Testar a autenticação com o atributo [Autorizar]
- RequireAuthorization
- Selecionando o esquema com o atributo [Autorizar]
- Aplicar políticas usando o atributo [Autorizar]
- Autorização baseada em função no ASP.NET Core
Correspondência de host em rotas com RequireHost
RequireHost Aplica uma restrição à rota que requer o host especificado. O RequireHost parâmetro pode ser: ou [Host]
- Anfitrião:
www.domain.com, associawww.domain.coma qualquer porta. - Host com wildcard:
*.domain.com, corresponde awww.domain.com,subdomain.domain.comouwww.subdomain.domain.comem qualquer porta. - Porta:
*:5000, corresponde à porta 5000 em qualquer host. - Host e porta:
www.domain.com:5000ou*.domain.com:5000, corresponde ao host e à porta.
Vários parâmetros podem ser especificados usando RequireHost ou [Host]. A restrição corresponde a hosts válidos para qualquer um dos parâmetros. Por exemplo, [Host("domain.com", "*.domain.com")] corresponde a domain.com, www.domain.com e subdomain.domain.com.
O código a seguir usa RequireHost para exigir o host especificado na rota:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
O código a seguir usa o [Host] atributo no controlador para exigir qualquer um dos hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Quando o [Host] atributo é aplicado ao controlador e ao método de ação:
- O atributo é utilizado na ação.
- O atributo controller é ignorado.
Grupos de rotas
O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Ele reduz o código repetitivo e permite personalizar grupos inteiros de endpoints com uma única chamada para métodos como RequireAuthorization e WithMetadata que adicionam metadados de endpoint .
Por exemplo, o código a seguir cria dois grupos semelhantes de pontos de extremidade:
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
O primeiro grupo de endpoints só corresponderá a solicitações prefixadas com /public/todos e será acessível sem quaisquer autenticações. O segundo grupo de pontos de extremidade só corresponderá a solicitações prefixadas com /private/todos e exigirá autenticação.
A fábrica de filtros de ponto de extremidade QueryPrivateTodos é uma função local que modifica os parâmetros do manipulador de rotas TodoDb para permitir o acesso e o armazenamento de dados de tarefas privadas.
Os grupos de rotas também suportam grupos aninhados e padrões de prefixo complexos com parâmetros e restrições de rota. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.
O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade sem alterar o padrão de rota.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Adicionar filtros ou metadados a um grupo comporta-se da mesma forma que adicioná-los individualmente a cada ponto de extremidade antes de adicionar quaisquer filtros ou metadados adicionais que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que ele tenha sido adicionado em segundo lugar. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. Os filtros de ordem adicionados são importantes se aplicados ao mesmo grupo ou ponto de extremidade específico.
Uma solicitação para /outer/inner/ registrará o seguinte:
/outer group filter
/inner group filter
MapGet filter
Diretrizes de desempenho para roteamento
Quando uma aplicação tem problemas de desempenho, geralmente suspeita-se do roteamento como a causa. A razão pela qual o roteamento é suspeito é que estruturas como controladores e Razor Pages relatam a quantidade de tempo gasto dentro da estrutura em suas mensagens de registro. Quando há uma diferença significativa entre o tempo relatado pelos controladores e o tempo total da solicitação:
- Os desenvolvedores eliminam o código do aplicativo como a fonte do problema.
- É comum assumir que o roteamento é a causa.
O desempenho do roteamento é testado usando milhares de endpoints. É improvável que um aplicativo típico encontre um problema de desempenho apenas por ser muito grande. A causa raiz mais comum do desempenho de roteamento lento geralmente é um middleware personalizado com mau comportamento.
Este exemplo de código a seguir demonstra uma técnica básica para restringir a origem do atraso:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Para agendar o temporização de rotas:
- Intercale cada middleware com uma cópia do middleware de temporização mostrado no código anterior.
- Adicione um identificador exclusivo para correlacionar os dados de tempo com o código.
Esta é uma maneira básica de reduzir o atraso quando ele é significativo, por exemplo, mais do que 10ms. Subtrair Time 2 de Time 1 indica o tempo gasto dentro do middleware UseRouting.
O código a seguir usa uma abordagem mais compacta para o código de tempo anterior:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Recursos de roteamento potencialmente caros
A lista a seguir fornece algumas informações sobre os recursos de roteamento que são relativamente caros em comparação com modelos de rota básicos:
- Expressões regulares: É possível escrever expressões regulares que são complexas ou têm um longo tempo de execução com uma pequena quantidade de entrada.
- Segmentos complexos (
{x}-{y}-{z}):- São significativamente mais caros do que analisar um segmento de caminho de URL regular.
- Resulte em muito mais substrings a serem alocadas.
- Acesso síncrono a dados: muitos aplicativos complexos têm acesso ao banco de dados como parte de seu roteamento. Use pontos de extensibilidade como MatcherPolicy e EndpointSelectorContext, que são assíncronos.
Orientações para grandes tabelas de rotas
Por padrão, ASP.NET Core usa um algoritmo de roteamento que troca memória por tempo de CPU. Isso tem o bom efeito de que o tempo de correspondência de rota depende apenas do comprimento do caminho a ser correspondido e não do número de rotas. No entanto, essa abordagem pode ser potencialmente problemática em alguns casos, quando o aplicativo tem um grande número de rotas (em milhares) e há uma grande quantidade de prefixos variáveis nas rotas. Por exemplo, se as rotas tiverem parâmetros nos primeiros segmentos da rota, como {parameter}/some/literal.
É improvável que um aplicativo se depare com uma situação em que isso seja um problema, a menos que:
- Há um grande número de rotas no aplicativo usando esse padrão.
- Há um grande número de rotas no aplicativo.
Como determinar se um aplicativo está a enfrentar o problema da grande tabela de rotas.
- Existem dois sintomas a procurar:
- O aplicativo é lento para iniciar na primeira solicitação.
- Observe que isso é necessário, mas não suficiente. Há muitos outros problemas que não são de rota que podem causar inicialização lenta do aplicativo. Verifique a condição abaixo para determinar com precisão que o aplicativo está se deparando com essa situação.
- A aplicação consome muita memória durante a inicialização e um dump de memória mostra um grande número de
Microsoft.AspNetCore.Routing.Matching.DfaNodeinstâncias.
- O aplicativo é lento para iniciar na primeira solicitação.
Como resolver este problema
Existem várias técnicas e otimizações que podem ser aplicadas a rotas que irão melhorar amplamente este cenário:
- Aplique restrições de rota aos seus parâmetros, por exemplo
{parameter:int}, ,{parameter:guid},{parameter:regex(\\d+)}, etc. sempre que possível.- Isso permite que o algoritmo de roteamento otimize internamente as estruturas usadas para correspondência e reduza drasticamente a memória usada.
- Na grande maioria dos casos, isso será suficiente para voltar a um comportamento aceitável.
- Altere as rotas para mover parâmetros para segmentos posteriores no modelo.
- Isso reduz o número de "percursos" possíveis para corresponder a um ponto de extremidade num determinado trajeto.
- Use uma rota dinâmica e execute o mapeamento para um controlador/página dinamicamente.
- Isto pode ser conseguido usando
MapDynamicControllerRouteeMapDynamicPageRoute.
- Isto pode ser conseguido usando
Guia de orientação para autores de bibliotecas
Esta seção contém orientações para autores de bibliotecas que se baseiam no roteamento. Esses detalhes destinam-se a garantir que os desenvolvedores de aplicativos tenham uma boa experiência usando bibliotecas e estruturas que estendem o roteamento.
Definir endpoints
Para criar uma estrutura que use roteamento para correspondência de URLs, comece por definir uma experiência de usuário que se construa sobre UseEndpoints.
CONSTRUA em cima do IEndpointRouteBuilder. Isso permite que os usuários componham sua estrutura com outros recursos ASP.NET Core sem confusão. Cada modelo ASP.NET Core inclui roteamento. Suponha que o roteamento esteja presente e seja familiar para os usuários.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DO retorne um tipo concreto selado de uma chamada para MapMyFramework(...) que implementa IEndpointConventionBuilder. A maioria dos métodos de estrutura Map... segue esse padrão. A IEndpointConventionBuilder interface:
- Permite que os metadados sejam compostos.
- É alvo de uma variedade de métodos de extensão.
Declarar o teu próprio tipo permite-te adicionar funcionalidades específicas do framework à ferramenta de construção. Não há problema em envolver um construtor declarado pela estrutura e encaminhar chamadas para ele.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE escrever o seu próprio EndpointDataSource.
EndpointDataSource é a primitiva de baixo nível para declarar e atualizar uma coleção de pontos de extremidade.
EndpointDataSource é uma API poderosa usada por controladores e Razor páginas.
Os testes de roteamento têm um exemplo básico de uma fonte de dados não atualizada.
CONSIDERAR a implementação GetGroupedEndpoints. Isso dá controle total sobre a execução das convenções de agrupamento e os metadados finais nos endpoints agrupados. Por exemplo, isso permite que implementações personalizadas EndpointDataSource executem filtros de ponto de extremidade adicionados a grupos.
NÃO tente registrar um EndpointDataSource por padrão. Exigir que os usuários registrem sua estrutura no UseEndpoints. A filosofia do encaminhamento é que nada é incluído por padrão, e que UseEndpoints é o lugar para registrar endpoints.
Criando middleware integrado ao roteamento
CONSIDERE definir tipos de metadados como uma interface.
DO possibilitam o uso de tipos de metadados como um atributo em classes e métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks como controladores e Razor Pages suportam a aplicação de atributos de metadados a tipos e métodos. Se você declarar tipos de metadados:
- Torne-os acessíveis como atributos.
- A maioria dos usuários está familiarizada com a aplicação de atributos.
Declarar um tipo de metadados como uma interface adiciona outra camada de flexibilidade:
- As interfaces são combináveis.
- Os desenvolvedores podem declarar seus próprios tipos que combinam várias políticas.
DO possibilitam a substituição de metadados, conforme mostrado no exemplo a seguir:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
A melhor maneira de seguir estas diretrizes é evitar a definição de metadados de marcadores:
- Não procure apenas pela presença de um tipo de metadados.
- Defina uma propriedade nos metadados e verifique a propriedade.
A coleta de metadados é ordenada e suporta a substituição por prioridade. No caso dos controladores, os metadados sobre o método de ação são mais específicos.
FAÇA com que o middleware seja útil com e sem roteamento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como exemplo dessa diretriz, considere o UseAuthorization middleware. O middleware de autorização permite definir uma política de fallback.
A política de fallback, se especificada, aplica-se a:
- Pontos de extremidade sem uma política especificada.
- Solicitações que não correspondem a um endpoint.
Isso torna o middleware de autorização útil fora do contexto de roteamento. O middleware de autorização pode ser usado para programação de middleware tradicional.
Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Recursos adicionais
O roteamento é responsável por corresponder às solicitações HTTP de entrada e enviar essas solicitações para os pontos de extremidade executáveis do aplicativo. Os pontos de extremidade são as unidades de código executável de tratamento de solicitações do aplicativo. Os pontos de extremidade são definidos na aplicação e configurados quando a aplicação é iniciada. O processo de correspondência de ponto de extremidade pode extrair valores da URL da solicitação e fornecer esses valores para processamento de solicitação. Usando informações de endpoint do aplicativo, o roteamento também é capaz de gerar URLs que correspondem a endpoints.
Os aplicativos podem configurar o roteamento usando:
- Controllers
- Razor Páginas
- SignalR
- Serviços gRPC
- Middleware compatível com endpoint, como Verificações de Integridade.
- Delegados e lambdas registados com encaminhamento.
Este artigo aborda detalhes de baixo nível do roteamento ASP.NET Core. Para obter informações sobre como configurar o roteamento:
- Para controladores, consulte Roteamento para ações do controlador no ASP.NET Core.
- Para Razor convenções do Pages, consulte Razor Rotas de páginas e convenções de aplicativos no ASP.NET Core.
Noções básicas de roteamento
O código a seguir mostra um exemplo básico de roteamento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
O exemplo anterior inclui um único ponto de extremidade usando o MapGet método:
- Quando uma solicitação HTTP
GETé enviada para a URL/raiz :- O delegado da solicitação realiza a execução.
-
Hello World!é escrito na resposta HTTP.
- Se o método de solicitação não for
GETou a URL raiz não for/, nenhuma rota corresponderá e um HTTP 404 será retornado.
O roteamento utiliza um par de middleware, configurado por UseRouting e UseEndpoints:
-
UseRoutingAdiciona mapeamento de rotas ao pipeline de middleware. Esse middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência com base na solicitação. -
UseEndpointsAdiciona a execução de endpoints ao pipeline de middleware. Executa o delegado associado ao ponto final selecionado.
Normalmente, as aplicações não necessitam de chamar UseRouting ou UseEndpoints.
WebApplicationBuilder configura um pipeline de middleware que envolve o middleware adicionado em Program.cs com UseRouting e UseEndpoints. No entanto, os aplicativos podem alterar a ordem em que UseRouting e UseEndpoints executar chamando esses métodos explicitamente. Por exemplo, o código a seguir faz uma chamada explícita para UseRouting:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
No código anterior:
- A chamada para
app.Useimplementa um middleware personalizado que inicia o processo no início do pipeline. - A chamada para
UseRoutingconfigura o middleware de correspondência de rota para ser executado depois de o middleware personalizado. - O ponto de extremidade registrado com
MapGeté executado no final do pipeline.
Se o exemplo anterior não incluísse uma chamada para UseRouting, o middleware personalizado seria executado após o middleware correspondente à rota.
Endpoints
O MapGet método é usado para definir um ponto de extremidade. Um endpoint é algo que pode ser:
- Selecionado, combinando o URL e o método HTTP.
- Executado, ao correr o delegado.
Os pontos de extremidade que podem ser correspondidos e executados pela aplicação são configurados em UseEndpoints. Por exemplo, MapGet, MapPost, e métodos semelhantes conectam delegados de solicitação ao sistema de roteamento. Métodos adicionais podem ser usados para conectar os recursos do framework ASP.NET Core ao sistema de roteamento.
- MapRazorPages para Razor Páginas
- MapControllers para controladores
- MapHub<THub> para SignalR
- MapGrpcService<TService> para gRPC
O exemplo a seguir mostra o roteamento com um modelo de rota mais sofisticado:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
A cadeia de caracteres /hello/{name:alpha} é um modelo de rota. Um modelo de rota é usado para configurar como o ponto de extremidade é correspondido. Neste caso, o modelo corresponde:
- Um URL como
/hello/Docs - Qualquer caminho de URL que comece com
/hello/seguido por uma sequência de caracteres alfabéticos.:alphaAplica uma restrição de rota que corresponde apenas a caracteres alfabéticos. As restrições de rota são explicadas mais adiante neste artigo.
O segundo segmento do caminho da URL, {name:alpha}:
- Está vinculado ao
nameparâmetro. - É capturado e armazenado em HttpRequest.RouteValues.
O exemplo a seguir mostra o roteamento com verificações de integridade e autorização:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
O exemplo anterior demonstra como:
- O middleware de autorização pode ser usado com roteamento.
- Os pontos de extremidade podem ser usados para configurar o comportamento de autorização.
A MapHealthChecks chamada adiciona um endpoint de verificação de saúde. O encadeamento de RequireAuthorization nesta chamada anexa uma política de autorização ao endpoint.
Chamando UseAuthentication e UseAuthorization adiciona o middleware de autenticação e autorização. Esses middleware são colocados entre UseRouting e UseEndpoints para que possam:
- Veja qual ponto de extremidade foi selecionado pela
UseRouting. - Aplique uma política de autorização antes de UseEndpoints enviar para o destino.
Metadados do ponto final
No exemplo anterior, há dois pontos de extremidade, mas apenas o ponto de extremidade de verificação de integridade tem uma política de autorização anexada. Se a solicitação corresponder ao ponto de extremidade de verificação de integridade, /healthz, uma verificação de autorização será executada. Isso demonstra que os pontos de extremidade podem ter dados extras anexados a eles. Esses dados extra são chamados metadados de ponto final:
- Os metadados podem ser processados por middleware com reconhecimento de roteamento.
- Os metadados podem ser de qualquer tipo .NET.
Conceitos de roteamento
O sistema de roteamento se baseia no pipeline de middleware adicionando o poderoso conceito de ponto final . Os pontos de extremidade representam unidades da funcionalidade do aplicativo que são distintas entre si em termos de roteamento, autorização e dos vários sistemas do ASP.NET Core.
Definição de Endpoint no ASP.NET Core
Um ponto de extremidade ASP.NET Core é:
- Executável: Possui um RequestDelegate.
- Extensível: Tem uma coleção de metadados .
- Selecionável: Opcionalmente, tem informações de roteamento.
- Enumerável: A coleção de pontos de extremidade pode ser listada ao recuperar o EndpointDataSource de DI.
O código a seguir mostra como recuperar e inspecionar o ponto de extremidade correspondente à solicitação atual:
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}
await next(context);
});
app.MapGet("/", () => "Inspect Endpoint.");
O ponto de extremidade, se selecionado, pode ser obtido a partir do HttpContext. Suas propriedades podem ser inspecionadas. Os objetos de ponto de extremidade são imutáveis e não podem ser modificados após a criação. O tipo mais comum de ponto de extremidade é o RouteEndpoint.
RouteEndpoint Inclui informações que permitem que ele seja selecionado pelo sistema de roteamento.
No código anterior, app. Use configura um middleware embutido.
O código a seguir mostra que, dependendo de onde app.Use é chamado no pipeline, pode não haver um ponto de extremidade:
// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");
app.UseEndpoints(_ => { });
// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
await next(context);
});
O exemplo anterior adiciona Console.WriteLine instruções que exibem se um ponto de extremidade foi selecionado ou não. Para maior clareza, o exemplo atribui um nome de exibição ao ponto de extremidade fornecido / .
O exemplo anterior também inclui chamadas para UseRouting e UseEndpoints para controlar exatamente quando esses middleware são executados dentro do pipeline.
Executar este código com um URL de / exibe:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Executar este código com qualquer outro URL exibe:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Esta saída demonstra que:
- O ponto de extremidade é sempre nulo antes
UseRoutingde ser chamado. - Se uma correspondência for encontrada, o ponto de extremidade não será nulo entre
UseRoutinge UseEndpoints. - O
UseEndpointsmiddleware é terminal quando uma correspondência é encontrada. O middleware do terminal é definido posteriormente neste artigo. - O middleware após
UseEndpointsexecuta somente quando nenhuma correspondência é encontrada.
O UseRouting middleware utiliza o método SetEndpoint para anexar o endpoint ao contexto atual. É possível substituir o UseRouting middleware por lógica personalizada e ainda obter os benefícios do uso de endpoints. Os pontos de extremidade são uma primitiva de baixo nível, como o middleware, e não são acoplados à implementação de roteamento. A maioria dos aplicativos não precisa substituir UseRouting por lógica personalizada.
O UseEndpoints middleware foi projetado para ser usado em conjunto com o UseRouting middleware. A lógica principal para executar um endpoint é simples. Utilize GetEndpoint para recuperar o ponto de extremidade e, em seguida, invoque a propriedade RequestDelegate.
O código a seguir demonstra como o middleware pode influenciar ou reagir ao roteamento:
app.UseHttpMethodOverride();
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
await next(context);
});
app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }
O exemplo anterior demonstra dois conceitos importantes:
- O middleware pode ser executado antes
UseRoutingpara modificar os dados nos quais o roteamento opera.- Normalmente, o middleware que aparece antes do roteamento modifica alguma propriedade da solicitação, como UseRewriter, UseHttpMethodOverrideou UsePathBase.
- O middleware pode ser executado entre
UseRoutinge UseEndpoints para processar os resultados do roteamento antes que o ponto de extremidade seja executado.- Middleware executado entre
UseRoutingeUseEndpoints:- Geralmente inspeciona metadados para entender os endereços finais.
- Muitas vezes toma decisões de segurança, como fazem
UseAuthorizationeUseCors.
- A combinação de middleware e metadados permite configurar políticas por endpoint.
- Middleware executado entre
O código anterior mostra um exemplo de um middleware personalizado que oferece suporte a políticas por ponto de extremidade. O middleware grava um log de auditoria de acesso a dados confidenciais no console. O middleware pode ser configurado para auditar um endpoint com os RequiresAuditAttribute metadados. Este exemplo demonstra um padrão de aceitação em que apenas os pontos de extremidade marcados como confidenciais são auditados. É possível definir essa lógica ao contrário, auditando tudo o que não está marcado como seguro, por exemplo. O sistema de metadados do endpoint é flexível. Esta lógica pode ser desenhada de qualquer forma que se adeque ao caso de uso.
O código de exemplo anterior destina-se a demonstrar os conceitos básicos de pontos de extremidade. A amostra não se destina a utilização em produção. Uma versão mais completa de um middleware de registo de auditoria seria:
- Faça login em um arquivo ou banco de dados.
- Inclua detalhes como o utilizador, endereço IP, nome do ponto de extremidade confidencial e muito mais.
Os metadados RequiresAuditAttribute da política de auditoria são definidos como um Attribute para facilitar o uso com estruturas baseadas em classe, como controladores e SignalR. Ao usar caminho para codificar:
- Os metadados são anexados a uma API do construtor.
- As frameworks baseados em classe incluem todos os atributos no método e na classe correspondentes ao criar endpoints.
As práticas recomendadas para tipos de metadados são defini-los como interfaces ou atributos. Interfaces e atributos permitem a reutilização de código. O sistema de metadados é flexível e não impõe quaisquer limitações.
Compare o middleware do terminal com o roteamento
O exemplo a seguir demonstra o middleware e o roteamento do terminal:
// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}
await next(context);
});
app.UseRouting();
// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");
O estilo de middleware mostrado com Approach 1: é middleware terminal. Denomina-se middleware de terminal porque realiza uma operação de correspondência:
- A operação de correspondência no exemplo anterior é
Path == "/"para o middleware ePath == "/Routing"para o roteamento. - Quando uma correspondência é bem-sucedida, executa uma funcionalidade e retorna, em vez de invocar o middleware
next.
É chamado de middleware de terminal porque encerra a pesquisa, executa algumas funcionalidades e, em seguida, retorna.
A lista a seguir compara o middleware do terminal com o roteamento:
- Ambas as abordagens permitem finalizar o fluxo de processamento.
- O middleware encerra o pipeline retornando em vez de invocar
next. - Os endpoints são sempre terminais.
- O middleware encerra o pipeline retornando em vez de invocar
- O middleware do terminal permite posicionar o middleware em um local arbitrário no pipeline:
- Os pontos de extremidade são executados na posição de UseEndpoints.
- O middleware do terminal permite que o código arbitrário determine quando o middleware corresponde:
- O código personalizado para correspondência de rotas pode ser extenso e difícil de escrever corretamente.
- O roteamento fornece soluções diretas para aplicativos típicos. A maioria das aplicações não requer código de correspondência de rota personalizado.
- Os endpoints interagem com o middleware, como
UseAuthorizationeUseCors.- Usar um middleware de terminal com
UseAuthorizationouUseCorsrequer interface manual com o sistema de autorização.
- Usar um middleware de terminal com
Um ponto de extremidade define ambos:
- Um delegado para processar solicitações.
- Uma coleção de metadados arbitrários. Os metadados são utilizados para implementar preocupações abrangentes com base em políticas e configurações anexadas a cada endpoint.
O middleware de terminal pode ser uma ferramenta eficaz, mas pode exigir:
- Uma quantidade significativa de codificação e testes.
- Integração manual com outros sistemas para alcançar o nível desejado de flexibilidade.
Considere a integração com o roteamento antes de escrever um middleware de terminal.
Middleware de terminal existente que se integra com o Map ou MapWhen geralmente pode ser transformado em um ponto de extremidade com reconhecimento de roteamento. MapHealthChecks demonstra o padrão para router-ware:
- Escreva um método de extensão em IEndpointRouteBuilder.
- Crie um pipeline de middleware aninhado usando CreateApplicationBuilder.
- Anexe o middleware ao novo pipeline. Neste caso, UseHealthChecks.
- Build o pipeline de middleware em RequestDelegate.
- Chame
Mape forneça o novo pipeline de middleware. - Retorne o objeto builder fornecido pelo método de extensão
Map.
O código a seguir mostra o uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
O exemplo anterior mostra por que retornar o objeto construtor é importante. Permitir que o objeto builder seja retornado para o desenvolvedor da aplicação possibilita a configuração de políticas, como autorização, para o ponto de extremidade. Neste exemplo, o middleware de verificações de integridade não tem integração direta com o sistema de autorização.
O sistema de metadados foi criado em resposta aos problemas encontrados pelos autores de extensibilidade usando middleware de terminal. É problemático para cada middleware implementar sua própria integração com o sistema de autorização.
Correspondência de URL
- É o processo pelo qual o encaminhamento associa uma solicitação de entrada a um ponto de extremidade.
- Baseia-se em dados no caminho e cabeçalhos da URL.
- Pode ser estendido para considerar quaisquer dados na solicitação.
Quando um middleware de roteamento é executado, ele define um Endpoint e uns valores de rota para um recurso de solicitação na HttpContext da solicitação atual.
- Chamar HttpContext.GetEndpoint obtém o ponto de extremidade.
-
HttpRequest.RouteValuesObtém a coleção de valores de rota.
O middleware é executado depois de o middleware de roteamento inspecionar o endpoint e tomar medidas. Por exemplo, um middleware de autorização pode interrogar os metadados do endpoint para uma política de autorização. Depois de todo o middleware na linha de processamento de solicitação ser executado, o delegado do ponto de extremidade selecionado é invocado.
O sistema de roteamento no endpoint é responsável por todas as decisões de despacho. Como o middleware aplica políticas com base no ponto de extremidade selecionado, é importante que:
- Qualquer decisão que possa afetar o despacho ou a aplicação de políticas de segurança é tomada dentro do sistema de roteamento.
Warning
Para compatibilidade com versões anteriores, quando um delegado de ponto de extremidade Controller ou Razor Pages é executado, as propriedades de RouteContext.RouteData são definidas como valores apropriados com base no processamento da solicitação realizado até agora.
O RouteContext tipo será marcado como obsoleto em uma versão futura:
- Migrar
RouteData.ValuesparaHttpRequest.RouteValues. - Migre
RouteData.DataTokenspara recuperar IDataTokensMetadata dos metadados do ponto final.
A correspondência de URL opera em um conjunto configurável de fases. Em cada fase, a saída é um conjunto de correspondências. O conjunto de jogos pode ser reduzido ainda mais na próxima fase. A implementação de roteamento não garante uma ordem de processamento para pontos de extremidade correspondentes. Todas as correspondências possíveis são processadas de uma só vez. As fases de correspondência de URL ocorrem na seguinte ordem. ASP.NET Núcleo:
- Processa o caminho da URL em relação ao conjunto de pontos de extremidade e seus modelos de rota, coletando todas as correspondências.
- Pega a lista anterior e remove correspondências que falham com restrições de rota aplicadas.
- Pega a lista anterior e remove itens que não correspondem ao conjunto de instâncias MatcherPolicy.
- Usa o EndpointSelector para tomar uma decisão final da lista anterior.
A lista de pontos finais é priorizada de acordo com:
Todos os pontos de extremidade correspondentes são processados em cada fase até que o EndpointSelector seja alcançado. A EndpointSelector é a fase final. Ele escolhe o ponto final de prioridade mais alta entre as correspondências como a melhor opção. Se houver outras partidas com a mesma prioridade da melhor partida, uma exceção de partida ambígua é lançada.
A precedência de rota é calculada com base em um modelo de rota mais específico recebendo uma prioridade mais alta. Por exemplo, considere os modelos /hello e /{message}:
- Ambos correspondem ao caminho da URL
/hello. -
/helloé mais específica e, por conseguinte, mais prioritária.
Em geral, a precedência de rota faz um bom trabalho ao escolher a melhor opção para os tipos de esquemas de URL usados na prática. Use Order apenas quando necessário para evitar uma ambiguidade.
Devido aos tipos de extensibilidade fornecidos pelo roteamento, não é possível para o sistema de roteamento calcular antecipadamente as rotas ambíguas. Considere um exemplo como os modelos de rota /{message:alpha} e /{message:int}:
- A
alpharestrição corresponde apenas a caracteres alfabéticos. - A
intrestrição corresponde apenas a números. - Esses modelos têm a mesma precedência de rota, mas não há um único URL com o qual ambos correspondam.
- Se o sistema de roteamento relatasse um erro de ambiguidade na inicialização, bloquearia esse caso de uso válido.
Warning
A ordem das operações internas UseEndpoints não influencia o comportamento do roteamento, com uma exceção. MapControllerRoute e MapAreaRoute atribuem automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. Isso simula o comportamento de longo prazo dos controladores sem que o sistema de roteamento forneça as mesmas garantias que as implementações de roteamento mais antigas.
Roteamento de ponto de extremidade no ASP.NET Core:
- Não tem o conceito de rotas.
- Não fornece garantias de encomenda. Todos os endpoints são processados de uma só vez.
Precedência do modelo de rota e ordem de seleção do ponto final
A precedência do modelo de rota é um sistema que atribui a cada modelo de rota um valor com base em quão específico ele é. Precedência do modelo de rota:
- Evita a necessidade de ajustar a ordem dos pontos finais em casos comuns.
- Tenta corresponder às expectativas de senso comum do comportamento de encaminhamento.
Por exemplo, considere modelos /Products/List e /Products/{id}. Seria razoável supor que /Products/List é uma correspondência melhor do que /Products/{id} para o caminho /Products/List da URL. Isso funciona porque o segmento /List literal é considerado como tendo melhor precedência do que o segmento /{id}de parâmetro.
Os detalhes de como a precedência funciona são acoplados à forma como os modelos de rota são definidos:
- Modelos com mais segmentos são considerados mais específicos.
- Um segmento com texto literal é considerado mais específico do que um segmento de parâmetro.
- Um segmento de parâmetro com uma restrição é considerado mais específico do que um sem.
- Um segmento complexo é considerado tão específico quanto um segmento de parâmetro com uma restrição.
- Os parâmetros "catch-all" são os menos específicos. Consulte catch-all na seção Modelos de rota para obter informações importantes sobre rotas catch-all.
Conceitos de geração de URL
Geração de URL:
- É o processo pelo qual o roteamento pode criar um caminho de URL com base em um conjunto de valores de rota.
- Permite uma separação lógica entre endpoints e os URLs que lhes acedem.
O roteamento de ponto de extremidade inclui a API LinkGenerator.
LinkGenerator é um serviço singleton disponível na DI. A LinkGenerator API pode ser usada fora do contexto de uma solicitação em execução. Mvc.IUrlHelper e cenários que dependem do IUrlHelper, como Tag Helpers, HTML Helpers e Action Results, usam a API internamente para fornecer recursos de geração de links.
O gerador de links é apoiado pelo conceito de um endereço e esquemas de endereço. Um esquema de endereços é uma forma de determinar os pontos finais que devem ser considerados para a geração de links. Por exemplo, os cenários de nome de rota e valores de rota com os quais muitos usuários estão familiarizados a partir de controladores e Razor Páginas são implementados como um esquema de endereço.
O gerador de links pode vincular a controladores e Razor páginas através dos seguintes métodos de extensão:
Sobrecargas desses métodos aceitam argumentos que incluem o HttpContext. Esses métodos são funcionalmente equivalentes a Url.Action e Url.Page, mas oferecem flexibilidade e opções adicionais.
Os GetPath* métodos são mais semelhantes a Url.Action e Url.Page, na medida em que geram um URI contendo um caminho absoluto. Os GetUri* métodos sempre geram um URI absoluto contendo um esquema e host. Os métodos que aceitam um HttpContext geram um URI no contexto da solicitação em execução. Os valores de rota ambiente , caminho base da URL, esquema e host da solicitação em execução são usados, a menos que sejam substituídos.
LinkGenerator é chamado com um endereço. A geração de um URI ocorre em duas etapas:
- Um endereço está associado a uma lista de terminais que correspondem ao endereço.
- Cada RoutePattern de ponto de extremidade é avaliado até que um padrão de rota que corresponda aos valores fornecidos seja encontrado. A saída resultante é combinada com as outras peças de URI fornecidas ao gerador de link e retornadas.
Os métodos fornecidos por LinkGenerator suportam capacidades padrão de geração de links para qualquer tipo de endereço. A maneira mais conveniente de usar o gerador de links é através de métodos de extensão que executam operações para um tipo de endereço específico:
| Método de extensão | Description |
|---|---|
| GetPathByAddress | Gera um URI com um caminho absoluto com base nos valores fornecidos. |
| GetUriByAddress | Gera um URI absoluto com base nos valores fornecidos. |
Warning
Preste atenção às seguintes implicações dos métodos de chamada LinkGenerator :
Use
GetUri*métodos de extensão com cuidado em uma configuração de aplicativo que não valida oHostcabeçalho das solicitações de entrada. Se o cabeçalhoHostdas solicitações de entrada não for validado, entradas de solicitações não confiáveis poderão ser enviadas de volta ao cliente em URIs em uma vista ou página. Recomendamos que todas as aplicações de produção configurem o seu servidor para validar o cabeçalho doHostcom valores válidos conhecidos.Use LinkGenerator com cuidado em middleware em combinação com
MapouMapWhen.Map*Altera o caminho base da solicitação em execução, o que afeta a saída da geração de link. Todas as LinkGenerator APIs permitem especificar um caminho base. Especifique um caminho base vazio para desfazer o efeito deMap*na geração de links.
Exemplo de middleware
No exemplo a seguir, um middleware usa a LinkGenerator API para criar um link para um método de ação que lista produtos de armazenamento. Ao usar o gerador de links, injetando-o numa classe e chamando GenerateLink, qualquer classe numa aplicação terá acesso:
public class ProductsMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public async Task InvokeAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;
var productsPath = _linkGenerator.GetPathByAction("Products", "Store");
await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}
Modelos de rotas
Os tokens dentro dos {} definem parâmetros de rota que são vinculados se houver uma correspondência na rota. Mais de um parâmetro de rota pode ser definido em um segmento de rota, mas os parâmetros de rota devem ser separados por um valor literal. Por exemplo:
{controller=Home}{action=Index}
não é uma rota válida, porque não há valor literal entre {controller} e {action}. Os parâmetros de rota devem ter um nome e podem ter atributos adicionais especificados.
O texto literal diferente dos parâmetros de rota (por exemplo, {id}) e o separador / de caminho devem corresponder ao texto na URL. A correspondência de texto é insensível a maiúsculas e minúsculas e baseia-se na representação decodificada do caminho da URL. Para corresponder a um delimitador literal de parâmetro de rota { ou }, escape o delimitador repetindo o caractere. Por exemplo {{ ou }}.
Asterisco * ou duplo asterisco **:
- Pode ser usado como um prefixo para um parâmetro de rota para vincular ao restante do URI.
- São chamados de parâmetros catch-all . Por exemplo:
blog/{**slug}- Corresponde a qualquer URI que começa com
blog/e tem qualquer valor após ele. - O valor seguinte
blog/é atribuído ao valor de rota slug.
- Corresponde a qualquer URI que começa com
Warning
Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:
- Uma rota abrangente, por exemplo,
{**slug}" - A rota de abrangência total não processa as solicitações que deveria processar.
- A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.
Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.
Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz escape dos caracteres apropriados quando a rota é utilizada para gerar uma URL, incluindo os caracteres / separadores de caminho. Por exemplo, a rota foo/{*path} com valores de rota { path = "my/path" } gera foo/my%2Fpath. Observe a barra para a frente escapada. Para separar caracteres de caminho de ida e volta, use o prefixo do parâmetro de rota **. A rota foo/{**path} com { path = "my/path" } gera foo/my/path.
Os padrões de URL que tentam capturar um nome de arquivo com uma extensão de arquivo opcional têm considerações adicionais. Por exemplo, considere o modelo files/{filename}.{ext?}. Quando os valores para ambos filename e ext existem, ambos os valores são preenchidos. Se houver apenas um valor para filename na URL, a rota corresponderá porque o . final é opcional. Os seguintes URLs correspondem a esta rota:
/files/myFile.txt/files/myFile
Os parâmetros de rota podem ter valores padrão designados especificando o valor padrão após o nome do parâmetro separado por um sinal de igual (=). Por exemplo, {controller=Home} define Home como o valor padrão para controller. O valor padrão será usado se nenhum valor estiver presente na URL do parâmetro. Os parâmetros de rota são tornados opcionais anexando um ponto de interrogação (?) ao final do nome do parâmetro. Por exemplo, id?. A diferença entre valores opcionais e parâmetros de rota padrão é:
- Um parâmetro de rota com um valor padrão sempre produz um valor.
- Um parâmetro opcional tem um valor somente quando um valor é fornecido pela URL da solicitação.
Os parâmetros de rota podem ter restrições que devem corresponder ao valor de rota ligado à URL. Adicionar : e o nome da restrição após o nome do parâmetro de rota especifica uma restrição embutida em um parâmetro de rota. Se a restrição exigir argumentos, eles serão colocados entre parênteses (...) após o nome da restrição. Várias restrições embutidas podem ser especificadas ao anexar outro nome de restrição.
O nome da restrição e os argumentos são passados para o serviço IInlineConstraintResolver para criar uma instância de IRouteConstraint a ser usada no processamento de URLs. Por exemplo, o modelo blog/{article:minlength(10)} de rota especifica uma minlength restrição com o argumento 10. Para obter mais informações sobre restrições de rota e uma lista das restrições fornecidas pela estrutura, consulte a seção Restrições de rota .
Os parâmetros de rota também podem ter transformadores de parâmetros. Os transformadores de parâmetros transformam o valor de um parâmetro ao gerar links e fazer a correspondência de ações e páginas com URLs. Como as restrições, os transformadores de parâmetros podem ser adicionados em linha a um parâmetro de rota adicionando um : e nome do transformador após o nome do parâmetro de rota. Por exemplo, o modelo blog/{article:slugify} de rota especifica um slugify transformador. Para obter mais informações sobre transformadores de parâmetro, consulte a seção Transformadores de parâmetro .
A tabela a seguir demonstra modelos de rota de exemplo e seu comportamento:
| Modelo de rota | Exemplo de URI correspondente | A URI do pedido… |
|---|---|---|
hello |
/hello |
Corresponde apenas ao único caminho /hello. |
{Page=Home} |
/ |
Combina e configura Page para Home. |
{Page=Home} |
/Contact |
Combina e configura Page para Contact. |
{controller}/{action}/{id?} |
/Products/List |
Mapeia para o Products controlador e List ação. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapeia para o Products controlador e a Details ação com id definido para 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapeia para o Home controlador e o Index método.
id é ignorado. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapeia para o Products controlador e o Index método.
id é ignorado. |
Usar um modelo é geralmente a abordagem mais simples para roteamento. Restrições e padrões também podem ser especificados fora do modelo de rota.
Segmentos complexos
Segmentos complexos são processados combinando delimitadores literais da direita para a esquerda de uma forma não gananciosa . Por exemplo, [Route("/a{b}c{d}")] é um segmento complexo.
Segmentos complexos funcionam de uma maneira particular que deve ser entendida para usá-los com sucesso. O exemplo nesta seção demonstra por que segmentos complexos só funcionam realmente bem quando o texto do delimitador não aparece dentro dos valores dos parâmetros. Usar um regex e, em seguida, extrair manualmente os valores é necessário para casos mais complexos.
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Este é um resumo das etapas que o roteamento executa com o modelo /a{b}c{d} e o caminho /abcdda URL. O | é usado para ajudar a visualizar como o algoritmo funciona:
- O primeiro literal, da direita para a esquerda, é
c. Assim/abcdé pesquisado a partir da direita e encontra/ab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/ab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Não há texto restante e nenhum modelo de rota restante, então esta é uma correspondência.
Aqui está um exemplo de um caso negativo usando o mesmo modelo /a{b}c{d} e o caminho da URL /aabcd. O | é usado para ajudar a visualizar como o algoritmo funciona. Este caso não é uma correspondência, o que é explicado pelo mesmo algoritmo:
- O primeiro literal, da direita para a esquerda, é
c. Assim/aabcdé pesquisado a partir da direita e encontra/aab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/aab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/a|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Neste ponto, há texto
arestante, mas o algoritmo ficou sem modelo de rota para analisar, então isso não é uma correspondência.
Uma vez que o algoritmo de correspondência não é ganancioso:
- Ele corresponde à menor quantidade de texto possível em cada etapa.
- Qualquer caso em que o valor do delimitador apareça dentro dos valores dos parâmetros resulta em não correspondência.
As expressões regulares fornecem muito mais controle sobre seu comportamento correspondente.
A correspondência gananciosa, também conhecida como correspondência não-gulosa, seleciona a maior sequência possível. Não ganancioso encontra a menor sequência de caracteres possível.
Roteamento com caracteres especiais
O roteamento com caracteres especiais pode levar a resultados inesperados. Por exemplo, considere um controlador com o seguinte método de ação:
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
Quando string id contém os seguintes valores codificados, podem ocorrer resultados inesperados:
| ASCII | Encoded |
|---|---|
/ |
%2F |
|
+ |
Os parâmetros de rota nem sempre são decodificados por URL. Este problema poderá ser resolvido no futuro. Para obter mais informações, consulte este problema do GitHub;
Restrições de rota
As restrições de rota são executadas quando ocorre uma correspondência com a URL de entrada e o caminho da URL é tokenizado em valores de rota. As restrições de rota geralmente inspecionam o valor de rota associado por meio do modelo de rota e tomam uma decisão verdadeira ou falsa sobre se o valor é aceitável. Algumas restrições de rota usam dados fora do valor da rota para considerar se a solicitação pode ser roteada. Por exemplo, o HttpMethodRouteConstraint pode aceitar ou rejeitar uma solicitação com base no seu verbo HTTP. As restrições são usadas em solicitações de roteamento e geração de links.
Warning
Não use restrições para validação de entrada. Se as restrições forem usadas para validação de entrada, a entrada inválida resultará em uma 404 resposta Não encontrada. A entrada inválida deve produzir uma 400 solicitação incorreta com uma mensagem de erro apropriada. As restrições de rota são usadas para desambiguar rotas semelhantes, não para validar as entradas para uma rota específica.
A tabela a seguir demonstra exemplos de restrições de rota e seu comportamento esperado:
| restrição | Example | Exemplos de combinações | Notes |
|---|---|---|---|
int |
{id:int} |
123456789, -123456789 |
Corresponde a qualquer número inteiro |
bool |
{active:bool} |
true, FALSE |
Combina true ou false. Case-insensitive |
datetime |
{dob:datetime} |
2016-12-31, 2016-12-31 7:32pm |
Corresponde a um valor válido DateTime na cultura invariante. Ver advertência anterior. |
decimal |
{price:decimal} |
49.99, -1,000.01 |
Corresponde a um valor válido decimal na cultura invariante. Ver advertência anterior. |
double |
{weight:double} |
1.234, -1,001.01e8 |
Corresponde a um valor válido double na cultura invariante. Ver advertência anterior. |
float |
{weight:float} |
1.234, -1,001.01e8 |
Corresponde a um valor válido float na cultura invariante. Ver advertência anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Corresponde a um valor válido Guid |
long |
{ticks:long} |
123456789, -123456789 |
Corresponde a um valor válido long |
minlength(value) |
{username:minlength(4)} |
Rick |
String deve ter pelo menos 4 caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
String não deve ter mais de 8 caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
A cadeia de caracteres deve ter exatamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
A cadeia de caracteres deve ter no mínimo 8 e não mais de 16 caracteres |
min(value) |
{age:min(18)} |
19 |
O valor inteiro deve ser pelo menos 18 |
max(value) |
{age:max(120)} |
91 |
O valor inteiro não deve ser superior a 120 |
range(min,max) |
{age:range(18,120)} |
91 |
O valor inteiro deve ser pelo menos 18, mas não superior a 120 |
alpha |
{name:alpha} |
Rick |
A cadeia de caracteres deve consistir em um ou mais caracteres alfabéticos, como a, - e z, e ser insensível a maiúsculas/minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
String deve corresponder à expressão regular. Veja dicas sobre como definir uma expressão regular. |
required |
{name:required} |
Rick |
Usado para impor que um valor não parâmetro esteja presente durante a geração de URL |
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Várias restrições delimitadas por dois pontos podem ser aplicadas a um único parâmetro. Por exemplo, a restrição a seguir restringe um parâmetro a um valor inteiro igual ou superior a 1:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warning
As restrições de rota que verificam a URL e são convertidas para um tipo CLR sempre usam a cultura invariante. Por exemplo, a conversão para o tipo CLR int ou DateTime. Essas restrições pressupõem que a URL não é localizável. As restrições de rota fornecidas pela estrutura não modificam os valores armazenados nos valores de rota. Todos os valores de rota analisados a partir da URL são armazenados como cadeias de caracteres. Por exemplo, a float restrição tenta converter o valor da rota em um float, mas o valor convertido é usado apenas para verificar se ele pode ser convertido em float.
Expressões regulares em restrições
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Expressões regulares podem ser especificadas como restrições embutidas usando a regex(...) restrição de rota. Os métodos na MapControllerRoute família também aceitam um objeto literal de restrições. Se esse formulário for usado, os valores de cadeia de caracteres serão interpretados como expressões regulares.
O código a seguir usa uma restrição de regex embutida:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
O código a seguir usa um literal de objeto para especificar uma restrição de regex:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
A estrutura ASP.NET Core adiciona RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão regular. Consulte RegexOptions para obter uma descrição desses membros.
As expressões regulares usam delimitadores e tokens semelhantes aos usados pelo roteamento e pela linguagem C#. Os tokens de expressão regular precisam de ser escapados. Para usar a expressão regular ^\d{3}-\d{2}-\d{4}$ numa restrição inline, use uma das seguintes opções:
- Substitua os caracteres
\fornecidos na cadeia de caracteres por caracteres\\no arquivo de origem C# para fazer o escape do caractere de escape da cadeia de caracteres\. - Literais de string verbatim.
Para escapar dos caracteres delimitadores do parâmetro de roteamento {, }, [, ], dobre os caracteres na expressão, por exemplo, {{, }}, [[, ]]. A tabela a seguir mostra uma expressão regular e sua versão com escape:
| Expressão regular | Expressão regular escapada |
|---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
As expressões regulares usadas no roteamento geralmente começam com o ^ caractere e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente terminam com o $ caractere e correspondem ao final da cadeia de caracteres. Os ^ caracteres e $ garantem que a expressão regular corresponda ao valor do parâmetro route inteiro. Sem os ^ caracteres e $ , a expressão regular corresponde a qualquer substring dentro da cadeia de caracteres, o que geralmente é indesejável. A tabela a seguir fornece exemplos e explica por que eles correspondem ou não correspondem:
| Expression | String | Match | Comment |
|---|---|---|---|
[a-z]{2} |
hello | Yes | Correspondências de substring |
[a-z]{2} |
123abc456 | Yes | Correspondências de substring |
[a-z]{2} |
mz | Yes | Expressão de correspondências |
[a-z]{2} |
MZ | Yes | Não diferencia maiúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Ver ^ e $ acima |
^[a-z]{2}$ |
123abc456 | No | Ver ^ e $ acima |
Para obter mais informações sobre sintaxe de expressão regular, consulte Expressões regulares do .NET Framework.
Para restringir um parâmetro a um conjunto conhecido de valores possíveis, use uma expressão regular. Por exemplo, {action:regex(^(list|get|create)$)} só corresponde ao valor da action rota para list, get, ou create. Se passada para o dicionário de restrições, a cadeia de caracteres ^(list|get|create)$ é equivalente. As restrições passadas no dicionário de restrições que não correspondem a uma das restrições conhecidas também são tratadas como expressões regulares. As restrições passadas dentro de um modelo que não correspondem a uma das restrições conhecidas não são tratadas como expressões regulares.
Restrições de rota personalizadas
Restrições de rota personalizadas podem ser criadas implementando a IRouteConstraint interface. A IRouteConstraint interface contém Match, que retorna true se a restrição for satisfeita e false de outra forma.
Restrições de rota personalizadas raramente são necessárias. Antes de implementar uma restrição de rota personalizada, considere alternativas, como vinculação de modelo.
A pasta ASP.NET Core Constraints fornece bons exemplos de criação de restrições. Por exemplo, GuidRouteConstraint.
Para utilizar um IRouteConstraint personalizado, o tipo de restrição de rota deve ser registado com o ConstraintMap da aplicação no contêiner de serviços. A ConstraintMap é um dicionário que mapeia chaves de restrição de rota para as implementações IRouteConstraint que validam essas restrições.
ConstraintMap de um aplicativo pode ser atualizado em Program.cs como parte de uma chamada de AddRouting ou configurando RouteOptions diretamente com builder.Services.Configure<RouteOptions>. Por exemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
A restrição anterior é aplicada no seguinte código:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
A implementação de NoZeroesRouteConstraint impede que 0 seja usado em um parâmetro de rota.
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
public bool Match(
HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
O código anterior:
- Previne
0no{id}segmento da rota. - É mostrado para fornecer um exemplo básico de implementação de uma restrição personalizada. Ele não deve ser usado em um aplicativo de produção.
O código a seguir é uma abordagem melhor para evitar que um id contendo a 0 seja processado:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
O código anterior tem as seguintes vantagens em relação à NoZeroesRouteConstraint abordagem:
- Não requer uma restrição personalizada.
- Ele retorna um erro mais descritivo quando o parâmetro route inclui
0.
Transformadores de parâmetros
Transformadores de parâmetros:
- Execute ao gerar um link usando LinkGenerator.
- Implementar Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
- São configurados usando ConstraintMap.
- Pegue o valor de rota do parâmetro e transforme-o em um novo valor de cadeia de caracteres.
- O resultado é usar o valor transformado no link gerado.
Por exemplo, um transformador de parâmetro personalizado slugify no padrão de rota blog\{article:slugify} com Url.Action(new { article = "MyTestArticle" }) gera blog\my-test-article.
Considere a seguinte IOutboundParameterTransformer implementação:
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
Para usar um transformador de parâmetro em um padrão de rota, configure-o usando ConstraintMap em Program.cs:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
A estrutura ASP.NET Core usa transformadores de parâmetro para transformar o(s) URI(s) onde um ponto final é resolvido. Por exemplo, transformadores de parâmetros transformam os valores de rota usados para corresponder a area, controller, action e page.
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Com o modelo de rota anterior, a ação SubscriptionManagementController.GetAll é correspondida com o URI /subscription-management/get-all. Um transformador de parâmetros não altera os valores de rota usados para gerar um link. Por exemplo, Url.Action("GetAll", "SubscriptionManagement") produz /subscription-management/get-all.
O ASP.NET Core fornece convenções de API para o uso de transformadores de parâmetros com rotas geradas:
- A Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention convenção MVC aplica um transformador de parâmetro especificado a todas as rotas de atributos no aplicativo. O transformador de parâmetros transforma os tokens da rota do atributo conforme são substituídos. Para obter mais informações, consulte Usar um transformador de parâmetro para personalizar a substituição de token.
- Razor O Pages usa a convenção da PageRouteTransformerConvention API. Esta convenção aplica um transformador de parâmetros especificado a todas as Páginas descobertas Razor automaticamente. O transformador de parâmetros transforma os segmentos de pasta e nome de arquivo das rotas Pages Razor . Para obter mais informações, consulte Usar um transformador de parâmetros para personalizar rotas de página.
Referência de geração de URL
Esta seção contém uma referência para o algoritmo implementado pela geração de URL. Na prática, os exemplos de geração de URL mais complexos utilizam controladores ou Páginas Razor. Consulte Roteamento em controladores para obter informações adicionais.
O processo de geração de URL começa com uma chamada para LinkGenerator.GetPathByAddress ou um método semelhante. O método é fornecido com um endereço, um conjunto de valores de rota e, opcionalmente, informações sobre a solicitação atual da HttpContext.
O primeiro passo é utilizar o endereço para resolver um conjunto de pontos de extremidade candidatos, usando um IEndpointAddressScheme<TAddress> que corresponda ao tipo do endereço.
Uma vez que o conjunto de candidatos é encontrado pelo esquema de endereçamento, os pontos de extremidade são ordenados e processados iterativamente até que uma operação de geração de URL seja bem-sucedida. A geração de URL não verifica ambiguidades, o primeiro resultado retornado é o resultado final.
Solução de problemas de geração de URL com registro em log
A primeira etapa na solução de problemas de geração de URL é definir o nível de log de Microsoft.AspNetCore.Routing como TRACE.
LinkGenerator registra muitos detalhes sobre seu processamento, o que pode ser útil para solucionar problemas.
Consulte Referência de geração de URL para obter detalhes sobre a geração de URL.
Addresses
Endereços são o conceito na geração de URLs usado para associar uma chamada no gerador de ligações a um conjunto de endpoints candidatos.
Os endereços são um conceito extensível que vem com duas implementações por padrão:
- Usando o nome do ponto de extremidade (
string) como endereço:- Fornece funcionalidade semelhante ao nome da rota do MVC.
- Usa o tipo de metadados IEndpointNameMetadata.
- Resolve a cadeia de caracteres fornecida em relação aos metadados de todos os pontos de extremidade registrados.
- Lança uma exceção na inicialização se vários endpoints usarem o mesmo nome.
- Recomendado para uso geral fora de controladores e Razor Páginas.
- Usando valores de rota (RouteValuesAddress) como endereço:
- Fornece funcionalidade semelhante à dos controladores e do Pages para a geração de URLs herdadas.
- Muito complexo para expandir e depurar.
- Fornece a implementação usada por
IUrlHelper, Tag Helpers, HTML Helpers, Action Results, etc.
A função do esquema de endereços é fazer a associação entre o endereço e os pontos de extremidade correspondentes por critérios arbitrários:
- O esquema de nomes de ponto de extremidade executa uma pesquisa básica de dicionário.
- O esquema de valores de rota tem um algoritmo de melhor subconjunto de conjunto complexo.
Valores ambientais e valores explícitos
A partir da solicitação atual, o roteamento acessa os valores de rota da solicitação HttpContext.Request.RouteValuesatual. Os valores associados à solicitação atual são chamados de valores ambientais. Para fins de clareza, a documentação refere-se aos valores de rota passados para os métodos como valores explícitos.
O exemplo a seguir mostra valores ambientais e valores explícitos. Ele fornece valores de ambiente da solicitação atual e valores explícitos:
public class WidgetController : ControllerBase
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator) =>
_linkGenerator = linkGenerator;
public IActionResult Index()
{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;
return Content(indexPath);
}
// ...
O código anterior:
- Devoluções
/Widget/Index/17 - Obtém LinkGenerator via DI.
O código a seguir fornece apenas valores explícitos e nenhum valor ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
O método anterior retorna /Home/Subscribe/17
O seguinte código no WidgetController retorna /Widget/Subscribe/17:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
O código a seguir fornece o controlador a partir de valores ambientais na solicitação atual e valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
No código anterior:
-
/Gadget/Edit/17é retornado. - Url obtém o IUrlHelper.
-
Action gera uma URL com um caminho absoluto para um método de ação. A URL contém o nome especificado
actione os valoresroute.
O código a seguir fornece valores de ambiente da solicitação atual e valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
O código anterior define url para /Edit/17 quando a página Editar Razor contém a seguinte diretiva de página:
@page "{id:int}"
Se a página Editar não contiver o "{id:int}" modelo de rota, url será /Edit?id=17.
O comportamento dos MVCs IUrlHelper adiciona uma camada de complexidade, além das regras descritas aqui:
-
IUrlHelpersempre fornece os valores de rota da solicitação atual como valores ambientais. -
IUrlHelper.Action sempre copia os valores atuais
actionecontrollerde rota como valores explícitos, a menos que sejam substituídos pelo desenvolvedor. -
IUrlHelper.Page sempre copia o valor da rota atual
pagecomo um valor explícito, a menos que seja substituído. -
IUrlHelper.Pagesempre substitui o valor da rota atualhandlerpornullvalores explícitos, a menos que sejam substituídos.
Os usuários muitas vezes são surpreendidos pelos detalhes comportamentais dos valores ambientais, porque o MVC parece não seguir suas próprias regras. Por razões históricas e de compatibilidade, certos valores de rota, como action, controller, page, e handler têm seu próprio comportamento de caso especial.
A funcionalidade equivalente fornecida por LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica essas anomalias de IUrlHelper para compatibilidade.
Processo de geração de URL
Uma vez encontrado o conjunto de pontos de extremidade candidatos, o algoritmo de geração de URL:
- Processa os extremos iterativamente.
- Devolve o primeiro resultado bem-sucedido.
A primeira etapa deste processo é chamada de invalidação de valores de rota. A invalidação do valor de rota é o processo pelo qual o roteamento decide quais valores de rota dos valores ambientais devem ser usados e quais devem ser ignorados. Cada valor ambiental é considerado e combinado com os valores explícitos ou ignorado.
A melhor maneira de pensar sobre o papel dos valores de ambiente é que eles tentam reduzir o número de teclas que os desenvolvedores de aplicativos precisam pressionar, em alguns casos comuns. Tradicionalmente, os cenários em que os valores ambientais são úteis estão relacionados ao MVC:
- Ao vincular a outra ação no mesmo controlador, o nome do controlador não precisa ser especificado.
- Ao vincular a outro controlador na mesma área, o nome da área não precisa ser especificado.
- Ao vincular ao mesmo método de ação, os valores de rota não precisam ser especificados.
- Ao vincular a outra parte do aplicativo, você não deseja transferir valores de rota que não têm significado nessa parte do aplicativo.
As chamadas para LinkGenerator ou IUrlHelper que retornam null geralmente são causadas por não entender a invalidação do valor da rota. Solucione problemas de invalidação de valor de rota especificando claramente mais valores de rota para ver se isso resolve o problema.
A invalidação do valor de rota funciona com base na suposição de que o esquema de URL do aplicativo é hierárquico, com uma hierarquia formada da esquerda para a direita. Considere o modelo {controller}/{action}/{id?} de rota do controlador básico para ter uma noção intuitiva de como isso funciona na prática. Uma alteração em um valor invalida todos os valores de rota que aparecem à direita. Isso reflete o pressuposto sobre hierarquia. Se a aplicação tiver um valor de ambiente para id, e a operação especificar um valor diferente para o controller:
-
idnão será reutilizado porque{controller}está à esquerda de{id?}.
Alguns exemplos que demonstram este princípio:
- Se os valores explícitos contiverem um valor para
id, o valor de ambiente paraidserá ignorado. Os valores ambientais paracontrollereactionpodem ser usados. - Se os valores explícitos contiverem um valor para
action, qualquer valor ambiente paraactionserá ignorado. Os valores ambientais paracontrollerpodem ser usados. Se o valor explícito paraactionfor diferente do valor ambiente paraaction, oidvalor não será usado. Se o valor explícito paraactionfor o mesmo que o valor ambiente paraaction, oidvalor pode ser usado. - Se os valores explícitos contiverem um valor para
controller, qualquer valor ambiente paracontrollerserá ignorado. Se o valor explícito paracontrollerfor diferente do valor ambiente paracontroller, os valoresactioneidnão serão usados. Se o valor explícito paracontrollerfor o mesmo que o valor ambiente paracontroller, osactionvalores eidpodem ser usados.
Este processo é ainda mais complicado pela existência de rotas de atributos e rotas convencionais dedicadas. Controladores de rotas convencionais, como {controller}/{action}/{id?}, especificam uma hierarquia usando parâmetros de rota. Para rotas convencionais dedicadas e rotas de atributos para controladores e Razor páginas:
- Há uma hierarquia de valores de rota.
- Eles não aparecem no modelo.
Para esses casos, a geração de URL define o conceito de valores necessários . Os endpoints criados por controladores e Razor Pages têm valores obrigatórios especificados que permitem que a invalidação de valores de rota funcione corretamente.
Detalhes do algoritmo de invalidação do valor da rota:
- Os nomes de valor necessários são combinados com os parâmetros de rota e, em seguida, processados da esquerda para a direita.
- Para cada parâmetro, o valor ambiente e o valor explícito são comparados:
- Se o valor ambiental e o valor explícito forem os mesmos, o processo continua.
- Se o valor ambiente estiver presente e o valor explícito não, o valor ambiente será usado ao gerar a URL.
- Se o valor ambiental não estiver presente e o valor explícito estiver, rejeite o valor ambiental e todos os valores ambientais subsequentes.
- Se o valor ambiente e o valor explícito estiverem presentes e os dois valores forem diferentes, rejeite o valor ambiente e todos os valores ambientais subsequentes.
Neste ponto, a operação de geração de URL está pronta para avaliar as restrições de rota. O conjunto de valores aceitos é combinado com os valores padrão do parâmetro, que são fornecidos às restrições. Se todas as restrições passarem, a operação continuará.
Em seguida, os valores aceitos podem ser usados para expandir o modelo de rota. O modelo de rota é processado:
- Da esquerda para a direita.
- Cada parâmetro tem seu valor aceito substituído.
- Com os seguintes casos especiais:
- Se os valores aceitos estiverem faltando um valor e o parâmetro tiver um valor padrão, o valor padrão será usado.
- Se os valores aceitos estiverem faltando um valor e o parâmetro for opcional, o processamento continuará.
- Se qualquer parâmetro de rota à direita de um parâmetro opcional ausente tiver um valor, a operação falhará.
- Os parâmetros contíguos com valor padrão e os parâmetros opcionais são recolhidos sempre que possível.
Os valores explicitamente fornecidos que não correspondem a um segmento da rota são adicionados à cadeia de caracteres de consulta. A tabela a seguir mostra o resultado ao usar o modelo {controller}/{action}/{id?}de rota.
| Valores Ambientais | Valores explícitos | Result |
|---|---|---|
| controlador = "Home" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | controller = "Ordem", ação = "Sobre" | /Order/About |
| controlador = "Home", cor = "Vermelho" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | ação = "Sobre", cor = "Vermelho" | /Home/About?color=Red |
Problemas com a invalidação de valores de rota
O código a seguir mostra um exemplo de um esquema de geração de URL que não é suportado pelo roteamento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
No código anterior, o culture parâmetro route é usado para localização. O desejo é que o culture parâmetro seja sempre aceite como um valor ambiental. No entanto, o culture parâmetro não é aceite como um valor ambiente devido à forma como os valores necessários funcionam:
- No modelo de rota
"default", o parâmetro de rotacultureestá à esquerda decontroller, portanto, alterações emcontrollernão invalidarãoculture. -
"blog"no modelo de rota, o parâmetro de rotacultureé considerado estar à direita docontroller, que aparece nos valores necessários.
Analisar caminhos de URL com LinkParser
A LinkParser classe adiciona suporte para interpretar um caminho de URL num conjunto de valores de rota. O ParsePathByEndpointName método usa um nome de ponto de extremidade e um caminho de URL e retorna um conjunto de valores de rota extraídos do caminho de URL.
No exemplo de controlador a seguir, a ação de GetProduct usa um modelo de rota de api/Products/{id} e possui um Name de GetProduct:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
Na mesma classe de controlador, a AddRelatedProduct ação espera um caminho de URL, pathToRelatedProduct, que pode ser fornecido como um parâmetro de cadeia de caracteres de consulta:
[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];
// ...
No exemplo anterior, a ação AddRelatedProduct extrai o valor da rota id do caminho do URL. Por exemplo, com um caminho de URL de /api/Products/1, o valor relatedProductId é definido como 1. Essa abordagem permite que os clientes da API usem caminhos de URL ao se referirem a recursos, sem exigir conhecimento de como essa URL é estruturada.
Configurar metadados de ponto de extremidade
Os links a seguir disponibilizam informações sobre como configurar os metadados do ponto final:
- Habilitar Cors com roteamento de ponto final
-
Exemplo de IAuthorizationPolicyProvider usando um atributo personalizado
[MinimumAgeAuthorize] - Testar a autenticação com o atributo [Autorizar]
- RequireAuthorization
- Selecionando o esquema com o atributo [Autorizar]
- Aplicar políticas usando o atributo [Autorizar]
- Autorização baseada em função no ASP.NET Core
Correspondência de host em rotas com RequireHost
RequireHost Aplica uma restrição à rota que requer o host especificado. O RequireHost parâmetro pode ser: ou [Host]
- Anfitrião:
www.domain.com, associawww.domain.coma qualquer porta. - Host com wildcard:
*.domain.com, corresponde awww.domain.com,subdomain.domain.comouwww.subdomain.domain.comem qualquer porta. - Porta:
*:5000, corresponde à porta 5000 em qualquer host. - Host e porta:
www.domain.com:5000ou*.domain.com:5000, corresponde ao host e à porta.
Vários parâmetros podem ser especificados usando RequireHost ou [Host]. A restrição corresponde a hosts válidos para qualquer um dos parâmetros. Por exemplo, [Host("domain.com", "*.domain.com")] corresponde a domain.com, www.domain.com e subdomain.domain.com.
O código a seguir usa RequireHost para exigir o host especificado na rota:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
O código a seguir usa o [Host] atributo no controlador para exigir qualquer um dos hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Quando o [Host] atributo é aplicado ao controlador e ao método de ação:
- O atributo é utilizado na ação.
- O atributo controller é ignorado.
Diretrizes de desempenho para roteamento
Quando uma aplicação tem problemas de desempenho, geralmente suspeita-se do roteamento como a causa. A razão pela qual o roteamento é suspeito é que estruturas como controladores e Razor Pages relatam a quantidade de tempo gasto dentro da estrutura em suas mensagens de registro. Quando há uma diferença significativa entre o tempo relatado pelos controladores e o tempo total da solicitação:
- Os desenvolvedores eliminam o código do aplicativo como a fonte do problema.
- É comum assumir que o roteamento é a causa.
O desempenho do roteamento é testado usando milhares de endpoints. É improvável que um aplicativo típico encontre um problema de desempenho apenas por ser muito grande. A causa raiz mais comum do desempenho de roteamento lento geralmente é um middleware personalizado com mau comportamento.
Este exemplo de código a seguir demonstra uma técnica básica para restringir a origem do atraso:
var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});
app.MapGet("/", () => "Timing Test.");
Para agendar o temporização de rotas:
- Intercale cada middleware com uma cópia do middleware de temporização mostrado no código anterior.
- Adicione um identificador exclusivo para correlacionar os dados de tempo com o código.
Esta é uma maneira básica de reduzir o atraso quando ele é significativo, por exemplo, mais do que 10ms. Subtrair Time 2 de Time 1 indica o tempo gasto dentro do middleware UseRouting.
O código a seguir usa uma abordagem mais compacta para o código de tempo anterior:
public sealed class AutoStopwatch : IDisposable
{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());
public void Dispose()
{
if (_disposed)
{
return;
}
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(async (context, next) =>
{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");
Recursos de roteamento potencialmente caros
A lista a seguir fornece algumas informações sobre os recursos de roteamento que são relativamente caros em comparação com modelos de rota básicos:
- Expressões regulares: É possível escrever expressões regulares que são complexas ou têm um longo tempo de execução com uma pequena quantidade de entrada.
- Segmentos complexos (
{x}-{y}-{z}):- São significativamente mais caros do que analisar um segmento de caminho de URL regular.
- Resulte em muito mais substrings a serem alocadas.
- Acesso síncrono a dados: muitos aplicativos complexos têm acesso ao banco de dados como parte de seu roteamento. Use pontos de extensibilidade como MatcherPolicy e EndpointSelectorContext, que são assíncronos.
Orientações para grandes tabelas de rotas
Por padrão, ASP.NET Core usa um algoritmo de roteamento que troca memória por tempo de CPU. Isso tem o bom efeito de que o tempo de correspondência de rota depende apenas do comprimento do caminho a ser correspondido e não do número de rotas. No entanto, essa abordagem pode ser potencialmente problemática em alguns casos, quando o aplicativo tem um grande número de rotas (em milhares) e há uma grande quantidade de prefixos variáveis nas rotas. Por exemplo, se as rotas tiverem parâmetros nos primeiros segmentos da rota, como {parameter}/some/literal.
É improvável que um aplicativo se depare com uma situação em que isso seja um problema, a menos que:
- Há um grande número de rotas no aplicativo usando esse padrão.
- Há um grande número de rotas no aplicativo.
Como determinar se um aplicativo está a enfrentar o problema da grande tabela de rotas.
- Existem dois sintomas a procurar:
- O aplicativo é lento para iniciar na primeira solicitação.
- Observe que isso é necessário, mas não suficiente. Há muitos outros problemas que não são de rota que podem causar inicialização lenta do aplicativo. Verifique a condição abaixo para determinar com precisão que o aplicativo está se deparando com essa situação.
- A aplicação consome muita memória durante a inicialização e um dump de memória mostra um grande número de
Microsoft.AspNetCore.Routing.Matching.DfaNodeinstâncias.
- O aplicativo é lento para iniciar na primeira solicitação.
Como resolver este problema
Existem várias técnicas e otimizações que podem ser aplicadas a rotas que irão melhorar amplamente este cenário:
- Aplique restrições de rota aos seus parâmetros, por exemplo
{parameter:int}, ,{parameter:guid},{parameter:regex(\\d+)}, etc. sempre que possível.- Isso permite que o algoritmo de roteamento otimize internamente as estruturas usadas para correspondência e reduza drasticamente a memória usada.
- Na grande maioria dos casos, isso será suficiente para voltar a um comportamento aceitável.
- Altere as rotas para mover parâmetros para segmentos posteriores no modelo.
- Isso reduz o número de "percursos" possíveis para corresponder a um ponto de extremidade num determinado trajeto.
- Use uma rota dinâmica e execute o mapeamento para um controlador/página dinamicamente.
- Isto pode ser conseguido usando
MapDynamicControllerRouteeMapDynamicPageRoute.
- Isto pode ser conseguido usando
Guia de orientação para autores de bibliotecas
Esta seção contém orientações para autores de bibliotecas que se baseiam no roteamento. Esses detalhes destinam-se a garantir que os desenvolvedores de aplicativos tenham uma boa experiência usando bibliotecas e estruturas que estendem o roteamento.
Definir endpoints
Para criar uma estrutura que use roteamento para correspondência de URLs, comece por definir uma experiência de usuário que se construa sobre UseEndpoints.
CONSTRUA em cima do IEndpointRouteBuilder. Isso permite que os usuários componham sua estrutura com outros recursos ASP.NET Core sem confusão. Cada modelo ASP.NET Core inclui roteamento. Suponha que o roteamento esteja presente e seja familiar para os usuários.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DO retorne um tipo concreto selado de uma chamada para MapMyFramework(...) que implementa IEndpointConventionBuilder. A maioria dos métodos de estrutura Map... segue esse padrão. A IEndpointConventionBuilder interface:
- Permite que os metadados sejam compostos.
- É alvo de uma variedade de métodos de extensão.
Declarar o teu próprio tipo permite-te adicionar funcionalidades específicas do framework à ferramenta de construção. Não há problema em envolver um construtor declarado pela estrutura e encaminhar chamadas para ele.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE escrever o seu próprio EndpointDataSource.
EndpointDataSource é a primitiva de baixo nível para declarar e atualizar uma coleção de pontos de extremidade.
EndpointDataSource é uma API poderosa usada por controladores e Razor páginas.
Os testes de roteamento têm um exemplo básico de uma fonte de dados não atualizada.
NÃO tente registrar um EndpointDataSource por padrão. Exigir que os usuários registrem sua estrutura no UseEndpoints. A filosofia do encaminhamento é que nada é incluído por padrão, e que UseEndpoints é o lugar para registrar endpoints.
Criando middleware integrado ao roteamento
CONSIDERE definir tipos de metadados como uma interface.
DO possibilitam o uso de tipos de metadados como um atributo em classes e métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks como controladores e Razor Pages suportam a aplicação de atributos de metadados a tipos e métodos. Se você declarar tipos de metadados:
- Torne-os acessíveis como atributos.
- A maioria dos usuários está familiarizada com a aplicação de atributos.
Declarar um tipo de metadados como uma interface adiciona outra camada de flexibilidade:
- As interfaces são combináveis.
- Os desenvolvedores podem declarar seus próprios tipos que combinam várias políticas.
DO possibilitam a substituição de metadados, conforme mostrado no exemplo a seguir:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
A melhor maneira de seguir estas diretrizes é evitar a definição de metadados de marcadores:
- Não procure apenas pela presença de um tipo de metadados.
- Defina uma propriedade nos metadados e verifique a propriedade.
A coleta de metadados é ordenada e suporta a substituição por prioridade. No caso dos controladores, os metadados sobre o método de ação são mais específicos.
FAÇA com que o middleware seja útil com e sem roteamento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como exemplo dessa diretriz, considere o UseAuthorization middleware. O middleware de autorização permite definir uma política de fallback.
A política de fallback, se especificada, aplica-se a:
- Pontos de extremidade sem uma política especificada.
- Solicitações que não correspondem a um endpoint.
Isso torna o middleware de autorização útil fora do contexto de roteamento. O middleware de autorização pode ser usado para programação de middleware tradicional.
Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Recursos adicionais
O roteamento é responsável por corresponder às solicitações HTTP de entrada e enviar essas solicitações para os pontos de extremidade executáveis do aplicativo. Os pontos de extremidade são as unidades de código executável de tratamento de solicitações do aplicativo. Os pontos de extremidade são definidos na aplicação e configurados quando a aplicação é iniciada. O processo de correspondência de ponto de extremidade pode extrair valores da URL da solicitação e fornecer esses valores para processamento de solicitação. Usando informações de endpoint do aplicativo, o roteamento também é capaz de gerar URLs que correspondem a endpoints.
Os aplicativos podem configurar o roteamento usando:
- Controllers
- Razor Páginas
- SignalR
- Serviços gRPC
- Middleware compatível com endpoint, como Verificações de Integridade.
- Delegados e lambdas registados com encaminhamento.
Este documento abrange detalhes de baixo nível do roteamento ASP.NET Core. Para obter informações sobre como configurar o roteamento:
- Para controladores, consulte Roteamento para ações do controlador no ASP.NET Core.
- Para Razor convenções do Pages, consulte Razor Rotas de páginas e convenções de aplicativos no ASP.NET Core.
O sistema de roteamento de ponto final descrito neste documento se aplica ao ASP.NET Core 3.0 ou posterior. Para obter informações sobre o sistema de roteamento anterior com base no IRouter, selecione a versão ASP.NET Core 2.1 usando uma das seguintes abordagens:
- O seletor de versão para uma versão anterior.
- Selecione roteamento no ASP.NET Core 2.1.
Visualizar ou descarregar amostra de código (como descarregar)
Os exemplos de download para este documento são habilitados por uma classe específica Startup . Para executar um exemplo específico, modifique Program.cs para chamar a classe desejada Startup .
Noções básicas de roteamento
Todos os modelos ASP.NET Core incluem roteamento no código gerado. O roteamento é registrado no pipeline de middleware no Startup.Configure.
O código a seguir mostra um exemplo básico de roteamento:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
O roteamento utiliza um par de middleware, configurado por UseRouting e UseEndpoints:
-
UseRoutingAdiciona mapeamento de rotas ao pipeline de middleware. Esse middleware examina o conjunto de pontos de extremidade definidos no aplicativo e seleciona a melhor correspondência com base na solicitação. -
UseEndpointsAdiciona a execução de endpoints ao pipeline de middleware. Executa o delegado associado ao ponto final selecionado.
O exemplo anterior inclui uma única rota para codificar no ponto de extremidade usando o método MapGet:
- Quando uma solicitação HTTP
GETé enviada para a URL/raiz :- O delegado de solicitação mostrado executa.
-
Hello World!é escrito na resposta HTTP. Por padrão, a URL/raiz éhttps://localhost:5001/.
- Se o método de solicitação não for
GETou a URL raiz não for/, nenhuma rota corresponderá e um HTTP 404 será retornado.
Endpoint
O MapGet método é usado para definir um ponto de extremidade. Um endpoint é algo que pode ser:
- Selecionado, combinando o URL e o método HTTP.
- Executado, ao correr o delegado.
Os pontos de extremidade que podem ser correspondidos e executados pela aplicação são configurados em UseEndpoints. Por exemplo, MapGet, MapPost, e métodos semelhantes conectam delegados de solicitação ao sistema de roteamento. Métodos adicionais podem ser usados para conectar os recursos do framework ASP.NET Core ao sistema de roteamento.
- MapRazorPages para Razor Páginas
- MapControllers para controladores
- MapHub<THub> para SignalR
- MapGrpcService<TService> para gRPC
O exemplo a seguir mostra o roteamento com um modelo de rota mais sofisticado:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello/{name:alpha}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
});
A cadeia de caracteres /hello/{name:alpha} é um modelo de rota. É utilizado para configurar a forma como o ponto de extremidade é alinhado. Neste caso, o modelo corresponde:
- Um URL como
/hello/Ryan - Qualquer caminho de URL que comece com
/hello/seguido por uma sequência de caracteres alfabéticos.:alphaAplica uma restrição de rota que corresponde apenas a caracteres alfabéticos. As restrições de rota são explicadas mais adiante neste documento.
O segundo segmento do caminho da URL, {name:alpha}:
- Está vinculado ao
nameparâmetro. - É capturado e armazenado em HttpRequest.RouteValues.
O sistema de roteamento de ponto final descrito neste documento é novo a partir do ASP.NET Core 3.0. No entanto, todas as versões do ASP.NET Core suportam o mesmo conjunto de recursos de modelo de rota e restrições de rota.
O exemplo a seguir mostra o roteamento com verificações de integridade e autorização:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Matches request to an endpoint.
app.UseRouting();
// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
app.UseAuthentication();
app.UseAuthorization();
// Execute the matched endpoint.
app.UseEndpoints(endpoints =>
{
// Configure the Health Check endpoint and require an authorized user.
endpoints.MapHealthChecks("/healthz").RequireAuthorization();
// Configure another endpoint, no authorization requirements.
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
Se quiser ver os comentários de código traduzidos para outros idiomas além do inglês, avise-nos nesta discussão do GitHub .
O exemplo anterior demonstra como:
- O middleware de autorização pode ser usado com roteamento.
- Os pontos de extremidade podem ser usados para configurar o comportamento de autorização.
A MapHealthChecks chamada adiciona um endpoint de verificação de saúde. O encadeamento de RequireAuthorization nesta chamada anexa uma política de autorização ao endpoint.
Chamando UseAuthentication e UseAuthorization adiciona o middleware de autenticação e autorização. Esses middleware são colocados entre UseRouting e UseEndpoints para que possam:
- Veja qual ponto de extremidade foi selecionado pela
UseRouting. - Aplique uma política de autorização antes de UseEndpoints enviar para o destino.
Metadados do ponto final
No exemplo anterior, há dois pontos de extremidade, mas apenas o ponto de extremidade de verificação de integridade tem uma política de autorização anexada. Se a solicitação corresponder ao ponto de extremidade de verificação de integridade, /healthz, uma verificação de autorização será executada. Isso demonstra que os pontos de extremidade podem ter dados extras anexados a eles. Esses dados extra são chamados metadados de ponto final:
- Os metadados podem ser processados por middleware com reconhecimento de roteamento.
- Os metadados podem ser de qualquer tipo .NET.
Conceitos de roteamento
O sistema de roteamento se baseia no pipeline de middleware adicionando o poderoso conceito de ponto final . Os pontos de extremidade representam unidades da funcionalidade do aplicativo que são distintas entre si em termos de roteamento, autorização e dos vários sistemas do ASP.NET Core.
Definição de Endpoint no ASP.NET Core
Um ponto de extremidade ASP.NET Core é:
- Executável: Possui um RequestDelegate.
- Extensível: Tem uma coleção de metadados .
- Selecionável: Opcionalmente, tem informações de roteamento.
- Enumerável: A coleção de pontos de extremidade pode ser listada ao recuperar o EndpointDataSource de DI.
O código a seguir mostra como recuperar e inspecionar o ponto de extremidade correspondente à solicitação atual:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.Use(next => context =>
{
var endpoint = context.GetEndpoint();
if (endpoint is null)
{
return Task.CompletedTask;
}
Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
if (endpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine("Endpoint has route pattern: " +
routeEndpoint.RoutePattern.RawText);
}
foreach (var metadata in endpoint.Metadata)
{
Console.WriteLine($"Endpoint has metadata: {metadata}");
}
return Task.CompletedTask;
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
O ponto de extremidade, se selecionado, pode ser obtido a partir do HttpContext. Suas propriedades podem ser inspecionadas. Os objetos de ponto de extremidade são imutáveis e não podem ser modificados após a criação. O tipo mais comum de ponto de extremidade é o RouteEndpoint.
RouteEndpoint Inclui informações que permitem que ele seja selecionado pelo sistema de roteamento.
No código anterior, app.Use configura um middleware inline.
O código a seguir mostra que, dependendo de onde app.Use é chamado no pipeline, pode não haver um ponto de extremidade:
// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
app.UseRouting();
// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
app.UseEndpoints(endpoints =>
{
// Location 3: runs when this endpoint matches
endpoints.MapGet("/", context =>
{
Console.WriteLine(
$"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return Task.CompletedTask;
}).WithDisplayName("Hello");
});
// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
return next(context);
});
Este exemplo anterior adiciona Console.WriteLine instruções que exibem se um endpoint foi selecionado ou não. Para maior clareza, o exemplo atribui um nome de exibição ao ponto de extremidade fornecido / .
Executar este código com um URL de / exibe:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Executar este código com qualquer outro URL exibe:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Esta saída demonstra que:
- O ponto de extremidade é sempre nulo antes
UseRoutingde ser chamado. - Se uma correspondência for encontrada, o ponto de extremidade não será nulo entre
UseRoutinge UseEndpoints. - O
UseEndpointsmiddleware é terminal quando uma correspondência é encontrada. O middleware do terminal é definido posteriormente neste documento. - O middleware após
UseEndpointsexecuta somente quando nenhuma correspondência é encontrada.
O UseRouting middleware utiliza o método SetEndpoint para anexar o endpoint ao contexto atual. É possível substituir o UseRouting middleware por lógica personalizada e ainda obter os benefícios do uso de endpoints. Os pontos de extremidade são uma primitiva de baixo nível, como o middleware, e não são acoplados à implementação de roteamento. A maioria dos aplicativos não precisa substituir UseRouting por lógica personalizada.
O UseEndpoints middleware foi projetado para ser usado em conjunto com o UseRouting middleware. A lógica principal para executar um endpoint é simples. Utilize GetEndpoint para recuperar o ponto de extremidade e, em seguida, invoque a propriedade RequestDelegate.
O código a seguir demonstra como o middleware pode influenciar ou reagir ao roteamento:
public class IntegratedMiddlewareStartup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Location 1: Before routing runs. Can influence request before routing runs.
app.UseHttpMethodOverride();
app.UseRouting();
// Location 2: After routing runs. Middleware can match based on metadata.
app.Use(next => context =>
{
var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
== true)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}
return next(context);
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello world!");
});
// Using metadata to configure the audit policy.
endpoints.MapGet("/sensitive", async context =>
{
await context.Response.WriteAsync("sensitive data");
})
.WithMetadata(new AuditPolicyAttribute(needsAudit: true));
});
}
}
public class AuditPolicyAttribute : Attribute
{
public AuditPolicyAttribute(bool needsAudit)
{
NeedsAudit = needsAudit;
}
public bool NeedsAudit { get; }
}
O exemplo anterior demonstra dois conceitos importantes:
- O middleware pode ser executado antes
UseRoutingpara modificar os dados nos quais o roteamento opera.- Normalmente, o middleware que aparece antes do roteamento modifica alguma propriedade da solicitação, como UseRewriter, UseHttpMethodOverrideou UsePathBase.
- O middleware pode ser executado entre
UseRoutinge UseEndpoints para processar os resultados do roteamento antes que o ponto de extremidade seja executado.- Middleware executado entre
UseRoutingeUseEndpoints:- Geralmente inspeciona metadados para entender os endereços finais.
- Muitas vezes toma decisões de segurança, como fazem
UseAuthorizationeUseCors.
- A combinação de middleware e metadados permite configurar políticas por endpoint.
- Middleware executado entre
O código anterior mostra um exemplo de um middleware personalizado que oferece suporte a políticas por ponto de extremidade. O middleware grava um log de auditoria de acesso a dados confidenciais no console. O middleware pode ser configurado para auditar um endpoint com os AuditPolicyAttribute metadados. Este exemplo demonstra um padrão de aceitação em que apenas os pontos de extremidade marcados como confidenciais são auditados. É possível definir essa lógica ao contrário, auditando tudo o que não está marcado como seguro, por exemplo. O sistema de metadados do endpoint é flexível. Esta lógica pode ser desenhada de qualquer forma que se adeque ao caso de uso.
O código de exemplo anterior destina-se a demonstrar os conceitos básicos de pontos de extremidade. A amostra não se destina a utilização em produção. Uma versão mais completa de um middleware de registo de auditoria seria:
- Faça login em um arquivo ou banco de dados.
- Inclua detalhes como o utilizador, endereço IP, nome do ponto de extremidade confidencial e muito mais.
Os metadados AuditPolicyAttribute da política de auditoria são definidos como um Attribute para facilitar o uso com estruturas baseadas em classe, como controladores e SignalR. Ao usar caminho para codificar:
- Os metadados são anexados a uma API do construtor.
- As frameworks baseados em classe incluem todos os atributos no método e na classe correspondentes ao criar endpoints.
As práticas recomendadas para tipos de metadados são defini-los como interfaces ou atributos. Interfaces e atributos permitem a reutilização de código. O sistema de metadados é flexível e não impõe quaisquer limitações.
Comparando um middleware de terminal e roteamento
O exemplo de código a seguir contrasta o uso de middleware com o uso de roteamento:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Approach 1: Writing a terminal middleware.
app.Use(next => async context =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Hello terminal middleware!");
return;
}
await next(context);
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
// Approach 2: Using routing.
endpoints.MapGet("/Movie", async context =>
{
await context.Response.WriteAsync("Hello routing!");
});
});
}
O estilo de middleware mostrado com Approach 1: é middleware terminal. Denomina-se middleware de terminal porque realiza uma operação de correspondência:
- A operação de correspondência no exemplo anterior é
Path == "/"para o middleware ePath == "/Movie"para o roteamento. - Quando uma correspondência é bem-sucedida, executa uma funcionalidade e retorna, em vez de invocar o middleware
next.
É chamado de middleware de terminal porque encerra a pesquisa, executa algumas funcionalidades e, em seguida, retorna.
Comparando middleware terminal e roteamento:
- Ambas as abordagens permitem finalizar o fluxo de processamento.
- O middleware encerra o pipeline retornando em vez de invocar
next. - Os endpoints são sempre terminais.
- O middleware encerra o pipeline retornando em vez de invocar
- O middleware do terminal permite posicionar o middleware em um local arbitrário no pipeline:
- Os pontos de extremidade são executados na posição de UseEndpoints.
- O middleware do terminal permite que o código arbitrário determine quando o middleware corresponde:
- O código personalizado para correspondência de rotas pode ser extenso e difícil de escrever corretamente.
- O roteamento fornece soluções diretas para aplicativos típicos. A maioria das aplicações não requer código de correspondência de rota personalizado.
- Os endpoints interagem com o middleware, como
UseAuthorizationeUseCors.- Usar um middleware de terminal com
UseAuthorizationouUseCorsrequer interface manual com o sistema de autorização.
- Usar um middleware de terminal com
Um ponto de extremidade define ambos:
- Um delegado para processar solicitações.
- Uma coleção de metadados arbitrários. Os metadados são utilizados para implementar preocupações abrangentes com base em políticas e configurações anexadas a cada endpoint.
O middleware de terminal pode ser uma ferramenta eficaz, mas pode exigir:
- Uma quantidade significativa de codificação e testes.
- Integração manual com outros sistemas para alcançar o nível desejado de flexibilidade.
Considere a integração com o roteamento antes de escrever um middleware de terminal.
Middleware de terminal existente que se integra com o Map ou MapWhen geralmente pode ser transformado em um ponto de extremidade com reconhecimento de roteamento. MapHealthChecks demonstra o padrão para router-ware:
- Escreva um método de extensão em IEndpointRouteBuilder.
- Crie um pipeline de middleware aninhado usando CreateApplicationBuilder.
- Anexe o middleware ao novo pipeline. Neste caso, UseHealthChecks.
- Build o pipeline de middleware em RequestDelegate.
- Chame
Mape forneça o novo pipeline de middleware. - Retorne o objeto builder fornecido pelo método de extensão
Map.
O código a seguir mostra o uso de MapHealthChecks:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Matches request to an endpoint.
app.UseRouting();
// Endpoint aware middleware.
// Middleware can use metadata from the matched endpoint.
app.UseAuthentication();
app.UseAuthorization();
// Execute the matched endpoint.
app.UseEndpoints(endpoints =>
{
// Configure the Health Check endpoint and require an authorized user.
endpoints.MapHealthChecks("/healthz").RequireAuthorization();
// Configure another endpoint, no authorization requirements.
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
O exemplo anterior mostra por que retornar o objeto construtor é importante. Permitir que o objeto builder seja retornado para o desenvolvedor da aplicação possibilita a configuração de políticas, como autorização, para o ponto de extremidade. Neste exemplo, o middleware de verificações de integridade não tem integração direta com o sistema de autorização.
O sistema de metadados foi criado em resposta aos problemas encontrados pelos autores de extensibilidade usando middleware de terminal. É problemático para cada middleware implementar sua própria integração com o sistema de autorização.
Correspondência de URL
- É o processo pelo qual o encaminhamento associa uma solicitação de entrada a um ponto de extremidade.
- Baseia-se em dados no caminho e cabeçalhos da URL.
- Pode ser estendido para considerar quaisquer dados na solicitação.
Quando um middleware de roteamento é executado, ele define um Endpoint e uns valores de rota para um recurso de solicitação na HttpContext da solicitação atual.
- Chamar HttpContext.GetEndpoint obtém o ponto de extremidade.
-
HttpRequest.RouteValuesObtém a coleção de valores de rota.
O middleware é executado depois de o middleware de roteamento inspecionar o endpoint e tomar medidas. Por exemplo, um middleware de autorização pode interrogar os metadados do endpoint para uma política de autorização. Depois de todo o middleware na linha de processamento de solicitação ser executado, o delegado do ponto de extremidade selecionado é invocado.
O sistema de roteamento no endpoint é responsável por todas as decisões de despacho. Como o middleware aplica políticas com base no ponto de extremidade selecionado, é importante que:
- Qualquer decisão que possa afetar o despacho ou a aplicação de políticas de segurança é tomada dentro do sistema de roteamento.
Warning
Para compatibilidade retroativa, quando é executado o delegado de ponto de extremidade de um Controller ou Razor Pages, as propriedades de RouteContext.RouteData são definidas como valores apropriados com base no processamento de solicitação executado até agora.
O RouteContext tipo será marcado como obsoleto em uma versão futura:
- Migrar
RouteData.ValuesparaHttpRequest.RouteValues. - Migre
RouteData.DataTokenspara recuperar IDataTokensMetadata dos metadados do endpoint.
A correspondência de URL opera em um conjunto configurável de fases. Em cada fase, a saída é um conjunto de correspondências. O conjunto de jogos pode ser reduzido ainda mais na próxima fase. A implementação de roteamento não garante uma ordem de processamento para pontos de extremidade correspondentes. Todas as correspondências possíveis são processadas de uma só vez. As fases de correspondência de URL ocorrem na seguinte ordem. ASP.NET Núcleo:
- Processa o caminho da URL em relação ao conjunto de pontos de extremidade e seus modelos de rota, coletando todas as correspondências.
- Pega a lista anterior e remove correspondências que falham com restrições de rota aplicadas.
- Pega a lista anterior e remove os itens que não satisfazem o conjunto de instâncias de MatcherPolicy.
- Usa o EndpointSelector para tomar uma decisão final da lista anterior.
A lista de pontos finais é priorizada de acordo com:
Todos os pontos de extremidade correspondentes são processados em cada fase até que o EndpointSelector seja alcançado. A EndpointSelector é a fase final. Ele escolhe o ponto final de prioridade mais alta entre as correspondências como a melhor opção. Se houver outras partidas com a mesma prioridade da melhor partida, uma exceção de partida ambígua é lançada.
A precedência de rota é calculada com base em um modelo de rota mais específico recebendo uma prioridade mais alta. Por exemplo, considere os modelos /hello e /{message}:
- Ambos correspondem ao caminho da URL
/hello. -
/helloé mais específica e, por conseguinte, mais prioritária.
Em geral, a precedência de rota faz um bom trabalho ao escolher a melhor opção para os tipos de esquemas de URL usados na prática. Use Order apenas quando necessário para evitar uma ambiguidade.
Devido aos tipos de extensibilidade fornecidos pelo roteamento, não é possível para o sistema de roteamento calcular antecipadamente as rotas ambíguas. Considere um exemplo como os modelos de rota /{message:alpha} e /{message:int}:
- A
alpharestrição corresponde apenas a caracteres alfabéticos. - A
intrestrição corresponde apenas a números. - Esses modelos têm a mesma precedência de rota, mas não há um único URL com o qual ambos correspondam.
- Se o sistema de roteamento relatasse um erro de ambiguidade na inicialização, bloquearia esse caso de uso válido.
Warning
A ordem das operações internas UseEndpoints não influencia o comportamento do roteamento, com uma exceção. MapControllerRoute e MapAreaRoute atribuem automaticamente um valor de ordem aos seus pontos de extremidade com base na ordem em que são invocados. Isso simula o comportamento de longo prazo dos controladores sem que o sistema de roteamento forneça as mesmas garantias que as implementações de roteamento mais antigas.
Na implementação herdada do roteamento, é possível implementar a extensibilidade de roteamento que depende da ordem em que as rotas são processadas. Roteamento de endpoints no ASP.NET Core 3.0 ou posterior:
- Não tem um conceito de rotas.
- Não fornece garantias de encomenda. Todos os endpoints são processados de uma só vez.
Precedência do modelo de rota e ordem de seleção do ponto final
A precedência do modelo de rota é um sistema que atribui a cada modelo de rota um valor com base em quão específico ele é. Precedência do modelo de rota:
- Evita a necessidade de ajustar a ordem dos pontos finais em casos comuns.
- Tenta corresponder às expectativas de senso comum do comportamento de encaminhamento.
Por exemplo, considere modelos /Products/List e /Products/{id}. Seria razoável supor que /Products/List é uma correspondência melhor do que /Products/{id} para o caminho /Products/List da URL. Isso funciona porque o segmento /List literal é considerado como tendo melhor precedência do que o segmento /{id}de parâmetro.
Os detalhes de como a precedência funciona são acoplados à forma como os modelos de rota são definidos:
- Modelos com mais segmentos são considerados mais específicos.
- Um segmento com texto literal é considerado mais específico do que um segmento de parâmetro.
- Um segmento de parâmetro com uma restrição é considerado mais específico do que um sem.
- Um segmento complexo é considerado tão específico quanto um segmento de parâmetro com uma restrição.
- Os parâmetros "catch-all" são os menos específicos. Consulte catch-all na referência do modelo de rota para obter informações importantes sobre rotas catch-all.
Consulte o código-fonte no GitHub para obter uma referência de valores exatos.
Conceitos de geração de URL
Geração de URL:
- É o processo pelo qual o roteamento pode criar um caminho de URL com base em um conjunto de valores de rota.
- Permite uma separação lógica entre endpoints e os URLs que lhes acedem.
O roteamento de ponto de extremidade inclui a API LinkGenerator.
LinkGenerator é um serviço singleton disponível na DI. A LinkGenerator API pode ser usada fora do contexto de uma solicitação em execução. Mvc.IUrlHelper e cenários que dependem do IUrlHelper, como Tag Helpers, HTML Helpers e Action Results, usam a API internamente para fornecer recursos de geração de links.
O gerador de links é apoiado pelo conceito de um endereço e esquemas de endereço. Um esquema de endereços é uma forma de determinar os pontos finais que devem ser considerados para a geração de links. Por exemplo, os cenários de nome de rota e valores de rota com os quais muitos usuários estão familiarizados a partir de controladores e Razor Páginas são implementados como um esquema de endereço.
O gerador de links pode vincular a controladores e Razor páginas através dos seguintes métodos de extensão:
Sobrecargas desses métodos aceitam argumentos que incluem o HttpContext. Esses métodos são funcionalmente equivalentes a Url.Action e Url.Page, mas oferecem flexibilidade e opções adicionais.
Os GetPath* métodos são mais semelhantes a Url.Action e Url.Page, na medida em que geram um URI contendo um caminho absoluto. Os GetUri* métodos sempre geram um URI absoluto contendo um esquema e host. Os métodos que aceitam um HttpContext geram um URI no contexto da solicitação em execução. Os valores de rota ambiente , caminho base da URL, esquema e host da solicitação em execução são usados, a menos que sejam substituídos.
LinkGenerator é chamado com um endereço. A geração de um URI ocorre em duas etapas:
- Um endereço está associado a uma lista de terminais que correspondem ao endereço.
- Cada RoutePattern de ponto de extremidade é avaliado até que um padrão de rota que corresponda aos valores fornecidos seja encontrado. A saída resultante é combinada com as outras peças de URI fornecidas ao gerador de link e retornadas.
Os métodos fornecidos por LinkGenerator suportam capacidades padrão de geração de links para qualquer tipo de endereço. A maneira mais conveniente de usar o gerador de links é através de métodos de extensão que executam operações para um tipo de endereço específico:
| Método de extensão | Description |
|---|---|
| GetPathByAddress | Gera um URI com um caminho absoluto com base nos valores fornecidos. |
| GetUriByAddress | Gera um URI absoluto com base nos valores fornecidos. |
Warning
Preste atenção às seguintes implicações dos métodos de chamada LinkGenerator :
Use
GetUri*métodos de extensão com cuidado em uma configuração de aplicativo que não valida oHostcabeçalho das solicitações de entrada. Se o cabeçalhoHostdas solicitações de entrada não for validado, entradas de solicitações não confiáveis poderão ser enviadas de volta ao cliente em URIs em uma vista ou página. Recomendamos que todas as aplicações de produção configurem o seu servidor para validar o cabeçalho doHostcom valores válidos conhecidos.Use LinkGenerator com cuidado em middleware em combinação com
MapouMapWhen.Map*Altera o caminho base da solicitação em execução, o que afeta a saída da geração de link. Todas as LinkGenerator APIs permitem especificar um caminho base. Especifique um caminho base vazio para desfazer o efeito deMap*na geração de links.
Exemplo de middleware
No exemplo a seguir, um middleware usa a LinkGenerator API para criar um link para um método de ação que lista produtos de armazenamento. Ao usar o gerador de links, injetando-o numa classe e chamando GenerateLink, qualquer classe numa aplicação terá acesso:
public class ProductsLinkMiddleware
{
private readonly LinkGenerator _linkGenerator;
public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var url = _linkGenerator.GetPathByAction("ListProducts", "Store");
httpContext.Response.ContentType = "text/plain";
await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
}
}
Referência de modelo de rota
Os tokens dentro dos {} definem parâmetros de rota que são vinculados se houver uma correspondência na rota. Mais de um parâmetro de rota pode ser definido em um segmento de rota, mas os parâmetros de rota devem ser separados por um valor literal. Por exemplo, {controller=Home}{action=Index} não é uma rota válida, uma vez que não há valor literal entre {controller} e {action}. Os parâmetros de rota devem ter um nome e podem ter atributos adicionais especificados.
O texto literal diferente dos parâmetros de rota (por exemplo, {id}) e o separador / de caminho devem corresponder ao texto na URL. A correspondência de texto é insensível a maiúsculas e minúsculas e baseia-se na representação decodificada do caminho da URL. Para corresponder a um delimitador literal de parâmetro de rota { ou }, escape o delimitador repetindo o caractere. Por exemplo {{ ou }}.
Asterisco * ou duplo asterisco **:
- Pode ser usado como um prefixo para um parâmetro de rota para vincular ao restante do URI.
- São chamados de parâmetros catch-all . Por exemplo:
blog/{**slug}- Corresponde a qualquer URI que começa com
/bloge tem qualquer valor após ele. - O valor seguinte
/blogé atribuído ao valor de rota slug.
- Corresponde a qualquer URI que começa com
Warning
Um parâmetro catch-all pode corresponder a rotas incorretamente devido a um erro no roteamento. Os aplicativos afetados por esse bug têm as seguintes características:
- Uma rota abrangente, por exemplo,
{**slug}" - A rota de abrangência total não processa as solicitações que deveria processar.
- A remoção de outras rotas faz com que a rota "catch-all" passe a funcionar.
Veja os bugs 18677 e 16579 do GitHub, por exemplo, casos que atingiram esse bug.
Uma correção opcional para esse bug está contida no SDK do .NET Core 3.1.301 ou posterior. O código a seguir define uma opção interna que corrige esse bug:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Os parâmetros catch-all também podem corresponder à cadeia de caracteres vazia.
O parâmetro catch-all faz escape dos caracteres apropriados quando a rota é utilizada para gerar uma URL, incluindo os caracteres / separadores de caminho. Por exemplo, a rota foo/{*path} com valores de rota { path = "my/path" } gera foo/my%2Fpath. Observe a barra para a frente escapada. Para separar caracteres de caminho de ida e volta, use o prefixo do parâmetro de rota **. A rota foo/{**path} com { path = "my/path" } gera foo/my/path.
Os padrões de URL que tentam capturar um nome de arquivo com uma extensão de arquivo opcional têm considerações adicionais. Por exemplo, considere o modelo files/{filename}.{ext?}. Quando os valores para ambos filename e ext existem, ambos os valores são preenchidos. Se houver apenas um valor para filename na URL, a rota corresponderá porque o . final é opcional. Os seguintes URLs correspondem a esta rota:
/files/myFile.txt/files/myFile
Os parâmetros de rota podem ter valores padrão designados especificando o valor padrão após o nome do parâmetro separado por um sinal de igual (=). Por exemplo, {controller=Home} define Home como o valor padrão para controller. O valor padrão será usado se nenhum valor estiver presente na URL do parâmetro. Os parâmetros de rota são tornados opcionais anexando um ponto de interrogação (?) ao final do nome do parâmetro. Por exemplo, id?. A diferença entre valores opcionais e parâmetros de rota padrão é:
- Um parâmetro de rota com um valor padrão sempre produz um valor.
- Um parâmetro opcional tem um valor somente quando um valor é fornecido pela URL da solicitação.
Os parâmetros de rota podem ter restrições que devem corresponder ao valor de rota ligado à URL. Adicionar : e o nome da restrição após o nome do parâmetro de rota especifica uma restrição embutida em um parâmetro de rota. Se a restrição exigir argumentos, eles serão colocados entre parênteses (...) após o nome da restrição. Várias restrições embutidas podem ser especificadas ao anexar outro nome de restrição.
O nome da restrição e os argumentos são passados para o serviço IInlineConstraintResolver para criar uma instância de IRouteConstraint a ser usada no processamento de URLs. Por exemplo, o modelo blog/{article:minlength(10)} de rota especifica uma minlength restrição com o argumento 10. Para obter mais informações sobre restrições de rota e uma lista das restrições fornecidas pela estrutura, consulte a seção Referência de restrição de rota .
Os parâmetros de rota também podem ter transformadores de parâmetros. Os transformadores de parâmetros transformam o valor de um parâmetro ao gerar links e fazer a correspondência de ações e páginas com URLs. Como as restrições, os transformadores de parâmetros podem ser adicionados em linha a um parâmetro de rota adicionando um : e nome do transformador após o nome do parâmetro de rota. Por exemplo, o modelo blog/{article:slugify} de rota especifica um slugify transformador. Para obter mais informações sobre transformadores de parâmetro, consulte a seção Referência de transformador de parâmetro .
A tabela a seguir demonstra modelos de rota de exemplo e seu comportamento:
| Modelo de rota | Exemplo de URI correspondente | A URI do pedido… |
|---|---|---|
hello |
/hello |
Corresponde apenas ao único caminho /hello. |
{Page=Home} |
/ |
Combina e configura Page para Home. |
{Page=Home} |
/Contact |
Combina e configura Page para Contact. |
{controller}/{action}/{id?} |
/Products/List |
Mapeia para o Products controlador e List ação. |
{controller}/{action}/{id?} |
/Products/Details/123 |
Mapeia para o Products controlador e a Details ação com id definido para 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Mapeia para o Home controlador e o Index método.
id é ignorado. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Mapeia para o Products controlador e o Index método.
id é ignorado. |
Usar um modelo é geralmente a abordagem mais simples para roteamento. Restrições e padrões também podem ser especificados fora do modelo de rota.
Segmentos complexos
Segmentos complexos são processados combinando delimitadores literais da direita para a esquerda de uma forma não gananciosa . Por exemplo, [Route("/a{b}c{d}")] é um segmento complexo.
Segmentos complexos funcionam de uma maneira particular que deve ser entendida para usá-los com sucesso. O exemplo nesta seção demonstra por que segmentos complexos só funcionam realmente bem quando o texto do delimitador não aparece dentro dos valores dos parâmetros. Usar um regex e, em seguida, extrair manualmente os valores é necessário para casos mais complexos.
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Este é um resumo das etapas que o roteamento executa com o modelo /a{b}c{d} e o caminho /abcdda URL. O | é usado para ajudar a visualizar como o algoritmo funciona:
- O primeiro literal, da direita para a esquerda, é
c. Assim/abcdé pesquisado a partir da direita e encontra/ab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/ab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Não há texto restante e nenhum modelo de rota restante, então esta é uma correspondência.
Aqui está um exemplo de um caso negativo usando o mesmo modelo /a{b}c{d} e o caminho da URL /aabcd. O | é usado para ajudar a visualizar como o algoritmo funciona. Este caso não é uma correspondência, o que é explicado pelo mesmo algoritmo:
- O primeiro literal, da direita para a esquerda, é
c. Assim/aabcdé pesquisado a partir da direita e encontra/aab|c|d. - Tudo à direita (
d) agora corresponde ao parâmetro de rota{d}. - O próximo literal, da direita para a esquerda, é
a. Então/aab|c|dé pesquisado a partir de onde paramos, depoisaé encontrado/a|a|b|c|d. - O valor à direita (
b) agora corresponde ao{b}parâmetro route. - Neste ponto, há texto
arestante, mas o algoritmo ficou sem modelo de rota para analisar, então isso não é uma correspondência.
Uma vez que o algoritmo de correspondência não é ganancioso:
- Ele corresponde à menor quantidade de texto possível em cada etapa.
- Qualquer caso em que o valor do delimitador apareça dentro dos valores dos parâmetros resulta em não correspondência.
As expressões regulares fornecem muito mais controle sobre seu comportamento correspondente.
A correspondência gananciosa, também conhecida como correspondência não-gulosa, seleciona a maior sequência possível. Não ganancioso encontra a menor sequência de caracteres possível.
Referência de restrição de rota
As restrições de rota são executadas quando ocorre uma correspondência com a URL de entrada e o caminho da URL é tokenizado em valores de rota. As restrições de rota geralmente inspecionam o valor de rota associado por meio do modelo de rota e tomam uma decisão verdadeira ou falsa sobre se o valor é aceitável. Algumas restrições de rota usam dados fora do valor da rota para considerar se a solicitação pode ser roteada. Por exemplo, o HttpMethodRouteConstraint pode aceitar ou rejeitar uma solicitação com base no seu verbo HTTP. As restrições são usadas em solicitações de roteamento e geração de links.
Warning
Não use restrições para validação de entrada. Se as restrições forem usadas para validação de entrada, a entrada inválida resultará em uma 404 resposta Não encontrada. A entrada inválida deve produzir uma 400 solicitação incorreta com uma mensagem de erro apropriada. As restrições de rota são usadas para desambiguar rotas semelhantes, não para validar as entradas para uma rota específica.
A tabela a seguir demonstra exemplos de restrições de rota e seu comportamento esperado:
| restrição | Example | Exemplos de combinações | Notes |
|---|---|---|---|
int |
{id:int} |
123456789, -123456789 |
Corresponde a qualquer número inteiro |
bool |
{active:bool} |
true, FALSE |
Combina true ou false. Case-insensitive |
datetime |
{dob:datetime} |
2016-12-31, 2016-12-31 7:32pm |
Corresponde a um valor válido DateTime na cultura invariante. Ver advertência anterior. |
decimal |
{price:decimal} |
49.99, -1,000.01 |
Corresponde a um valor válido decimal na cultura invariante. Ver advertência anterior. |
double |
{weight:double} |
1.234, -1,001.01e8 |
Corresponde a um valor válido double na cultura invariante. Ver advertência anterior. |
float |
{weight:float} |
1.234, -1,001.01e8 |
Corresponde a um valor válido float na cultura invariante. Ver advertência anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Corresponde a um valor válido Guid |
long |
{ticks:long} |
123456789, -123456789 |
Corresponde a um valor válido long |
minlength(value) |
{username:minlength(4)} |
Rick |
String deve ter pelo menos 4 caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
String não deve ter mais de 8 caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
A cadeia de caracteres deve ter exatamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
A cadeia de caracteres deve ter no mínimo 8 e não mais de 16 caracteres |
min(value) |
{age:min(18)} |
19 |
O valor inteiro deve ser pelo menos 18 |
max(value) |
{age:max(120)} |
91 |
O valor inteiro não deve ser superior a 120 |
range(min,max) |
{age:range(18,120)} |
91 |
O valor inteiro deve ser pelo menos 18, mas não superior a 120 |
alpha |
{name:alpha} |
Rick |
A cadeia de caracteres deve consistir em um ou mais caracteres alfabéticos, como a, - e z, e ser insensível a maiúsculas/minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
String deve corresponder à expressão regular. Veja dicas sobre como definir uma expressão regular. |
required |
{name:required} |
Rick |
Usado para impor que um valor não parâmetro esteja presente durante a geração de URL |
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Várias restrições delimitadas por dois pontos podem ser aplicadas a um único parâmetro. Por exemplo, a restrição a seguir restringe um parâmetro a um valor inteiro igual ou superior a 1:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Warning
As restrições de rota que verificam a URL e são convertidas para um tipo CLR sempre usam a cultura invariante. Por exemplo, a conversão para o tipo CLR int ou DateTime. Essas restrições pressupõem que a URL não é localizável. As restrições de rota fornecidas pela estrutura não modificam os valores armazenados nos valores de rota. Todos os valores de rota analisados a partir da URL são armazenados como cadeias de caracteres. Por exemplo, a float restrição tenta converter o valor da rota em um float, mas o valor convertido é usado apenas para verificar se ele pode ser convertido em float.
Expressões regulares em restrições
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
Expressões regulares podem ser especificadas como restrições embutidas usando a regex(...) restrição de rota. Os métodos na MapControllerRoute família também aceitam um objeto literal de restrições. Se esse formulário for usado, os valores de cadeia de caracteres serão interpretados como expressões regulares.
O código a seguir usa uma restrição de regex embutida:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
context =>
{
return context.Response.WriteAsync("inline-constraint match");
});
});
O código a seguir usa um literal de objeto para especificar uma restrição de regex:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "people",
pattern: "People/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List", });
});
A estrutura ASP.NET Core adiciona RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant ao construtor de expressão regular. Consulte RegexOptions para obter uma descrição desses membros.
As expressões regulares usam delimitadores e tokens semelhantes aos usados pelo roteamento e pela linguagem C#. Os tokens de expressão regular precisam de ser escapados. Para usar a expressão regular ^\d{3}-\d{2}-\d{4}$ numa restrição inline, use uma das seguintes opções:
- Substitua os caracteres
\fornecidos na cadeia de caracteres por caracteres\\no arquivo de origem C# para fazer o escape do caractere de escape da cadeia de caracteres\. - Literais de string verbatim.
Para escapar dos caracteres delimitadores do parâmetro de roteamento {, }, [, ], dobre os caracteres na expressão, por exemplo, {{, }}, [[, ]]. A tabela a seguir mostra uma expressão regular e sua versão com escape:
| Expressão regular | Expressão regular escapada |
|---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
As expressões regulares usadas no roteamento geralmente começam com o ^ caractere e correspondem à posição inicial da cadeia de caracteres. As expressões geralmente terminam com o $ caractere e correspondem ao final da cadeia de caracteres. Os ^ caracteres e $ garantem que a expressão regular corresponda ao valor do parâmetro route inteiro. Sem os ^ caracteres e $ , a expressão regular corresponde a qualquer substring dentro da cadeia de caracteres, o que geralmente é indesejável. A tabela a seguir fornece exemplos e explica por que eles correspondem ou não correspondem:
| Expression | String | Match | Comment |
|---|---|---|---|
[a-z]{2} |
hello | Yes | Correspondências de substring |
[a-z]{2} |
123abc456 | Yes | Correspondências de substring |
[a-z]{2} |
mz | Yes | Expressão de correspondências |
[a-z]{2} |
MZ | Yes | Não diferencia maiúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Ver ^ e $ acima |
^[a-z]{2}$ |
123abc456 | No | Ver ^ e $ acima |
Para obter mais informações sobre sintaxe de expressão regular, consulte Expressões regulares do .NET Framework.
Para restringir um parâmetro a um conjunto conhecido de valores possíveis, use uma expressão regular. Por exemplo, {action:regex(^(list|get|create)$)} só corresponde ao valor da action rota para list, get, ou create. Se passada para o dicionário de restrições, a cadeia de caracteres ^(list|get|create)$ é equivalente. As restrições passadas no dicionário de restrições que não correspondem a uma das restrições conhecidas também são tratadas como expressões regulares. As restrições passadas dentro de um modelo que não correspondem a uma das restrições conhecidas não são tratadas como expressões regulares.
Restrições de rota personalizadas
Restrições de rota personalizadas podem ser criadas implementando a IRouteConstraint interface. A IRouteConstraint interface contém Match, que retorna true se a restrição for satisfeita e false de outra forma.
Restrições de rota personalizadas raramente são necessárias. Antes de implementar uma restrição de rota personalizada, considere alternativas, como vinculação de modelo.
A pasta ASP.NET Core Constraints fornece bons exemplos de criação de restrições. Por exemplo, GuidRouteConstraint.
Para utilizar um IRouteConstraint personalizado, o tipo de restrição de rota deve ser registado com o ConstraintMap da aplicação no contêiner de serviços. A ConstraintMap é um dicionário que mapeia chaves de restrição de rota para as implementações IRouteConstraint que validam essas restrições. Um aplicativo pode ConstraintMap ser atualizado em Startup.ConfigureServices numa de duas formas: como parte de uma chamada a services.AddRouting ou configurando RouteOptions diretamente com services.Configure<RouteOptions>. Por exemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});
}
A restrição anterior é aplicada no seguinte código:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
// GET /api/test/3
[HttpGet("{id:customName}")]
public IActionResult Get(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
// GET /api/test/my/3
[HttpGet("my/{id:customName}")]
public IActionResult Get(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
MyDisplayRouteInfo é fornecido pelo pacote Rick.Docs.Samples.RouteInfo NuGet e exibe informações de rota.
A implementação de MyCustomConstraint impede que 0 seja aplicado a um parâmetro de rota.
class MyCustomConstraint : IRouteConstraint
{
private Regex _regex;
public MyCustomConstraint()
{
_regex = new Regex(@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out object value))
{
var parameterValueString = Convert.ToString(value,
CultureInfo.InvariantCulture);
if (parameterValueString == null)
{
return false;
}
return _regex.IsMatch(parameterValueString);
}
return false;
}
}
Warning
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer informações para RegularExpressions causar um ataque de negação de serviço. APIs da estrutura ASP.NET Core que usam RegularExpressions implementam um tempo limite.
O código anterior:
- Previne
0no{id}segmento da rota. - É mostrado para fornecer um exemplo básico de implementação de uma restrição personalizada. Ele não deve ser usado em um aplicativo de produção.
O código a seguir é uma abordagem melhor para evitar que um id contendo a 0 seja processado:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return ControllerContext.MyDisplayRouteInfo(id);
}
O código anterior tem as seguintes vantagens em relação à MyCustomConstraint abordagem:
- Não requer uma restrição personalizada.
- Ele retorna um erro mais descritivo quando o parâmetro route inclui
0.
Referência do transformador de parâmetros
Transformadores de parâmetros:
- Execute ao gerar um link usando LinkGenerator.
- Implementar Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
- São configurados usando ConstraintMap.
- Pegue o valor de rota do parâmetro e transforme-o em um novo valor de cadeia de caracteres.
- O resultado é usar o valor transformado no link gerado.
Por exemplo, um transformador de parâmetro personalizado slugify no padrão de rota blog\{article:slugify} com Url.Action(new { article = "MyTestArticle" }) gera blog\my-test-article.
Considere a seguinte IOutboundParameterTransformer implementação:
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();
}
}
Para usar um transformador de parâmetro em um padrão de rota, configure-o usando ConstraintMap em Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
}
A estrutura ASP.NET Core usa transformadores de parâmetro para transformar o(s) URI(s) onde um ponto final é resolvido. Por exemplo, transformadores de parâmetros transformam os valores de rota usados para corresponder a um area, controller, action, e page.
routes.MapControllerRoute(
name: "default",
template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Com o modelo de rota anterior, a ação SubscriptionManagementController.GetAll é correspondida com o URI /subscription-management/get-all. Um transformador de parâmetros não altera os valores de rota usados para gerar um link. Por exemplo, Url.Action("GetAll", "SubscriptionManagement") produz /subscription-management/get-all.
O ASP.NET Core fornece convenções de API para o uso de transformadores de parâmetros com rotas geradas:
- A Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention convenção MVC aplica um transformador de parâmetro especificado a todas as rotas de atributos no aplicativo. O transformador de parâmetros transforma os tokens da rota do atributo conforme são substituídos. Para obter mais informações, consulte Usar um transformador de parâmetro para personalizar a substituição de token.
- Razor O Pages usa a convenção da PageRouteTransformerConvention API. Esta convenção aplica um transformador de parâmetros especificado a todas as Páginas descobertas Razor automaticamente. O transformador de parâmetros transforma os segmentos de pasta e nome de arquivo das rotas Pages Razor . Para obter mais informações, consulte Usar um transformador de parâmetros para personalizar rotas de página.
Referência de geração de URL
Esta seção contém uma referência para o algoritmo implementado pela geração de URL. Na prática, os exemplos de geração de URL mais complexos utilizam controladores ou Páginas Razor. Consulte Roteamento em controladores para obter informações adicionais.
O processo de geração de URL começa com uma chamada para LinkGenerator.GetPathByAddress ou um método semelhante. O método é fornecido com um endereço, um conjunto de valores de rota e, opcionalmente, informações sobre a solicitação atual da HttpContext.
O primeiro passo é utilizar o endereço para resolver um conjunto de pontos de extremidade candidatos, usando um IEndpointAddressScheme<TAddress> que corresponda ao tipo do endereço.
Uma vez que o conjunto de candidatos é encontrado pelo esquema de endereçamento, os pontos de extremidade são ordenados e processados iterativamente até que uma operação de geração de URL seja bem-sucedida. A geração de URL não verifica ambiguidades, o primeiro resultado retornado é o resultado final.
Solução de problemas de geração de URL com registro em log
A primeira etapa na solução de problemas de geração de URL é definir o nível de log de Microsoft.AspNetCore.Routing como TRACE.
LinkGenerator registra muitos detalhes sobre seu processamento, o que pode ser útil para solucionar problemas.
Consulte Referência de geração de URL para obter detalhes sobre a geração de URL.
Addresses
Endereços são o conceito na geração de URLs usado para associar uma chamada no gerador de ligações a um conjunto de endpoints candidatos.
Os endereços são um conceito extensível que vem com duas implementações por padrão:
- Usando o nome do ponto de extremidade (
string) como endereço:- Fornece funcionalidade semelhante ao nome da rota do MVC.
- Usa o tipo de metadados IEndpointNameMetadata.
- Resolve a cadeia de caracteres fornecida em relação aos metadados de todos os pontos de extremidade registrados.
- Lança uma exceção na inicialização se vários endpoints usarem o mesmo nome.
- Recomendado para uso geral fora de controladores e Razor Páginas.
- Usando valores de rota (RouteValuesAddress) como endereço:
- Fornece funcionalidade semelhante à dos controladores e do Pages para a geração de URLs herdadas.
- Muito complexo para expandir e depurar.
- Fornece a implementação usada por
IUrlHelper, Tag Helpers, HTML Helpers, Action Results, etc.
A função do esquema de endereços é fazer a associação entre o endereço e os pontos de extremidade correspondentes por critérios arbitrários:
- O esquema de nomes de ponto de extremidade executa uma pesquisa básica de dicionário.
- O esquema de valores de rota tem um algoritmo de melhor subconjunto de conjunto complexo.
Valores ambientais e valores explícitos
A partir da solicitação atual, o roteamento acessa os valores de rota da solicitação HttpContext.Request.RouteValuesatual. Os valores associados à solicitação atual são chamados de valores ambientais. Para fins de clareza, a documentação refere-se aos valores de rota passados para os métodos como valores explícitos.
O exemplo a seguir mostra valores ambientais e valores explícitos. Ele fornece valores ambientais da solicitação atual e valores explícitos: { id = 17, }:
public class WidgetController : Controller
{
private readonly LinkGenerator _linkGenerator;
public WidgetController(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public IActionResult Index()
{
var url = _linkGenerator.GetPathByAction(HttpContext,
null, null,
new { id = 17, });
return Content(url);
}
O código anterior:
- Devoluções
/Widget/Index/17 - Obtém LinkGenerator via DI.
O código a seguir não fornece valores ambientais e valores explícitos: { controller = "Home", action = "Subscribe", id = 17, }
public IActionResult Index2()
{
var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
new { id = 17, });
return Content(url);
}
O método anterior retorna /Home/Subscribe/17
O seguinte código no WidgetController retorna /Widget/Subscribe/17:
var url = _linkGenerator.GetPathByAction("Subscribe", null,
new { id = 17, });
O código a seguir fornece o controlador a partir de valores ambientais na solicitação atual e valores explícitos: { action = "Edit", id = 17, }:
public class GadgetController : Controller
{
public IActionResult Index()
{
var url = Url.Action("Edit", new { id = 17, });
return Content(url);
}
No código anterior:
-
/Gadget/Edit/17é retornado. - Url obtém o IUrlHelper.
-
Action gera uma URL com um caminho absoluto para um método de ação. A URL contém o nome especificado
actione os valoresroute.
O código a seguir fornece valores de ambiente da solicitação atual e valores explícitos: { page = "./Edit, id = 17, }
public class IndexModel : PageModel
{
public void OnGet()
{
var url = Url.Page("./Edit", new { id = 17, });
ViewData["URL"] = url;
}
}
O código anterior define url para /Edit/17 quando a página Editar Razor contém a seguinte diretiva de página:
@page "{id:int}"
Se a página Editar não contiver o "{id:int}" modelo de rota, url será /Edit?id=17.
O comportamento dos MVCs IUrlHelper adiciona uma camada de complexidade, além das regras descritas aqui:
-
IUrlHelpersempre fornece os valores de rota da solicitação atual como valores ambientais. -
IUrlHelper.Action sempre copia os valores atuais
actionecontrollerde rota como valores explícitos, a menos que sejam substituídos pelo desenvolvedor. -
IUrlHelper.Page sempre copia o valor da rota atual
pagecomo um valor explícito, a menos que seja substituído. -
IUrlHelper.Pagesempre substitui o valor da rota atualhandlerpornullvalores explícitos, a menos que sejam substituídos.
Os usuários muitas vezes são surpreendidos pelos detalhes comportamentais dos valores ambientais, porque o MVC parece não seguir suas próprias regras. Por razões históricas e de compatibilidade, certos valores de rota, como action, controller, page, e handler têm seu próprio comportamento de caso especial.
A funcionalidade equivalente fornecida por LinkGenerator.GetPathByAction e LinkGenerator.GetPathByPage duplica essas anomalias de IUrlHelper para compatibilidade.
Processo de geração de URL
Uma vez encontrado o conjunto de pontos de extremidade candidatos, o algoritmo de geração de URL:
- Processa os extremos iterativamente.
- Devolve o primeiro resultado bem-sucedido.
A primeira etapa deste processo é chamada de invalidação de valores de rota. A invalidação do valor de rota é o processo pelo qual o roteamento decide quais valores de rota dos valores ambientais devem ser usados e quais devem ser ignorados. Cada valor ambiental é considerado e combinado com os valores explícitos ou ignorado.
A melhor maneira de pensar sobre o papel dos valores de ambiente é que eles tentam reduzir o número de teclas que os desenvolvedores de aplicativos precisam pressionar, em alguns casos comuns. Tradicionalmente, os cenários em que os valores ambientais são úteis estão relacionados ao MVC:
- Ao vincular a outra ação no mesmo controlador, o nome do controlador não precisa ser especificado.
- Ao vincular a outro controlador na mesma área, o nome da área não precisa ser especificado.
- Ao vincular ao mesmo método de ação, os valores de rota não precisam ser especificados.
- Ao vincular a outra parte do aplicativo, você não deseja transferir valores de rota que não têm significado nessa parte do aplicativo.
As chamadas para LinkGenerator ou IUrlHelper que retornam null geralmente são causadas por não entender a invalidação do valor da rota. Solucione problemas de invalidação de valor de rota especificando claramente mais valores de rota para ver se isso resolve o problema.
A invalidação do valor de rota funciona com base na suposição de que o esquema de URL do aplicativo é hierárquico, com uma hierarquia formada da esquerda para a direita. Considere o modelo {controller}/{action}/{id?} de rota do controlador básico para ter uma noção intuitiva de como isso funciona na prática. Uma alteração em um valor invalida todos os valores de rota que aparecem à direita. Isso reflete o pressuposto sobre hierarquia. Se a aplicação tiver um valor de ambiente para id, e a operação especificar um valor diferente para o controller:
-
idnão será reutilizado porque{controller}está à esquerda de{id?}.
Alguns exemplos que demonstram este princípio:
- Se os valores explícitos contiverem um valor para
id, o valor de ambiente paraidserá ignorado. Os valores ambientais paracontrollereactionpodem ser usados. - Se os valores explícitos contiverem um valor para
action, qualquer valor ambiente paraactionserá ignorado. Os valores ambientais paracontrollerpodem ser usados. Se o valor explícito paraactionfor diferente do valor ambiente paraaction, oidvalor não será usado. Se o valor explícito paraactionfor o mesmo que o valor ambiente paraaction, oidvalor pode ser usado. - Se os valores explícitos contiverem um valor para
controller, qualquer valor ambiente paracontrollerserá ignorado. Se o valor explícito paracontrollerfor diferente do valor ambiente paracontroller, os valoresactioneidnão serão usados. Se o valor explícito paracontrollerfor o mesmo que o valor ambiente paracontroller, osactionvalores eidpodem ser usados.
Este processo é ainda mais complicado pela existência de rotas de atributos e rotas convencionais dedicadas. Controladores de rotas convencionais, como {controller}/{action}/{id?}, especificam uma hierarquia usando parâmetros de rota. Para rotas convencionais dedicadas e rotas de atributos para controladores e Razor páginas:
- Há uma hierarquia de valores de rota.
- Eles não aparecem no modelo.
Para esses casos, a geração de URL define o conceito de valores necessários . Os endpoints criados por controladores e Razor Pages têm valores obrigatórios especificados que permitem que a invalidação de valores de rota funcione corretamente.
Detalhes do algoritmo de invalidação do valor da rota:
- Os nomes de valor necessários são combinados com os parâmetros de rota e, em seguida, processados da esquerda para a direita.
- Para cada parâmetro, o valor ambiente e o valor explícito são comparados:
- Se o valor ambiental e o valor explícito forem os mesmos, o processo continua.
- Se o valor ambiente estiver presente e o valor explícito não, o valor ambiente será usado ao gerar a URL.
- Se o valor ambiental não estiver presente e o valor explícito estiver, rejeite o valor ambiental e todos os valores ambientais subsequentes.
- Se o valor ambiente e o valor explícito estiverem presentes e os dois valores forem diferentes, rejeite o valor ambiente e todos os valores ambientais subsequentes.
Neste ponto, a operação de geração de URL está pronta para avaliar as restrições de rota. O conjunto de valores aceitos é combinado com os valores padrão do parâmetro, que são fornecidos às restrições. Se todas as restrições passarem, a operação continuará.
Em seguida, os valores aceitos podem ser usados para expandir o modelo de rota. O modelo de rota é processado:
- Da esquerda para a direita.
- Cada parâmetro tem seu valor aceito substituído.
- Com os seguintes casos especiais:
- Se os valores aceitos estiverem faltando um valor e o parâmetro tiver um valor padrão, o valor padrão será usado.
- Se os valores aceitos estiverem faltando um valor e o parâmetro for opcional, o processamento continuará.
- Se qualquer parâmetro de rota à direita de um parâmetro opcional ausente tiver um valor, a operação falhará.
- Os parâmetros contíguos com valor padrão e os parâmetros opcionais são recolhidos sempre que possível.
Os valores explicitamente fornecidos que não correspondem a um segmento da rota são adicionados à cadeia de caracteres de consulta. A tabela a seguir mostra o resultado ao usar o modelo {controller}/{action}/{id?}de rota.
| Valores Ambientais | Valores explícitos | Result |
|---|---|---|
| controlador = "Home" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | controller = "Ordem", ação = "Sobre" | /Order/About |
| controlador = "Home", cor = "Vermelho" | ação = "Sobre nós" | /Home/About |
| controlador = "Home" | ação = "Sobre", cor = "Vermelho" | /Home/About?color=Red |
Problemas com a invalidação de valores de rota
A partir do ASP.NET Core 3.0, alguns esquemas de geração de URL usados em versões anteriores do ASP.NET Core não funcionam bem com a geração de URL. A equipe do ASP.NET Core planeja adicionar recursos para atender a essas necessidades em uma versão futura. Por enquanto, a melhor solução é usar o roteamento legado.
O código a seguir mostra um exemplo de um esquema de geração de URL que não é suportado pelo roteamento.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("blog", "{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost", });
});
No código anterior, o culture parâmetro route é usado para localização. O desejo é que o culture parâmetro seja sempre aceite como um valor ambiental. No entanto, o culture parâmetro não é aceite como um valor ambiente devido à forma como os valores necessários funcionam:
- No modelo de rota
"default", o parâmetro de rotacultureestá à esquerda decontroller, portanto, alterações emcontrollernão invalidarãoculture. -
"blog"no modelo de rota, o parâmetro de rotacultureé considerado estar à direita docontroller, que aparece nos valores necessários.
Configurando metadados de ponto final
Os links a seguir fornecem informações sobre como configurar metadados de ponto de extremidade:
- Habilitar Cors com roteamento de ponto final
-
Exemplo de IAuthorizationPolicyProvider usando um atributo personalizado
[MinimumAgeAuthorize] - Testar a autenticação com o atributo [Autorizar]
- RequireAuthorization
- Selecionando o esquema com o atributo [Autorizar]
- Aplicar políticas usando o atributo [Autorizar]
- Autorização baseada em função no ASP.NET Core
Correspondência de host em rotas com RequireHost
RequireHost Aplica uma restrição à rota que requer o host especificado. O parâmetro RequireHost ou [Host] pode ser:
- Anfitrião:
www.domain.com, associawww.domain.coma qualquer porta. - Host com wildcard:
*.domain.com, corresponde awww.domain.com,subdomain.domain.comouwww.subdomain.domain.comem qualquer porta. - Porta:
*:5000, corresponde à porta 5000 em qualquer host. - Host e porta:
www.domain.com:5000ou*.domain.com:5000, corresponde ao host e à porta.
Vários parâmetros podem ser especificados usando RequireHost ou [Host]. A restrição corresponde a hosts válidos para qualquer um dos parâmetros. Por exemplo, [Host("domain.com", "*.domain.com")] corresponde a domain.com, www.domain.com e subdomain.domain.com.
O código a seguir usa RequireHost para exigir o host especificado na rota:
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
.RequireHost("contoso.com");
endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
.RequireHost("adventure-works.com");
endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
});
}
O código a seguir usa o [Host] atributo no controlador para exigir qualquer um dos hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Host("example.com:8080")]
public IActionResult Privacy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
Quando o [Host] atributo é aplicado ao controlador e ao método de ação:
- O atributo é utilizado na ação.
- O atributo controller é ignorado.
Diretrizes de desempenho para roteamento
A maior parte do roteamento foi atualizada no ASP.NET Core 3.0 para aumentar o desempenho.
Quando uma aplicação tem problemas de desempenho, geralmente suspeita-se do roteamento como a causa. A razão pela qual o roteamento é suspeito é que estruturas como controladores e Razor Pages relatam a quantidade de tempo gasto dentro da estrutura em suas mensagens de registro. Quando há uma diferença significativa entre o tempo relatado pelos controladores e o tempo total da solicitação:
- Os desenvolvedores eliminam o código do aplicativo como a fonte do problema.
- É comum assumir que o roteamento é a causa.
O desempenho do roteamento é testado usando milhares de endpoints. É improvável que um aplicativo típico encontre um problema de desempenho apenas por ser muito grande. A causa raiz mais comum do desempenho de roteamento lento geralmente é um middleware personalizado com mau comportamento.
Este exemplo de código a seguir demonstra uma técnica básica para restringir a origem do atraso:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseRouting();
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseAuthorization();
app.Use(next => async context =>
{
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Timing test.");
});
});
}
Para agendar o temporização de rotas:
- Intercale cada middleware com uma cópia do middleware de temporização mostrado no código anterior.
- Adicione um identificador exclusivo para correlacionar os dados de tempo com o código.
Esta é uma maneira básica de reduzir o atraso quando ele é significativo, por exemplo, mais do que 10ms. Subtrair Time 2 de Time 1 indica o tempo gasto dentro do middleware UseRouting.
O código a seguir usa uma abordagem mais compacta para o código de tempo anterior:
public sealed class MyStopwatch : IDisposable
{
ILogger<Startup> _logger;
string _message;
Stopwatch _sw;
public MyStopwatch(ILogger<Startup> logger, string message)
{
_logger = logger;
_message = message;
_sw = Stopwatch.StartNew();
}
private bool disposed = false;
public void Dispose()
{
if (!disposed)
{
_logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
_message, _sw.ElapsedMilliseconds);
disposed = true;
}
}
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
int count = 0;
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseRouting();
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseAuthorization();
app.Use(next => async context =>
{
using (new MyStopwatch(logger, $"Time {++count}"))
{
await next(context);
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Timing test.");
});
});
}
Recursos de roteamento potencialmente caros
A lista a seguir fornece algumas informações sobre os recursos de roteamento que são relativamente caros em comparação com modelos de rota básicos:
- Expressões regulares: É possível escrever expressões regulares que são complexas ou têm um longo tempo de execução com uma pequena quantidade de entrada.
- Segmentos complexos (
{x}-{y}-{z}):- São significativamente mais caros do que analisar um segmento de caminho de URL regular.
- Resulte em muito mais substrings a serem alocadas.
- A lógica de segmento complexo não foi atualizada na atualização de desempenho de roteamento ASP.NET Core 3.0.
- Acesso síncrono a dados: muitos aplicativos complexos têm acesso ao banco de dados como parte de seu roteamento. O roteamento do ASP.NET Core 2.2 ou anterior pode não fornecer os pontos de extensibilidade corretos para suportar o acesso ao banco de dados por meio de roteamento. Por exemplo, IRouteConstrainte IActionConstraint são síncronos. Pontos de extensibilidade como MatcherPolicy e EndpointSelectorContext são assíncronos.
Guia de orientação para autores de bibliotecas
Esta seção contém orientações para autores de bibliotecas que se baseiam no roteamento. Esses detalhes destinam-se a garantir que os desenvolvedores de aplicativos tenham uma boa experiência usando bibliotecas e estruturas que estendem o roteamento.
Definir endpoints
Para criar uma estrutura que use roteamento para correspondência de URLs, comece por definir uma experiência de usuário que se construa sobre UseEndpoints.
CONSTRUA em cima do IEndpointRouteBuilder. Isso permite que os usuários componham sua estrutura com outros recursos ASP.NET Core sem confusão. Cada modelo ASP.NET Core inclui roteamento. Suponha que o roteamento esteja presente e seja familiar para os usuários.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...);
endpoints.MapHealthChecks("/healthz");
});
DO retorne um tipo concreto selado de uma chamada para MapMyFramework(...) que implementa IEndpointConventionBuilder. A maioria dos métodos de estrutura Map... segue esse padrão. A IEndpointConventionBuilder interface:
- Permite a composição de metadados.
- É alvo de uma variedade de métodos de extensão.
Declarar o teu próprio tipo permite-te adicionar funcionalidades específicas do framework à ferramenta de construção. Não há problema em envolver um construtor declarado pela estrutura e encaminhar chamadas para ele.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
endpoints.MapHealthChecks("/healthz");
});
CONSIDERE escrever o seu próprio EndpointDataSource.
EndpointDataSource é a primitiva de baixo nível para declarar e atualizar uma coleção de pontos de extremidade.
EndpointDataSource é uma API poderosa usada por controladores e Razor páginas.
Os testes de roteamento têm um exemplo básico de uma fonte de dados não atualizada.
NÃO tente registrar um EndpointDataSource por padrão. Exigir que os usuários registrem sua estrutura no UseEndpoints. A filosofia do encaminhamento é que nada é incluído por padrão, e que UseEndpoints é o lugar para registrar endpoints.
Criando middleware integrado ao roteamento
CONSIDERE definir tipos de metadados como uma interface.
DO possibilitam o uso de tipos de metadados como um atributo em classes e métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Frameworks como controladores e Razor Pages suportam a aplicação de atributos de metadados a tipos e métodos. Se você declarar tipos de metadados:
- Torne-os acessíveis como atributos.
- A maioria dos usuários está familiarizada com a aplicação de atributos.
Declarar um tipo de metadados como uma interface adiciona outra camada de flexibilidade:
- As interfaces são combináveis.
- Os desenvolvedores podem declarar seus próprios tipos que combinam várias políticas.
DO possibilitam a substituição de metadados, conforme mostrado no exemplo a seguir:
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}
[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }
[SuppressCoolMetadata]
public void Uncool() { }
}
A melhor maneira de seguir estas diretrizes é evitar a definição de metadados de marcadores:
- Não procure apenas pela presença de um tipo de metadados.
- Defina uma propriedade nos metadados e verifique a propriedade.
A coleta de metadados é ordenada e suporta a substituição por prioridade. No caso dos controladores, os metadados sobre o método de ação são mais específicos.
FAÇA com que o middleware seja útil com e sem roteamento.
app.UseRouting();
app.UseAuthorization(new AuthorizationPolicy() { ... });
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization();
});
Como exemplo dessa diretriz, considere o UseAuthorization middleware. O middleware de autorização permite definir uma política de fallback.
A política de fallback, se especificada, aplica-se a:
- Pontos de extremidade sem uma política especificada.
- Solicitações que não correspondem a um endpoint.
Isso torna o middleware de autorização útil fora do contexto de roteamento. O middleware de autorização pode ser usado para programação de middleware tradicional.
Diagnóstico de depuração
Para obter uma saída de diagnóstico de roteamento detalhada, defina Logging:LogLevel:Microsoft como Debug. No ambiente de desenvolvimento, defina o nível de log em appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}