Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis informaciónEste explorador xa non é compatible.
Actualice a Microsoft Edge para dispoñer das funcionalidades máis recentes, as actualizacións de seguranza e a asistencia técnica.
Por Ryan Nowak, Kirk Larkin y Rick Anderson
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Aviso
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
El enrutamiento es responsable de hacer coincidir las solicitudes HTTP entrantes y de enviarlas a los puntos de conexión ejecutables de la aplicación. Los puntos de conexión son las unidades de código de control de solicitudes ejecutable de la aplicación. Se definen en la aplicación y se configuran al iniciarla. El proceso de búsqueda de coincidencias de puntos de conexión puede extraer valores de la dirección URL de la solicitud y proporcionarlos para el procesamiento de la solicitud. Con la información de los puntos de conexión de la aplicación, el enrutamiento también puede generar direcciones URL que se asignan a los puntos de conexión.
Las aplicaciones pueden configurar el enrutamiento mediante:
En este artículo se describen los detalles de bajo nivel del enrutamiento de ASP.NET Core. Para obtener información sobre la configuración del enrutamiento:
En el código siguiente se muestra un ejemplo básico de enrutamiento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
En el ejemplo anterior se incluye un único punto de conexión que usa el método MapGet:
GET
a la dirección URL raíz /
: Hello World!
en la respuesta HTTP.GET
o la dirección URL raíz no es /
, no se detecta ninguna ruta y se devuelve HTTP 404.El enrutamiento usa un par de middleware, registrado por UseRouting y UseEndpoints:
UseRouting
agrega coincidencia de rutas a la canalización de middleware. Este middleware examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia en función de la solicitud.UseEndpoints
agrega la ejecución del punto de conexión a la canalización de middleware. Ejecuta el delegado asociado al punto de conexión seleccionado.Normalmente, las aplicaciones no necesitan llamar a UseRouting
ni a UseEndpoints
. WebApplicationBuilder configura una canalización de middleware que encapsula el middleware agregado en Program.cs
con UseRouting
y UseEndpoints
. Sin embargo, las aplicaciones pueden cambiar el orden en que se ejecutan UseRouting
y UseEndpoints
llamando a estos métodos explícitamente. Por ejemplo, el código siguiente realiza una llamada explícita a UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
En el código anterior:
app.Use
registra un middleware personalizado que se ejecuta al principio de la canalización.UseRouting
configura el middleware de coincidencia de rutas para que se ejecute después del middleware personalizado.MapGet
se ejecuta al final de la canalización.Si el ejemplo anterior no incluyese una llamada a UseRouting
, el middleware personalizado se ejecutaría después del middleware de coincidencia de rutas.
Nota: Las rutas agregadas directamente a WebApplication se ejecutan al final de la canalización.
El método MapGet
se usa para definir un punto de conexión. Un punto de conexión es algo que se puede:
Los puntos de conexión que la aplicación puede ejecutar y hacer coincidir se configuran en UseEndpoints
. Por ejemplo, MapGet, MapPost y métodos similares conectan delegados de solicitud al sistema de enrutamiento. Se pueden usar métodos adicionales para conectar características del marco ASP.NET Core al sistema de enrutamiento:
En el ejemplo siguiente se muestra el enrutamiento con una plantilla de ruta más sofisticada:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
La cadena /hello/{name:alpha}
es una plantilla de ruta. Se usa una plantilla de ruta para configurar la coincidencia del punto de conexión. En este caso, la plantilla coincide con:
/hello/Docs
./hello/
, seguido de una secuencia de caracteres alfabéticos. :alpha
aplica una restricción de ruta que solo coincide con caracteres alfabéticos. Las restricciones de ruta se explican más adelante en este artículo.El segundo segmento de la ruta de dirección URL, {name:alpha}
:
name
.En el ejemplo siguiente se muestra el enrutamiento con comprobaciones de estado y autorización:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
En el ejemplo anterior se muestra cómo:
La llamada a MapHealthChecks agrega un punto de conexión de comprobación de estado. Al encadenar RequireAuthorization a esta llamada, se adjunta una directiva de autorización al punto de conexión.
La llamada a UseAuthentication y UseAuthorization agrega el middleware de autenticación y autorización. Estos middleware se colocan entre UseRouting y UseEndpoints
para que puedan:
UseRouting
.En el ejemplo anterior, hay dos puntos de conexión, pero solo el de comprobación de estado tiene una directiva de autorización adjunta. Si la solicitud coincide con el punto de conexión de comprobación de estado, /healthz
, se realiza una comprobación de autorización. Esto demuestra que los puntos de conexión pueden tener datos adicionales adjuntos. Estos datos adicionales se denominan metadatos de punto de conexión:
El sistema de enrutamiento se basa en la canalización de middleware mediante la adición del eficaz concepto de punto de conexión. Los puntos de conexión representan unidades de la funcionalidad de la aplicación que son diferentes entre sí en cuanto al enrutamiento, la autorización y cualquier número de sistemas de ASP.NET Core.
Un punto de conexión de ASP.NET Core es:
En el código siguiente se muestra cómo recuperar e inspeccionar el punto de conexión que coincide con la solicitud actual:
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.");
El punto de conexión, si se selecciona, se puede recuperar de HttpContext
. Se pueden inspeccionar sus propiedades. Los objetos de punto de conexión son inmutables y no se pueden modificar después de crearlos. El tipo más común de punto de conexión es RouteEndpoint. RouteEndpoint
incluye información que permite que el sistema de enrutamiento lo seleccione.
En el código anterior, app.Use configura un middleware insertado.
En el código siguiente se muestra que, en función de dónde se llame a app.Use
en la canalización, es posible que no haya un punto de conexión:
// 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);
});
En el ejemplo anterior se agregan instrucciones Console.WriteLine
que muestran si se ha seleccionado un punto de conexión o no. Para mayor claridad, en el ejemplo se asigna un nombre para mostrar al punto de conexión /
proporcionado.
El ejemplo anterior también incluye llamadas a UseRouting
y UseEndpoints
para controlar exactamente cuándo se ejecuta este middleware dentro de la canalización.
Al ejecutar este código con una dirección URL de /
se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Al ejecutar este código con otra dirección URL se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Este resultado muestra que:
UseRouting
.UseRouting
y UseEndpoints.UseEndpoints
es terminal cuando se encuentra una coincidencia. El middleware de terminal se define más adelante en este artículo.UseEndpoints
solo se ejecuta cuando no se encuentra ninguna coincidencia.El middleware UseRouting
usa el método SetEndpoint para asociar el punto de conexión al contexto actual. Se puede reemplazar el middleware UseRouting
con lógica personalizada y seguir aprovechando las ventajas del uso de puntos de conexión. Los puntos de conexión son una primitiva de bajo nivel como middleware y no están unidos a la implementación de enrutamiento. La mayoría de las aplicaciones no necesitan reemplazar UseRouting
por lógica personalizada.
El middleware UseEndpoints
está diseñado para usarse junto con el middleware UseRouting
. La lógica básica para ejecutar un punto de conexión no es complicada. Use GetEndpoint para recuperar el punto de conexión y, después, invoque su propiedad RequestDelegate.
En el código siguiente se muestra cómo el middleware puede influir en el enrutamiento o reaccionar ante este:
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 { }
En el ejemplo anterior se muestran dos conceptos importantes:
UseRouting
para modificar los datos sobre los que funciona el enrutamiento.
UseRouting
y UseEndpoints para procesar los resultados del enrutamiento antes de que se ejecute el punto de conexión.
UseRouting
y UseEndpoints
: UseAuthorization
y UseCors
.En el código anterior se muestra un ejemplo de middleware personalizado que admite directivas por punto de conexión. El middleware escribe un registro de auditoría de acceso a datos confidenciales en la consola. El middleware se puede configurar para auditar un punto de conexión con los metadatos de RequiresAuditAttribute
. En este ejemplo se muestra un patrón opcional en el que solo se auditan los puntos de conexión marcados como confidenciales. Esta lógica se puede definir en orden inverso, para auditar todo lo que no esté marcado como seguro, por ejemplo. El sistema de metadatos de punto de conexión es flexible. Esta lógica se puede diseñar de la manera que mejor se adapte al caso de uso.
El código del ejemplo anterior está diseñado para mostrar los conceptos básicos de los puntos de conexión. No está pensado para su uso en producción. Una versión más completa de un middleware de registro de auditoría:
El valor RequiresAuditAttribute
de metadatos de directiva de auditoría se define como Attribute
para facilitar su uso con marcos basados en clases como los controladores y SignalR. Cuando se usa de ruta a código:
Los procedimientos recomendados para los tipos de metadatos son definirlos como interfaces o atributos. Las interfaces y los atributos permiten la reutilización del código. El sistema de metadatos es flexible y no impone ninguna limitación.
En el ejemplo siguiente se muestra el middleware de terminal y el enrutamiento:
// 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.");
El estilo de middleware que se muestra con Approach 1:
es middleware de terminal. Se denomina middleware de terminal porque realiza una operación de búsqueda de coincidencias:
Path == "/"
para el middleware y Path == "/Routing"
para el enrutamiento.next
.Se denomina middleware de terminal porque finaliza la búsqueda, ejecuta alguna funcionalidad y, después, devuelve un valor.
En la lista siguiente se compara el middleware de terminal con el enrutamiento:
next
.UseAuthorization
y UseCors
.
UseAuthorization
o UseCors
se necesita interactuar de forma manual con el sistema de autorización.Un punto de conexión define:
El middleware de terminal puede ser una herramienta eficaz, pero puede requerir:
Considere la posibilidad de realizar la integración con el enrutamiento antes de escribir middleware de terminal.
El middleware de terminal existente que se integra con Map o MapWhen normalmente se puede convertir en un punto de conexión compatible con el enrutamiento. MapHealthChecks muestra el patrón para enrutadores:
Map
y proporcione la nueva canalización de middleware.Map
desde el método de extensión.En el código siguiente se muestra el uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
En el ejemplo anterior se muestra la importancia de devolver el objeto de generador. Al devolver el objeto de generador, el desarrollador de aplicaciones puede configurar directivas como la autorización para el punto de conexión. En este ejemplo, el middleware de comprobaciones de estado no tiene una integración directa con el sistema de autorización.
El sistema de metadatos se ha creado como respuesta a los problemas detectados por los autores de extensibilidad mediante el middleware de terminal. El problema de cada middleware es implementar su propia integración con el sistema de autorización.
Cuando se ejecuta un middleware de enrutamiento, se establece un objeto Endpoint
y se enrutan los valores a una característica de solicitud en el objeto HttpContext desde la solicitud actual:
HttpRequest.RouteValues
obtiene la colección de valores de ruta.El middleware que se ejecuta después del middleware de enrutamiento puede inspeccionar el punto de conexión y tomar medidas. Por ejemplo, un middleware de autorización puede consultar la colección de metadatos del punto de conexión de una directiva de autorización. Después de que se ejecuta todo el middleware en la canalización de procesamiento de solicitudes, se invoca al delegado del punto de conexión seleccionado.
El sistema de enrutamiento en el enrutamiento de punto de conexión es responsable de todas las decisiones relativas al envío. Como el middleware aplica directivas en función del punto de conexión seleccionado, es importante que:
Aviso
En cuanto a la compatibilidad con versiones anteriores, cuando se ejecuta un delegado del punto de conexión de controlador o Razor Pages, las propiedades de RouteContext.RouteData se establecen en los valores adecuados en función del procesamiento de solicitudes realizado hasta el momento.
El tipo RouteContext
se marcará como obsoleto en una versión futura:
RouteData.Values
a HttpRequest.RouteValues
.RouteData.DataTokens
para recuperar IDataTokensMetadata de los metadatos del punto de conexión.La coincidencia de direcciones URL funciona en un conjunto configurable de fases. En cada fase, la salida es un conjunto de coincidencias. El conjunto de coincidencias se puede reducir más en la fase siguiente. La implementación de enrutamiento no garantiza un orden de procesamiento para los puntos de conexión coincidentes. Todas las coincidencias posibles se procesan a la vez. Las fases de coincidencia de direcciones URL se producen en el orden siguiente. ASP.NET Core:
La lista de puntos de conexión se prioriza según:
Todos los puntos de conexión coincidentes se procesan en cada fase hasta que se alcanza EndpointSelector. EndpointSelector
es la fase final. Elige el punto de conexión de prioridad más alta entre las coincidencias como la mejor coincidencia. Si hay otras coincidencias con la misma prioridad que la mejor, se inicia una excepción de coincidencia ambigua.
La prioridad de ruta se calcula en función de una plantilla de ruta más específica a la que se le asigna una prioridad más alta. Por ejemplo, considere las plantillas /hello
y /{message}
:
/hello
./hello
es más específica y, por tanto, tiene mayor prioridad.Por lo general, la precedencia de rutas realiza un buen trabajo de elegir la mejor coincidencia para los tipos de esquemas de dirección URL que se usan en la práctica. Use Order solo cuando sea necesario para evitar una ambigüedad.
Debido a los tipos de extensibilidad que proporciona el enrutamiento, el sistema de enrutamiento no puede calcular las rutas ambiguas por adelantado. Considere un ejemplo como las plantillas de ruta /{message:alpha}
y /{message:int}
:
alpha
solo coincide con caracteres alfabéticos.int
solo coincide con números.Aviso
El orden de las operaciones dentro de UseEndpoints no influye en el comportamiento del enrutamiento, con una excepción. MapControllerRoute y MapAreaRoute asignan de forma automática un valor de orden a sus puntos de conexión en función del orden en el que se hayan invocado. Esto simula el comportamiento a largo plazo de los controladores sin que el sistema de enrutamiento proporcione las mismas garantías que las implementaciones de enrutamiento anteriores.
Enrutamiento de puntos de conexión en ASP.NET Core:
La prioridad de la plantilla de ruta es un sistema que asigna a cada plantilla de ruta un valor en función de su especificidad. Precedencia de la plantilla de ruta:
Por ejemplo, considere las plantillas /Products/List
y /Products/{id}
. Sería razonable suponer que /Products/List
es una mejor coincidencia que /Products/{id}
para la ruta de dirección URL /Products/List
. Funciona porque el segmento literal /List
se considera que tiene una mayor prioridad que el segmento de parámetro /{id}
.
Los detalles de cómo funciona la precedencia están vinculados a cómo se definen las plantillas de ruta:
Generación de direcciones URL:
El enrutamiento de punto de conexión incluye la API LinkGenerator. LinkGenerator
es un servicio singleton disponible desde la DI. La API LinkGenerator
se puede usar fuera del contexto de una solicitud en ejecución. Mvc.IUrlHelper y los escenarios que dependen de IUrlHelper, como los asistentes de etiquetas, los de HTML y los resultados de acción, usan de forma interna la API LinkGenerator
para proporcionar funciones de generación de vínculos.
El generador de vínculos está respaldado por el concepto de una dirección y esquemas de direcciones. Un esquema de direcciones es una manera de determinar los puntos de conexión que se deben tener en cuenta para la generación de vínculos. Por ejemplo, los escenarios de nombre y valores de ruta de controladores y Razor Pages con los que muchos usuarios están familiarizados se implementan como un esquema de direcciones.
El generador de vínculos puede vincular a controladores y Razor Pages a través de los métodos de extensión siguientes:
Las sobrecargas de estos métodos aceptan argumentos que incluyan HttpContext
. Estos métodos son funcionalmente equivalentes a Url.Action y Url.Page, pero ofrecen flexibilidad y opciones adicionales.
Los métodos GetPath*
son más similares a Url.Action
y Url.Page
, dado que generan un URI que contiene una ruta de acceso absoluta. Los métodos GetUri*
siempre generan un URI absoluto que contiene un esquema y un host. Los métodos que aceptan HttpContext
generan un URI en el contexto de la solicitud que se ejecuta. A menos que se reemplacen, se usan los valores de ruta de ambiente, la ruta de acceso base de la dirección URL, el esquema y el host de la solicitud en ejecución.
Se llama a LinkGenerator con una dirección. La generación de un URI se produce en dos pasos:
Los métodos proporcionados por LinkGenerator admiten funciones estándar de generación de vínculos para cualquier tipo de dirección. La forma más práctica de usar el generador de vínculos es a través de métodos de extensión que realicen operaciones para un tipo de dirección específica:
Método de extensión | Descripción |
---|---|
GetPathByAddress | Genera un URI con una ruta de acceso absoluta en función de los valores proporcionados. |
GetUriByAddress | Genera un URI absoluto en función de los valores proporcionados. |
Aviso
Preste atención a las consecuencias siguientes de llamar a los métodos LinkGenerator:
Use los métodos de extensión GetUri*
con precaución en una configuración de aplicación en la que no se valide el encabezado Host
de las solicitudes entrantes. Si no se valida el encabezado Host
de las solicitudes entrantes, la entrada de la solicitud que no sea de confianza se puede devolver al cliente en los URI de una página o vista. Se recomienda que todas las aplicaciones de producción configuren su servidor para validar el encabezado Host
en función de valores válidos conocidos.
Use LinkGenerator con precaución en el middleware junto con Map
o MapWhen
. Map*
cambia la ruta de acceso base de la solicitud que se ejecuta, lo que afecta a la salida de la generación de vínculos. Todas las API de LinkGenerator permiten especificar una ruta de acceso base. Especifique una ruta de acceso base vacía para deshacer el efecto de Map*
en la generación de vínculos.
En el ejemplo siguiente, un middleware usa la API LinkGenerator para crear un vínculo a un método de acción que enumera los productos de la tienda. El uso del generador de vínculos mediante su inserción en una clase y la llamada a GenerateLink
está disponible para cualquier clase de una aplicación:
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.");
}
}
Los tokens de {}
definen parámetros de ruta que se enlazan si se encuentran coincidencias con la ruta. Se puede definir más de un parámetro de ruta en un segmento de ruta, pero deben estar separados por un valor literal. Por ejemplo:
{controller=Home}{action=Index}
No es una ruta válida, ya que no hay ningún valor literal entre {controller}
y {action}
. Los parámetros de ruta deben tener un nombre y, opcionalmente, atributos adicionales especificados.
El texto literal diferente de los parámetros de ruta (por ejemplo, {id}
) y el separador de ruta /
deben coincidir con el texto de la dirección URL. La coincidencia de texto no distingue mayúsculas de minúsculas y se basa en la representación descodificada de la ruta de las direcciones URL. Para que el delimitador de parámetro de ruta literal {
o }
coincida, repita el carácter para aplicar escape al carácter. Por ejemplo, {{
o }}
.
Asterisco *
o asterisco doble **
:
blog/{**slug}
: blog/
y después tenga cualquier valor.blog/
se asigna al valor de ruta slug.Aviso
Un parámetro catch-all puede relacionar rutas de forma incorrecta debido a un error en el enrutamiento. Las aplicaciones afectadas por este error tienen las características siguientes:
{**slug}"
)Para ver casos de ejemplo relacionados con este error, consulte los errores 18677 y 16579 en GitHub.
Se incluye una corrección de participación para este error en el SDK de .NET Core 3.1.301 y versiones posteriores. En el código que hay a continuación se establece un cambio interno que corrige este error:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Los parámetros comodín también pueden coincidir con una cadena vacía.
El parámetro comodín inserta los caracteres de escape correspondientes cuando se usa la ruta para generar una dirección URL, incluidos los caracteres /
de separación de ruta de acceso. Por ejemplo, la ruta foo/{*path}
con valores de ruta { path = "my/path" }
genera foo/my%2Fpath
. Tenga en cuenta la barra diagonal de escape. Para los caracteres separadores de ruta de acceso de ida y vuelta, use el prefijo de parámetro de ruta **
. La ruta foo/{**path}
con { path = "my/path" }
genera foo/my/path
.
Los patrones de dirección URL que intentan capturar un nombre de archivo con una extensión de archivo opcional tienen consideraciones adicionales. Por ejemplo, considere la plantilla files/{filename}.{ext?}
. Cuando existen valores para filename
y ext
, los dos valores se rellenan. Si solo existe un valor para filename
en la dirección URL, la ruta coincide porque el carácter .
final es opcional. Las direcciones URL siguientes coinciden con esta ruta:
/files/myFile.txt
/files/myFile
Los parámetros de ruta pueden tener valores predeterminados designados mediante la especificación del valor predeterminado después del nombre de parámetro, separado por un signo igual (=
). Por ejemplo, {controller=Home}
define Home
como el valor predeterminado de controller
. El valor predeterminado se usa si no hay ningún valor en la dirección URL para el parámetro. Los parámetros de ruta se pueden convertir en opcionales si se anexa un signo de interrogación (?
) al final del nombre del parámetro. Por ejemplo: id?
. La diferencia entre los valores opcionales y los parámetros de ruta predeterminados es:
Los parámetros de ruta pueden tener restricciones que deben coincidir con el valor de ruta enlazado desde la dirección URL. Al agregar :
y un nombre de restricción después del nombre del parámetro de ruta, se especifica una restricción insertada en un parámetro de ruta. Si la restricción requiere argumentos, se incluyen entre paréntesis (...)
después del nombre de restricción. Se pueden especificar varias restricciones insertadas si se anexa otro carácter :
y un nombre de restricción.
El nombre de restricción y los argumentos se pasan al servicio IInlineConstraintResolver para crear una instancia de IRouteConstraint para su uso en el procesamiento de direcciones URL. Por ejemplo, la plantilla de ruta blog/{article:minlength(10)}
especifica una restricción minlength
con el argumento 10
. Para obtener más información sobre las restricciones de ruta y una lista de las restricciones proporcionadas por el marco, vea la sección Restricciones de ruta.
Los parámetros de ruta también pueden tener transformadores de parámetros. Los transformadores de parámetros transforman el valor de un parámetro al generar vínculos y hacer coincidir acciones y páginas con direcciones URL. Como sucede con las restricciones, los transformadores de parámetros se pueden agregar en línea a un parámetro de ruta mediante la incorporación un carácter :
y un nombre de transformador después del nombre del parámetro de ruta. Por ejemplo, la plantilla de ruta blog/{article:slugify}
especifica un transformador slugify
. Para obtener más información sobre los transformadores de parámetros, vea la sección Transformadores de parámetros.
En la tabla siguiente se muestran plantillas de ruta de ejemplo y su comportamiento:
Plantilla de ruta | URI coincidente de ejemplo | El URI de la solicitud... |
---|---|---|
hello |
/hello |
Solo coincide con la ruta de acceso única /hello . |
{Page=Home} |
/ |
Coincide y establece Page en Home . |
{Page=Home} |
/Contact |
Coincide y establece Page en Contact . |
{controller}/{action}/{id?} |
/Products/List |
Se asigna al controlador Products y la acción List . |
{controller}/{action}/{id?} |
/Products/Details/123 |
Se asigna al controlador Products y la acción Details con id establecido en 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Se asigna al controlador Home y al método Index . id se pasa por alto. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Se asigna al controlador Products y al método Index . id se pasa por alto. |
El uso de una plantilla suele ser el método de enrutamiento más sencillo. Las restricciones y los valores predeterminados también se pueden especificar fuera de la plantilla de ruta.
Los segmentos complejos se procesan mediante la búsqueda de coincidencias de delimitadores literales de derecha a izquierda de un modo no expansivo. Por ejemplo, [Route("/a{b}c{d}")]
es un segmento complejo.
Los segmentos complejos funcionan de una manera determinada que se debe entender para usarlos correctamente. En el ejemplo de esta sección se muestra por qué los segmentos complejos solo funcionan bien cuando el texto del delimitador no aparece dentro de los valores de los parámetros. En casos más complejos es necesario usar una expresión regular y extraer los valores de forma manual.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Este es un resumen de los pasos que realiza el enrutamiento con la plantilla /a{b}c{d}
y la ruta de dirección URL /abcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo:
c
. Por tanto, se busca en /abcd
desde la derecha y se encuentra /ab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /ab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.Este es un ejemplo de un caso negativo en el que se usa la misma plantilla /a{b}c{d}
y la ruta de dirección URL /aabcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo. Este caso no es una coincidencia, que se explica mediante el mismo algoritmo:
c
. Por tanto, se busca en /aabcd
desde la derecha y se encuentra /aab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /aab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /a|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.a
, pero el algoritmo se ha quedado sin plantilla de ruta para analizar, por lo que no es una coincidencia.Como el algoritmo de búsqueda de coincidencias es no expansivo:
Las expresiones regulares proporcionan un mayor control sobre el comportamiento de búsqueda de coincidencias.
La coincidencia expansiva, también conocida como coincidencia máxima intenta encontrar la coincidencia más larga posible en el texto de entrada que satisface el patrón regex. La coincidencia no expansiva, también conocida como coincidencia diferida intenta encontrar la coincidencia más larga posible en el texto de entrada que satisface el patrón regex.
El enrutamiento con caracteres especiales puede dar lugar a resultados inesperados. Por ejemplo, considere un controlador con el siguiente método de acción:
[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;
}
Cuando string id
contiene los siguientes valores codificados, pueden producirse resultados inesperados:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Los parámetros de ruta no siempre están descodificados por URL. Este problema puede abordarse en el futuro. Para obtener más información, vea esta incidencia de GitHub.
Las restricciones de ruta se ejecutan cuando se ha producido una coincidencia con la dirección URL entrante y la ruta de dirección URL se convierte en tokens en valores de ruta. En general, las restricciones de ruta inspeccionan el valor de ruta asociado a través de la plantilla de ruta y deciden si el valor es aceptable o no. Algunas restricciones de ruta usan datos ajenos al valor de ruta para decidir si la solicitud se puede enrutar. Por ejemplo, HttpMethodRouteConstraint puede aceptar o rechazar una solicitud basada en su verbo HTTP. Las restricciones se usan en las solicitudes de enrutamiento y la generación de vínculos.
Aviso
No use las restricciones para la validación de entradas. Si se usan restricciones para la validación de entradas, las que no sean válidas generan una respuesta 404
No encontrado. Una entrada no válida debería generar 400
Solicitud incorrecta con un mensaje de error adecuado. Las restricciones de ruta se usan para eliminar la ambigüedad entre rutas similares, no para validar las entradas de una ruta determinada.
En la tabla siguiente se muestran restricciones de ruta de ejemplo y su comportamiento esperado:
restricción | Ejemplo | Coincidencias de ejemplo | Notas |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Coincide con cualquier entero |
bool |
{active:bool} |
true , FALSE |
Coincide con true o false . No distingue mayúsculas de minúsculas |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Coincide con un valor DateTime válido en la referencia cultural invariable. Vea la advertencia anterior. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Coincide con un valor decimal válido en la referencia cultural invariable. Vea la advertencia anterior. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Coincide con un valor double válido en la referencia cultural invariable. Vea la advertencia anterior. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Coincide con un valor float válido en la referencia cultural invariable. Vea la advertencia anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Coincide con un valor Guid válido |
long |
{ticks:long} |
123456789 , -123456789 |
Coincide con un valor long válido |
minlength(value) |
{username:minlength(4)} |
Rick |
La cadena debe tener al menos cuatro caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
La cadena no debe tener más de ocho caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
La cadena debe tener una longitud de exactamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
La cadena debe tener una longitud como mínimo de ocho caracteres y como máximo de 16 |
min(value) |
{age:min(18)} |
19 |
El valor entero debe ser como mínimo 18 |
max(value) |
{age:max(120)} |
91 |
El valor entero debe ser como máximo 120 |
range(min,max) |
{age:range(18,120)} |
91 |
El valor entero debe ser como mínimo 18 y máximo 120 |
alpha |
{name:alpha} |
Rick |
La cadena debe constar de uno o más caracteres alfabéticos, a -z y no distinguir mayúsculas de minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
La cadena debe coincidir con la expresión regular. Vea las sugerencias sobre cómo definir una expresión regular. |
required |
{name:required} |
Rick |
Se usa para exigir que un valor que no es de parámetro esté presente durante la generación de dirección URL |
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Es posible aplicar varias restricciones delimitadas por dos puntos a un único parámetro. Por ejemplo, la siguiente restricción permite limitar un parámetro a un valor entero de 1 o superior:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Aviso
Las restricciones de ruta que comprueban la dirección URL y que se convierten en un tipo CLR siempre usan la referencia cultural invariable. Por ejemplo, la conversión al tipo int
o DateTime
de CLR. Estas restricciones dan por supuesto que la dirección URL no es localizable. Las restricciones de ruta proporcionadas por el marco de trabajo no modifican los valores almacenados en los valores de ruta. Todos los valores de ruta analizados desde la dirección URL se almacenan como cadenas. Por ejemplo, la restricción float
intenta convertir el valor de ruta en un valor Float, pero el valor convertido se usa exclusivamente para comprobar que se puede convertir en Float.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Las expresiones regulares se pueden especificar como restricciones insertadas mediante la restricción de ruta regex(...)
. Los métodos de la familia MapControllerRoute también aceptan un literal de objeto de restricciones. Si se usa ese formato, los valores de cadena se interpretan como expresiones regulares.
En el código siguiente se usa una restricción de expresión regular insertada:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
En el código siguiente se usa un literal de objeto para especificar una restricción de expresión regular:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
El marco de trabajo de ASP.NET Core agrega RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
al constructor de expresiones regulares. Vea RegexOptions para obtener una descripción de estos miembros.
Las expresiones regulares usan delimitadores y tokens similares a los que usan el enrutamiento y el lenguaje C#. Es necesario usar secuencias de escape con los tokens de expresiones regulares. Para usar la expresión regular ^\d{3}-\d{2}-\d{4}$
en una restricción insertada, utilice una de las opciones siguientes:
\
proporcionados en la cadena como caracteres \\
en el archivo de código fuente de C# para aplicar secuencias de escape al carácter de escape de cadena \
.Para aplicar secuencias de escape a los caracteres delimitadores de parámetro de enrutamiento ({
, }
, [
y ]
), duplique los caracteres en la expresión, por ejemplo {{
, }}
, [[
y ]]
. En la tabla siguiente se muestra una expresión regular y su versión con la secuencia de escape:
Expresión regular | Expresión regular con secuencia de escape |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Las expresiones regulares que se usan en el enrutamiento suelen empezar con el carácter ^
y coincidir con la posición inicial de la cadena. Las expresiones suelen terminar con el carácter $
y coincidir con el final de la cadena. Los caracteres ^
y $
garantizan que la expresión regular coincide con el valor completo del parámetro de ruta. Sin los caracteres ^
y $
, la expresión regular coincide con cualquier subcadena de la cadena, lo que normalmente no es deseable. En la tabla siguiente se proporcionan ejemplos y se explica por qué coinciden o no:
Expresión | String | Coincidir con | Comentario |
---|---|---|---|
[a-z]{2} |
hello | Sí | Coincidencias de subcadenas |
[a-z]{2} |
123abc456 | Sí | Coincidencias de subcadenas |
[a-z]{2} |
mz | Sí | Coincide con la expresión |
[a-z]{2} |
MZ | Sí | No distingue mayúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Vea ^ y $ más arriba |
^[a-z]{2}$ |
123abc456 | No | Vea ^ y $ más arriba |
Para obtener más información sobre la sintaxis de expresiones regulares, vea Expresiones regulares de .NET Framework.
Para restringir un parámetro a un conjunto conocido de valores posibles, use una expresión regular. Por ejemplo, {action:regex(^(list|get|create)$)}
solo hace coincidir el valor de ruta action
con list
, get
o create
. Si se pasa al diccionario de restricciones, la cadena ^(list|get|create)$
es equivalente. Las restricciones que se pasan al diccionario de restricciones que no coinciden con una de las conocidas también se tratan como expresiones regulares. Las restricciones que se pasan en una plantilla y que no coinciden con una de las conocidas no se tratan como expresiones regulares.
Se pueden crear restricciones de ruta personalizadas mediante la implementación de la interfaz IRouteConstraint. La interfaz IRouteConstraint
contiene Match, que devuelve true
si se cumple la restricción, y false
en caso contrario.
Las restricciones de ruta personalizadas rara vez son necesarias. Antes de implementar una restricción de ruta personalizada, considere alternativas, como el enlace de modelos.
En la carpeta Constraints de ASP.NET Core se proporcionan buenos ejemplos de creación de restricciones. Por ejemplo, GuidRouteConstraint.
Para usar una restricción IRouteConstraint
personalizada, el tipo de restricción de ruta se debe registrar con el parámetro ConstraintMap de la aplicación en el contenedor de servicios. ConstraintMap
es un diccionario que asigna claves de restricciones de ruta a implementaciones de IRouteConstraint
que validen esas restricciones. El parámetro ConstraintMap
de una aplicación puede actualizarse en Program.cs
como parte de una llamada a AddRouting o configurando RouteOptions directamente con builder.Services.Configure<RouteOptions>
. Por ejemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
La restricción anterior se aplica en el código siguiente:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
La implementación de NoZeroesRouteConstraint
impide que 0
se use en un parámetro de ruta:
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);
}
}
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
El código anterior:
0
en el segmento {id}
de la ruta.El código siguiente es un enfoque mejor para impedir que se procese un valor id
que contenga 0
:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
El código anterior tiene las ventajas siguientes con respecto al enfoque de NoZeroesRouteConstraint
:
0
.Transformadores de parámetros:
Por ejemplo, un transformador de parámetros personalizado slugify
en el patrón de ruta blog\{article:slugify}
con Url.Action(new { article = "MyTestArticle" })
genera blog\my-test-article
.
Considere la siguiente implementación de IOutboundParameterTransformer
:
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 un transformador de parámetros en un patrón de ruta, configúrelo con ConstraintMap en Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
El marco ASP.NET Core usa los transformadores de parámetros para transformar el URI en el que se resuelve un punto de conexión. Por ejemplo, los transformadores de parámetros transforman los valores de ruta que se usan para hacer coincidir objetos area
, controller
, action
y page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Con la plantilla de ruta anterior, la acción SubscriptionManagementController.GetAll
coincide con el URI /subscription-management/get-all
. Un transformador de parámetros no cambia los valores de ruta usados para generar un vínculo. Por ejemplo, Url.Action("GetAll", "SubscriptionManagement")
genera /subscription-management/get-all
.
ASP.NET Core proporciona convenciones de API para usar transformadores de parámetros con rutas generadas:
Esta sección contiene una referencia para el algoritmo implementado por la generación de direcciones URL. En la práctica, los ejemplos más complejos de generación de direcciones URL usan controladores o Razor Pages. Vea Enrutamiento en controladores para obtener información adicional.
El proceso de generación de direcciones URL comienza con una llamada a LinkGenerator.GetPathByAddress o un método similar. Al método se le proporciona una dirección, un conjunto de valores de ruta y, opcionalmente, información sobre la solicitud actual de HttpContext
.
El primer paso consiste en usar la dirección para resolver un conjunto de puntos de conexión candidatos con una instancia de IEndpointAddressScheme<TAddress> que coincide con el tipo de la dirección.
Una vez que el esquema de direcciones encuentra el conjunto de candidatos, los puntos de conexión se ordenan y procesan de forma iterativa hasta que se realiza correctamente una operación de generación de direcciones URL. La generación de direcciones URL no comprueba si hay ambigüedades; el primer resultado devuelto es el resultado final.
El primer paso para solucionar problemas de generación de direcciones URL consiste en establecer el nivel de registro de Microsoft.AspNetCore.Routing
en TRACE
. LinkGenerator
registra muchos detalles sobre su procesamiento que pueden ser útiles para solucionar problemas.
Vea Referencia de generación de direcciones URL para obtener más información sobre la generación de direcciones URL.
Las direcciones son el concepto de la generación de direcciones URL que se usa para enlazar una llamada al generador de vínculos a un conjunto de puntos de conexión candidatos.
Las direcciones son un concepto extensible que incluyen dos implementaciones de forma predeterminada:
string
) como dirección: IUrlHelper
, aplicaciones auxiliares de etiquetas, aplicaciones auxiliares HTML, resultados de acciones, etc.El papel del esquema de direcciones consiste en establecer la asociación entre la dirección y los puntos de conexión coincidentes mediante criterios arbitrarios:
A partir de la solicitud actual, el enrutamiento accede a los valores de ruta del objeto HttpContext.Request.RouteValues
de la solicitud actual. Los valores asociados a la solicitud actual se conocen como valores de ambiente. Para mayor claridad, en la documentación se hace referencia a los valores de ruta que se pasan a los métodos como valores explícitos.
En el ejemplo siguiente se muestran valores de ambiente y valores explícitos. Proporciona valores de ambiente de la solicitud actual y 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);
}
// ...
El código anterior:
/Widget/Index/17
El código siguiente solo proporciona valores explícitos y valores sin ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
El método anterior devuelve /Home/Subscribe/17
.
El código siguiente en WidgetController
devuelve /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
En el código siguiente se proporciona el controlador a partir de los valores de ambiente de la solicitud actual y los valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
En el código anterior:
/Gadget/Edit/17
.action
especificado y los valores route
.En el código siguiente se proporcionan valores de ambiente de la solicitud actual y valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
En el código anterior se establece url
en /Edit/17
cuando la página de Razor de edición contiene la siguiente directiva de página:
@page "{id:int}"
Si la página de edición no contiene la plantilla de ruta "{id:int}"
, url
es /Edit?id=17
.
El comportamiento de IUrlHelper de MVC agrega una capa de complejidad además de las reglas descritas aquí:
IUrlHelper
siempre proporciona los valores de ruta de la solicitud actual como valores de ambiente.action
y controller
actuales como valores explícitos a menos que el desarrollador los invalide.page
actual como un valor explícito a menos que se invalide. IUrlHelper.Page
siempre invalida el valor de ruta handler
actual con null
como un valor explícito a menos que se invalide.A los usuarios a menudo les sorprenden los detalles del comportamiento de los valores de ambiente, ya que MVC no parece seguir sus propias reglas. Por motivos históricos y de compatibilidad, algunos valores de ruta como action
, controller
, page
y handler
tienen su propio comportamiento de caso especial.
La funcionalidad equivalente proporcionada por LinkGenerator.GetPathByAction
y LinkGenerator.GetPathByPage
duplica estas anomalías de IUrlHelper
por motivos de compatibilidad.
Una vez que se encuentra el conjunto de puntos de conexión candidatos, el algoritmo de generación de direcciones URL:
El primer paso de este proceso se denomina invalidación del valor de ruta. La invalidación del valor de ruta es el proceso por el que el enrutamiento decide qué valores de ruta de los valores de ambiente se deben usar y cuáles se deben omitir. Cada valor de ambiente se tiene en cuenta y se combina con los valores explícitos, o bien se pasa por alto.
La mejor manera de pensar en el rol de los valores de ambiente es que intentan ahorrar trabajo a los desarrolladores de aplicaciones, en algunos casos comunes. Tradicionalmente, los escenarios en los que los valores de ambiente son útiles están relacionados con MVC:
Las llamadas a LinkGenerator
o IUrlHelper
que devuelven null
se suelen deber a que no se comprende la invalidación del valor de ruta. Para solucionar problemas de invalidación del valor de ruta, especifique de forma explícita más valores de ruta para ver si eso resuelve el problema.
La invalidación del valor de ruta se basa en la suposición de que el esquema de direcciones URL de la aplicación es jerárquico, con una jerarquía formada de izquierda a derecha. Considere la posibilidad de usar la plantilla de ruta de controlador básica {controller}/{action}/{id?}
para hacerse una idea intuitiva de cómo funciona esto en la práctica. Un cambio en un valor invalida todos los valores de ruta que aparecen a la derecha. Esto refleja la suposición sobre la jerarquía. Si la aplicación tiene un valor de ambiente para id
y la operación especifica otro valor para controller
:
id
no se reutilizará porque {controller}
está a la izquierda de {id?}
.Algunos ejemplos demuestran este principio:
id
, se omite el valor de ambiente de id
. Se pueden usar los valores de ambiente para controller
y action
.action
, se omite cualquier valor de ambiente para action
. Se pueden usar los valores de ambiente para controller
. Si el valor explícito para action
es diferente del valor de ambiente para action
, no se usará el valor de id
. Si el valor explícito para action
es diferente del valor de ambiente para action
, se puede usar el valor de id
.controller
, se omite cualquier valor de ambiente para controller
. Si el valor explícito para controller
es diferente del valor de ambiente para controller
, no se usarán los valores de action
y id
. Si el valor explícito para controller
es igual que el valor de ambiente para controller
, se pueden usar los valores de action
y id
.Este proceso sea complica todavía más por la existencia de rutas de atributo y rutas convencionales dedicadas. Las rutas convencionales de controlador, como {controller}/{action}/{id?}
, especifican una jerarquía mediante parámetros de ruta. Para las rutas convencionales dedicadas y las rutas de atributo a controladores y Razor Pages:
En estos casos, la generación de direcciones URL define el concepto de valores necesarios. Los puntos de conexión creados por controladores y Razor Pages tienen valores necesarios especificados que permiten que la invalidación del valor de ruta funcione.
El algoritmo de invalidación del valor de ruta en detalle:
En este punto, la operación de generación de direcciones URL está lista para evaluar las restricciones de ruta. El conjunto de valores aceptados se combina con los valores predeterminados de parámetro, que se proporcionan a las restricciones. Si todas las restricciones son correctas, la operación continúa.
A continuación, se pueden usar los valores aceptados para expandir la plantilla de ruta. La plantilla de ruta se procesa:
Los valores que se proporcionan de forma explícita que no coinciden con un segmento de la ruta se agregan a la cadena de consulta. En la tabla siguiente se muestra el resultado cuando se usa la plantilla de ruta {controller}/{action}/{id?}
.
Valores de ambiente | Valores explícitos | Resultado |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Los parámetros de ruta opcionales deben aparecer después de todos los parámetros de ruta y literales obligatorios. En el código siguiente, los parámetros id
y name
deben aparecer después del parámetro color
:
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 ?? ""}");
}
}
En el código siguiente se muestra un ejemplo de un esquema de generación de direcciones URL que no es compatible con el enrutamiento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
En el código anterior, se usa el parámetro de ruta culture
para la localización. El objetivo es que el parámetro culture
siempre se acepte como valor de ambiente. Pero el parámetro culture
no se acepta como valor de ambiente debido al funcionamiento de los valores necesarios:
"default"
, el parámetro de ruta culture
está a la izquierda de controller
, por lo que los cambios en controller
no invalidarán culture
."blog"
, se considera que el parámetro de ruta culture
está a la derecha de controller
, que aparece en los valores necesarios.La clase LinkParser agrega compatibilidad con el análisis de una ruta de dirección URL en un conjunto de valores de ruta. El método ParsePathByEndpointName toma un nombre de punto de conexión y una ruta de dirección URL y devuelve un conjunto de valores de ruta extraídos de esta.
En el controlador de ejemplo siguiente, la acción GetProduct
usa una plantilla de ruta de api/Products/{id}
y tiene un valor de Name de GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
En la misma clase de controlador, la acción AddRelatedProduct
espera una ruta de dirección URL, pathToRelatedProduct
, que se puede proporcionar como parámetro de cadena 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"];
// ...
En el ejemplo anterior, la acción AddRelatedProduct
extrae el valor de ruta id
de la ruta de dirección URL. Por ejemplo, con una ruta de dirección URL de /api/Products/1
, el valor de relatedProductId
se establece en 1
. Este enfoque permite a los clientes de la API usar rutas de dirección URL al hacer referencia a recursos, sin necesidad de conocer cómo se estructura dicha dirección URL.
Los vínculos siguientes proporcionan información sobre cómo configurar los metadatos del punto de conexión:
[MinimumAgeAuthorize]
personalizadoRequireHost aplica una restricción a la ruta que requiere el host especificado. El parámetro RequireHost
o [Host] puede ser:
www.domain.com
, compara www.domain.com
con cualquier puerto.*.domain.com
, coincide con www.domain.com
, subdomain.domain.com
o www.subdomain.domain.com
en cualquier puerto.*:5000
, coincide con el puerto 5000 con cualquier host.www.domain.com:5000
o *.domain.com:5000
, coincide con el host y el puerto.Se pueden especificar varios parámetros mediante RequireHost
o [Host]
. La restricción coincide con los hosts válidos para cualquiera de los parámetros. Por ejemplo, [Host("domain.com", "*.domain.com")]
coincide con domain.com
, www.domain.com
y subdomain.domain.com
.
En el código siguiente se usa RequireHost
para requerir el host especificado en la ruta:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
En el código siguiente se usa el atributo [Host]
en el controlador para requerir cualquiera de los hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Cuando el atributo [Host]
se aplica al método de acción y al controlador:
Aviso
Las API que se basan en el encabezado host, como HttpRequest.Host y RequireHost, están sujetas a una posible suplantación de identidad por parte de los clientes.
Para evitar la suplantación de identidad de host y puerto, use uno de los métodos siguientes:
El método de extensión MapGroup ayuda a organizar grupos de puntos de conexión con un prefijo común. Reduce el código repetitivo y permite personalizar grupos completos de puntos de conexión con una sola llamada a métodos como RequireAuthorization y WithMetadata, que agregan metadatos de punto de conexión.
Por ejemplo, el código siguiente crea dos grupos similares de puntos de conexión:
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;
}
En este escenario, puede usar una dirección relativa para el encabezado Location
en el 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);
}
El primer grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /public/todos
y que sean accesibles sin autenticación. El segundo grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /private/todos
y que requieran autenticación.
La fábrica de filtros de punto de conexiónQueryPrivateTodos
es una función local que modifica los parámetros TodoDb
del controlador de ruta para permitir el acceso y almacenar datos privados de tareas pendientes.
Los grupos de rutas también admiten grupos anidados y patrones de prefijo complejos con parámetros y restricciones de ruta. En el ejemplo siguiente, y el controlador de rutas asignado al grupo user
puede capturar los parámetros de ruta {org}
y {group}
definidos en los prefijos del grupo externo.
El prefijo también puede estar vacío. Esto puede ser útil para agregar metadatos de punto de conexión o filtros a un grupo de puntos de conexión sin cambiar el patrón de ruta.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
La adición de filtros o metadatos a un grupo se comporta del mismo modo que la adición individual a cada punto de conexión antes de agregar filtros o metadatos adicionales que quizás se hayan agregado a un grupo interno o a un punto de conexión 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);
});
En el ejemplo anterior, el filtro externo registrará la solicitud entrante antes que el filtro interno aunque se haya agregado en segundo lugar. Dado que los filtros se aplicaron a diferentes grupos, el orden en que se agregaron el uno con respecto al otro no es importante. El orden en que se agregan los filtros es importante si se aplican al mismo grupo o punto de conexión específico.
Una solicitud a /outer/inner/
registrará lo siguiente:
/outer group filter
/inner group filter
MapGet filter
Cuando una aplicación tiene problemas de rendimiento, a menudo se sospecha que el enrutamiento es el problema. El motivo es que marcos como los controladores y Razor Pages notifican la cantidad de tiempo empleado en el marco de trabajo en sus mensajes de registro. Cuando hay una diferencia significativa entre el tiempo notificado por los controladores y el tiempo total de la solicitud:
El rendimiento del enrutamiento se prueba mediante miles de puntos de conexión. No es probable que una aplicación típica detecte un problema de rendimiento simplemente por ser demasiado grande. La causa raíz más común del rendimiento lento del enrutamiento suele ser middleware personalizado con un comportamiento incorrecto.
En el ejemplo de código siguiente se muestra una técnica básica para limitar el origen del retraso:
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 controlar el tiempo del enrutamiento:
Se trata de una forma básica de reducir el retraso cuando es significativo, por ejemplo, de más de 10ms
. Al restar Time 2
de Time 1
se notifica el tiempo invertido dentro del middleware UseRouting
.
En el código siguiente se usa un enfoque más compacto del código de control de tiempo 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.");
En la lista siguiente se proporciona información sobre las características de enrutamiento que son relativamente costosas en comparación con las plantillas de ruta básicas:
{x}-{y}-{z}
): De forma predeterminada, ASP.NET Core utiliza un algoritmo de enrutamiento que compara la memoria con el tiempo de CPU. Esto genera un buen resultado por el hecho de que el tiempo de coincidencia de enrutamiento solo depende de la longitud de la ruta de acceso con la que debe coincidir y no del número de rutas. Sin embargo, este enfoque puede ser potencialmente problemático en algunos casos, cuando la aplicación tiene un gran número de rutas (miles) y hay una gran cantidad de prefijos de variable en las rutas. Por ejemplo, si las rutas tienen parámetros en los primeros segmentos de la ruta, como {parameter}/some/literal
.
Es poco probable que una aplicación se ejecute en una situación en la que esto sea un problema, a menos que:
Microsoft.AspNetCore.Routing.Matching.DfaNode
.Hay varias técnicas y optimizaciones que se pueden aplicar a las rutas y que mejorarán en gran medida este escenario:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
, etc., siempre que sea posible.
MapDynamicControllerRoute
y MapDynamicPageRoute
.Cuando el enrutamiento coincide con un punto de conexión, normalmente permite que el resto de la canalización de middleware se ejecute antes de invocar la lógica del punto de conexión. Los servicios pueden reducir el uso de recursos filtrando las solicitudes conocidas al principio de la canalización. Use el método de extensión ShortCircuit para hacer que el enrutamiento invoque la lógica del punto de conexión inmediatamente y, a continuación, finalice la solicitud. Por ejemplo, es posible que una ruta determinada no tenga que pasar por la autenticación o el middleware CORS. En el ejemplo siguiente se cortocircuitan las solicitudes que coinciden con la ruta /short-circuit
:
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
Opcionalmente, el método ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) puede tomar un código de estado.
Use el método MapShortCircuit para configurar el cortocircuito para varias rutas a la vez, pasando a ella una matriz de parámetros de prefijos de dirección URL. Por ejemplo, los exploradores y los bots suelen sondear servidores para rutas de acceso conocidas como robots.txt
y favicon.ico
. Si la aplicación no tiene esos archivos, una línea de código puede configurar ambas rutas:
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
MapShortCircuit
devuelve IEndpointConventionBuilder para que se puedan agregar restricciones de ruta adicionales, como el filtrado de host.
Los métodos ShortCircuit
y MapShortCircuit
no afectan al middleware colocado antes de UseRouting
. Al intentar usar estos métodos con puntos de conexión que también tienen los metadatos [Authorize]
o [RequireCors]
, se producirá un error en las solicitudes con InvalidOperationException
. Estos metadatos se aplican por los atributos [Authorize]
o [EnableCors]
, o por los métodos RequireCors o RequireAuthorization.
Para ver el efecto de cortocircuitar el middleware, establezca la categoría de registro "Microsoft" en "Información" en appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Ejecute el código siguiente:
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();
El ejemplo siguiente procede de los registros de consola generados mediante la ejecución del punto de conexión /
. Incluye la salida del middleware de registro:
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
En el ejemplo siguiente se ejecuta el punto de conexión /short-circuit
. No tiene nada del middleware de registro porque el middleware estaba cortocircuitado:
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.
Esta sección contiene instrucciones para los autores de bibliotecas que realizan la compilación sobre el enrutamiento. Estos detalles están diseñados para garantizar que los desarrolladores de aplicaciones tengan una buena experiencia en el uso de bibliotecas y marcos que amplían el enrutamiento.
Para crear un marco que use el enrutamiento para la coincidencia de direcciones URL, empiece por definir una experiencia de usuario que se base en UseEndpoints.
REALICE la compilación sobre IEndpointRouteBuilder. Esto permite a los usuarios crear el marco de trabajo con otras características de ASP.NET Core sin confusión. Todas las plantillas de ASP.NET Core incluyen el enrutamiento. Asuma que el enrutamiento está presente y es familiar para los usuarios.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DEVUELVA un tipo concreto sellado a partir de una llamada a MapMyFramework(...)
que implemente IEndpointConventionBuilder. La mayoría de los métodos Map...
del marco siguen este patrón. La interfaz IEndpointConventionBuilder
:
La declaración de un tipo propio permite agregar funcionalidad específica del marco propia al generador. Es correcto encapsular un generador declarado por el marco y reenviarle llamadas.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE LA POSIBILIDAD de escribir un objeto EndpointDataSource propio. EndpointDataSource
es la primitiva de bajo nivel para declarar y actualizar una colección de puntos de conexión. EndpointDataSource
es una API eficaz que usan los controladores y Razor Pages. Para más información, vea Enrutamiento dinámico de puntos de conexión.
Las pruebas de enrutamiento tienen un ejemplo básico de un origen de datos que no es de actualización.
CONSIDERE la posibilidad de implementar GetGroupedEndpoints. Esto proporciona control completo sobre la ejecución de convenciones de grupo y los metadatos finales en los puntos de conexión agrupados. Por ejemplo, esto permite que las implementaciones personalizadas de EndpointDataSource
ejecuten filtros de punto de conexión agregados a grupos.
NO intente registrar un objeto EndpointDataSource
de forma predeterminada. Exija a los usuarios que registren el marco en UseEndpoints. La filosofía del enrutamiento es que nada se incluye de forma predeterminada y que UseEndpoints
es el lugar donde se registran los puntos de conexión.
CONSIDERE LA POSIBILIDAD de definir tipos de metadatos como una interfaz.
PERMITA el uso de los tipos de metadatos como atributo en clases y métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Los marcos como los controladores y Razor Pages admiten la aplicación de atributos de metadatos a tipos y métodos. Si declara tipos de metadatos:
La declaración de un tipo de metadatos como una interfaz agrega otro nivel de flexibilidad:
PERMITA que los metadatos se puedan invalidar, como se muestra en el ejemplo siguiente:
[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() { }
}
La mejor manera de seguir estas instrucciones es evitar la definición de metadatos de marcador:
La colección de metadatos está ordenada y admite la invalidación por prioridad. En el caso de los controladores, los metadatos del método de acción son más específicos.
PERMITA que el middleware sea útil con y sin el enrutamiento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como ejemplo de esta instrucción, considere la posibilidad de usar el middleware UseAuthorization
. El middleware de autorización permite pasar una directiva de reserva. La directiva de reserva, si se especifica, se aplica a:
Esto hace que el middleware de autorización sea útil fuera del contexto del enrutamiento. El middleware de autorización se puede usar para la programación de middleware tradicional.
Para ver la salida detallada del diagnóstico de cálculo de ruta, establezca Logging:LogLevel:Microsoft
en Debug
. En el entorno de desarrollo, establezca el nivel de registro en appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
El enrutamiento es responsable de hacer coincidir las solicitudes HTTP entrantes y de enviarlas a los puntos de conexión ejecutables de la aplicación. Los puntos de conexión son las unidades de código de control de solicitudes ejecutable de la aplicación. Se definen en la aplicación y se configuran al iniciarla. El proceso de búsqueda de coincidencias de puntos de conexión puede extraer valores de la dirección URL de la solicitud y proporcionarlos para el procesamiento de la solicitud. Con la información de los puntos de conexión de la aplicación, el enrutamiento también puede generar direcciones URL que se asignan a los puntos de conexión.
Las aplicaciones pueden configurar el enrutamiento mediante:
En este artículo se describen los detalles de bajo nivel del enrutamiento de ASP.NET Core. Para obtener información sobre la configuración del enrutamiento:
En el código siguiente se muestra un ejemplo básico de enrutamiento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
En el ejemplo anterior se incluye un único punto de conexión que usa el método MapGet:
GET
a la dirección URL raíz /
: Hello World!
en la respuesta HTTP.GET
o la dirección URL raíz no es /
, no se detecta ninguna ruta y se devuelve HTTP 404.El enrutamiento usa un par de middleware, registrado por UseRouting y UseEndpoints:
UseRouting
agrega coincidencia de rutas a la canalización de middleware. Este middleware examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia en función de la solicitud.UseEndpoints
agrega la ejecución del punto de conexión a la canalización de middleware. Ejecuta el delegado asociado al punto de conexión seleccionado.Normalmente, las aplicaciones no necesitan llamar a UseRouting
ni a UseEndpoints
. WebApplicationBuilder configura una canalización de middleware que encapsula el middleware agregado en Program.cs
con UseRouting
y UseEndpoints
. Sin embargo, las aplicaciones pueden cambiar el orden en que se ejecutan UseRouting
y UseEndpoints
llamando a estos métodos explícitamente. Por ejemplo, el código siguiente realiza una llamada explícita a UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
En el código anterior:
app.Use
registra un middleware personalizado que se ejecuta al principio de la canalización.UseRouting
configura el middleware de coincidencia de rutas para que se ejecute después del middleware personalizado.MapGet
se ejecuta al final de la canalización.Si el ejemplo anterior no incluyese una llamada a UseRouting
, el middleware personalizado se ejecutaría después del middleware de coincidencia de rutas.
El método MapGet
se usa para definir un punto de conexión. Un punto de conexión es algo que se puede:
Los puntos de conexión que la aplicación puede ejecutar y hacer coincidir se configuran en UseEndpoints
. Por ejemplo, MapGet, MapPost y métodos similares conectan delegados de solicitud al sistema de enrutamiento. Se pueden usar métodos adicionales para conectar características del marco ASP.NET Core al sistema de enrutamiento:
En el ejemplo siguiente se muestra el enrutamiento con una plantilla de ruta más sofisticada:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
La cadena /hello/{name:alpha}
es una plantilla de ruta. Se usa una plantilla de ruta para configurar la coincidencia del punto de conexión. En este caso, la plantilla coincide con:
/hello/Docs
./hello/
, seguido de una secuencia de caracteres alfabéticos. :alpha
aplica una restricción de ruta que solo coincide con caracteres alfabéticos. Las restricciones de ruta se explican más adelante en este artículo.El segundo segmento de la ruta de dirección URL, {name:alpha}
:
name
.En el ejemplo siguiente se muestra el enrutamiento con comprobaciones de estado y autorización:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
En el ejemplo anterior se muestra cómo:
La llamada a MapHealthChecks agrega un punto de conexión de comprobación de estado. Al encadenar RequireAuthorization a esta llamada, se adjunta una directiva de autorización al punto de conexión.
La llamada a UseAuthentication y UseAuthorization agrega el middleware de autenticación y autorización. Estos middleware se colocan entre UseRouting y UseEndpoints
para que puedan:
UseRouting
.En el ejemplo anterior, hay dos puntos de conexión, pero solo el de comprobación de estado tiene una directiva de autorización adjunta. Si la solicitud coincide con el punto de conexión de comprobación de estado, /healthz
, se realiza una comprobación de autorización. Esto demuestra que los puntos de conexión pueden tener datos adicionales adjuntos. Estos datos adicionales se denominan metadatos de punto de conexión:
El sistema de enrutamiento se basa en la canalización de middleware mediante la adición del eficaz concepto de punto de conexión. Los puntos de conexión representan unidades de la funcionalidad de la aplicación que son diferentes entre sí en cuanto al enrutamiento, la autorización y cualquier número de sistemas de ASP.NET Core.
Un punto de conexión de ASP.NET Core es:
En el código siguiente se muestra cómo recuperar e inspeccionar el punto de conexión que coincide con la solicitud actual:
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.");
El punto de conexión, si se selecciona, se puede recuperar de HttpContext
. Se pueden inspeccionar sus propiedades. Los objetos de punto de conexión son inmutables y no se pueden modificar después de crearlos. El tipo más común de punto de conexión es RouteEndpoint. RouteEndpoint
incluye información que permite que el sistema de enrutamiento lo seleccione.
En el código anterior, app.Use configura un middleware insertado.
En el código siguiente se muestra que, en función de dónde se llame a app.Use
en la canalización, es posible que no haya un punto de conexión:
// 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);
});
En el ejemplo anterior se agregan instrucciones Console.WriteLine
que muestran si se ha seleccionado un punto de conexión o no. Para mayor claridad, en el ejemplo se asigna un nombre para mostrar al punto de conexión /
proporcionado.
El ejemplo anterior también incluye llamadas a UseRouting
y UseEndpoints
para controlar exactamente cuándo se ejecuta este middleware dentro de la canalización.
Al ejecutar este código con una dirección URL de /
se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Al ejecutar este código con otra dirección URL se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Este resultado muestra que:
UseRouting
.UseRouting
y UseEndpoints.UseEndpoints
es terminal cuando se encuentra una coincidencia. El middleware de terminal se define más adelante en este artículo.UseEndpoints
solo se ejecuta cuando no se encuentra ninguna coincidencia.El middleware UseRouting
usa el método SetEndpoint para asociar el punto de conexión al contexto actual. Se puede reemplazar el middleware UseRouting
con lógica personalizada y seguir aprovechando las ventajas del uso de puntos de conexión. Los puntos de conexión son una primitiva de bajo nivel como middleware y no están unidos a la implementación de enrutamiento. La mayoría de las aplicaciones no necesitan reemplazar UseRouting
por lógica personalizada.
El middleware UseEndpoints
está diseñado para usarse junto con el middleware UseRouting
. La lógica básica para ejecutar un punto de conexión no es complicada. Use GetEndpoint para recuperar el punto de conexión y, después, invoque su propiedad RequestDelegate.
En el código siguiente se muestra cómo el middleware puede influir en el enrutamiento o reaccionar ante este:
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 { }
En el ejemplo anterior se muestran dos conceptos importantes:
UseRouting
para modificar los datos sobre los que funciona el enrutamiento.
UseRouting
y UseEndpoints para procesar los resultados del enrutamiento antes de que se ejecute el punto de conexión.
UseRouting
y UseEndpoints
: UseAuthorization
y UseCors
.En el código anterior se muestra un ejemplo de middleware personalizado que admite directivas por punto de conexión. El middleware escribe un registro de auditoría de acceso a datos confidenciales en la consola. El middleware se puede configurar para auditar un punto de conexión con los metadatos de RequiresAuditAttribute
. En este ejemplo se muestra un patrón opcional en el que solo se auditan los puntos de conexión marcados como confidenciales. Esta lógica se puede definir en orden inverso, para auditar todo lo que no esté marcado como seguro, por ejemplo. El sistema de metadatos de punto de conexión es flexible. Esta lógica se puede diseñar de la manera que mejor se adapte al caso de uso.
El código del ejemplo anterior está diseñado para mostrar los conceptos básicos de los puntos de conexión. No está pensado para su uso en producción. Una versión más completa de un middleware de registro de auditoría:
El valor RequiresAuditAttribute
de metadatos de directiva de auditoría se define como Attribute
para facilitar su uso con marcos basados en clases como los controladores y SignalR. Cuando se usa de ruta a código:
Los procedimientos recomendados para los tipos de metadatos son definirlos como interfaces o atributos. Las interfaces y los atributos permiten la reutilización del código. El sistema de metadatos es flexible y no impone ninguna limitación.
En el ejemplo siguiente se muestra el middleware de terminal y el enrutamiento:
// 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.");
El estilo de middleware que se muestra con Approach 1:
es middleware de terminal. Se denomina middleware de terminal porque realiza una operación de búsqueda de coincidencias:
Path == "/"
para el middleware y Path == "/Routing"
para el enrutamiento.next
.Se denomina middleware de terminal porque finaliza la búsqueda, ejecuta alguna funcionalidad y, después, devuelve un valor.
En la lista siguiente se compara el middleware de terminal con el enrutamiento:
next
.UseAuthorization
y UseCors
.
UseAuthorization
o UseCors
se necesita interactuar de forma manual con el sistema de autorización.Un punto de conexión define:
El middleware de terminal puede ser una herramienta eficaz, pero puede requerir:
Considere la posibilidad de realizar la integración con el enrutamiento antes de escribir middleware de terminal.
El middleware de terminal existente que se integra con Map o MapWhen normalmente se puede convertir en un punto de conexión compatible con el enrutamiento. MapHealthChecks muestra el patrón para enrutadores:
Map
y proporcione la nueva canalización de middleware.Map
desde el método de extensión.En el código siguiente se muestra el uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
En el ejemplo anterior se muestra la importancia de devolver el objeto de generador. Al devolver el objeto de generador, el desarrollador de aplicaciones puede configurar directivas como la autorización para el punto de conexión. En este ejemplo, el middleware de comprobaciones de estado no tiene una integración directa con el sistema de autorización.
El sistema de metadatos se ha creado como respuesta a los problemas detectados por los autores de extensibilidad mediante el middleware de terminal. El problema de cada middleware es implementar su propia integración con el sistema de autorización.
Cuando se ejecuta un middleware de enrutamiento, se establece un objeto Endpoint
y se enrutan los valores a una característica de solicitud en el objeto HttpContext desde la solicitud actual:
HttpRequest.RouteValues
obtiene la colección de valores de ruta.El middleware que se ejecuta después del middleware de enrutamiento puede inspeccionar el punto de conexión y tomar medidas. Por ejemplo, un middleware de autorización puede consultar la colección de metadatos del punto de conexión de una directiva de autorización. Después de que se ejecuta todo el middleware en la canalización de procesamiento de solicitudes, se invoca al delegado del punto de conexión seleccionado.
El sistema de enrutamiento en el enrutamiento de punto de conexión es responsable de todas las decisiones relativas al envío. Como el middleware aplica directivas en función del punto de conexión seleccionado, es importante que:
Aviso
En cuanto a la compatibilidad con versiones anteriores, cuando se ejecuta un delegado del punto de conexión de controlador o Razor Pages, las propiedades de RouteContext.RouteData se establecen en los valores adecuados en función del procesamiento de solicitudes realizado hasta el momento.
El tipo RouteContext
se marcará como obsoleto en una versión futura:
RouteData.Values
a HttpRequest.RouteValues
.RouteData.DataTokens
para recuperar IDataTokensMetadata de los metadatos del punto de conexión.La coincidencia de direcciones URL funciona en un conjunto configurable de fases. En cada fase, la salida es un conjunto de coincidencias. El conjunto de coincidencias se puede reducir más en la fase siguiente. La implementación de enrutamiento no garantiza un orden de procesamiento para los puntos de conexión coincidentes. Todas las coincidencias posibles se procesan a la vez. Las fases de coincidencia de direcciones URL se producen en el orden siguiente. ASP.NET Core:
La lista de puntos de conexión se prioriza según:
Todos los puntos de conexión coincidentes se procesan en cada fase hasta que se alcanza EndpointSelector. EndpointSelector
es la fase final. Elige el punto de conexión de prioridad más alta entre las coincidencias como la mejor coincidencia. Si hay otras coincidencias con la misma prioridad que la mejor, se inicia una excepción de coincidencia ambigua.
La prioridad de ruta se calcula en función de una plantilla de ruta más específica a la que se le asigna una prioridad más alta. Por ejemplo, considere las plantillas /hello
y /{message}
:
/hello
./hello
es más específica y, por tanto, tiene mayor prioridad.Por lo general, la precedencia de rutas realiza un buen trabajo de elegir la mejor coincidencia para los tipos de esquemas de dirección URL que se usan en la práctica. Use Order solo cuando sea necesario para evitar una ambigüedad.
Debido a los tipos de extensibilidad que proporciona el enrutamiento, el sistema de enrutamiento no puede calcular las rutas ambiguas por adelantado. Considere un ejemplo como las plantillas de ruta /{message:alpha}
y /{message:int}
:
alpha
solo coincide con caracteres alfabéticos.int
solo coincide con números.Aviso
El orden de las operaciones dentro de UseEndpoints no influye en el comportamiento del enrutamiento, con una excepción. MapControllerRoute y MapAreaRoute asignan de forma automática un valor de orden a sus puntos de conexión en función del orden en el que se hayan invocado. Esto simula el comportamiento a largo plazo de los controladores sin que el sistema de enrutamiento proporcione las mismas garantías que las implementaciones de enrutamiento anteriores.
Enrutamiento de puntos de conexión en ASP.NET Core:
La prioridad de la plantilla de ruta es un sistema que asigna a cada plantilla de ruta un valor en función de su especificidad. Precedencia de la plantilla de ruta:
Por ejemplo, considere las plantillas /Products/List
y /Products/{id}
. Sería razonable suponer que /Products/List
es una mejor coincidencia que /Products/{id}
para la ruta de dirección URL /Products/List
. Funciona porque el segmento literal /List
se considera que tiene una mayor prioridad que el segmento de parámetro /{id}
.
Los detalles de cómo funciona la precedencia están vinculados a cómo se definen las plantillas de ruta:
Generación de direcciones URL:
El enrutamiento de punto de conexión incluye la API LinkGenerator. LinkGenerator
es un servicio singleton disponible desde la DI. La API LinkGenerator
se puede usar fuera del contexto de una solicitud en ejecución. Mvc.IUrlHelper y los escenarios que dependen de IUrlHelper, como los asistentes de etiquetas, los de HTML y los resultados de acción, usan de forma interna la API LinkGenerator
para proporcionar funciones de generación de vínculos.
El generador de vínculos está respaldado por el concepto de una dirección y esquemas de direcciones. Un esquema de direcciones es una manera de determinar los puntos de conexión que se deben tener en cuenta para la generación de vínculos. Por ejemplo, los escenarios de nombre y valores de ruta de controladores y Razor Pages con los que muchos usuarios están familiarizados se implementan como un esquema de direcciones.
El generador de vínculos puede vincular a controladores y Razor Pages a través de los métodos de extensión siguientes:
Las sobrecargas de estos métodos aceptan argumentos que incluyan HttpContext
. Estos métodos son funcionalmente equivalentes a Url.Action y Url.Page, pero ofrecen flexibilidad y opciones adicionales.
Los métodos GetPath*
son más similares a Url.Action
y Url.Page
, dado que generan un URI que contiene una ruta de acceso absoluta. Los métodos GetUri*
siempre generan un URI absoluto que contiene un esquema y un host. Los métodos que aceptan HttpContext
generan un URI en el contexto de la solicitud que se ejecuta. A menos que se reemplacen, se usan los valores de ruta de ambiente, la ruta de acceso base de la dirección URL, el esquema y el host de la solicitud en ejecución.
Se llama a LinkGenerator con una dirección. La generación de un URI se produce en dos pasos:
Los métodos proporcionados por LinkGenerator admiten funciones estándar de generación de vínculos para cualquier tipo de dirección. La forma más práctica de usar el generador de vínculos es a través de métodos de extensión que realicen operaciones para un tipo de dirección específica:
Método de extensión | Descripción |
---|---|
GetPathByAddress | Genera un URI con una ruta de acceso absoluta en función de los valores proporcionados. |
GetUriByAddress | Genera un URI absoluto en función de los valores proporcionados. |
Aviso
Preste atención a las consecuencias siguientes de llamar a los métodos LinkGenerator:
Use los métodos de extensión GetUri*
con precaución en una configuración de aplicación en la que no se valide el encabezado Host
de las solicitudes entrantes. Si no se valida el encabezado Host
de las solicitudes entrantes, la entrada de la solicitud que no sea de confianza se puede devolver al cliente en los URI de una página o vista. Se recomienda que todas las aplicaciones de producción configuren su servidor para validar el encabezado Host
en función de valores válidos conocidos.
Use LinkGenerator con precaución en el middleware junto con Map
o MapWhen
. Map*
cambia la ruta de acceso base de la solicitud que se ejecuta, lo que afecta a la salida de la generación de vínculos. Todas las API de LinkGenerator permiten especificar una ruta de acceso base. Especifique una ruta de acceso base vacía para deshacer el efecto de Map*
en la generación de vínculos.
En el ejemplo siguiente, un middleware usa la API LinkGenerator para crear un vínculo a un método de acción que enumera los productos de la tienda. El uso del generador de vínculos mediante su inserción en una clase y la llamada a GenerateLink
está disponible para cualquier clase de una aplicación:
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.");
}
}
Los tokens de {}
definen parámetros de ruta que se enlazan si se encuentran coincidencias con la ruta. Se puede definir más de un parámetro de ruta en un segmento de ruta, pero deben estar separados por un valor literal. Por ejemplo:
{controller=Home}{action=Index}
No es una ruta válida, ya que no hay ningún valor literal entre {controller}
y {action}
. Los parámetros de ruta deben tener un nombre y, opcionalmente, atributos adicionales especificados.
El texto literal diferente de los parámetros de ruta (por ejemplo, {id}
) y el separador de ruta /
deben coincidir con el texto de la dirección URL. La coincidencia de texto no distingue mayúsculas de minúsculas y se basa en la representación descodificada de la ruta de las direcciones URL. Para que el delimitador de parámetro de ruta literal {
o }
coincida, repita el carácter para aplicar escape al carácter. Por ejemplo, {{
o }}
.
Asterisco *
o asterisco doble **
:
blog/{**slug}
: blog/
y después tenga cualquier valor.blog/
se asigna al valor de ruta slug.Aviso
Un parámetro catch-all puede relacionar rutas de forma incorrecta debido a un error en el enrutamiento. Las aplicaciones afectadas por este error tienen las características siguientes:
{**slug}"
)Para ver casos de ejemplo relacionados con este error, consulte los errores 18677 y 16579 en GitHub.
Se incluye una corrección de participación para este error en el SDK de .NET Core 3.1.301 y versiones posteriores. En el código que hay a continuación se establece un cambio interno que corrige este error:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Los parámetros comodín también pueden coincidir con una cadena vacía.
El parámetro comodín inserta los caracteres de escape correspondientes cuando se usa la ruta para generar una dirección URL, incluidos los caracteres /
de separación de ruta de acceso. Por ejemplo, la ruta foo/{*path}
con valores de ruta { path = "my/path" }
genera foo/my%2Fpath
. Tenga en cuenta la barra diagonal de escape. Para los caracteres separadores de ruta de acceso de ida y vuelta, use el prefijo de parámetro de ruta **
. La ruta foo/{**path}
con { path = "my/path" }
genera foo/my/path
.
Los patrones de dirección URL que intentan capturar un nombre de archivo con una extensión de archivo opcional tienen consideraciones adicionales. Por ejemplo, considere la plantilla files/{filename}.{ext?}
. Cuando existen valores para filename
y ext
, los dos valores se rellenan. Si solo existe un valor para filename
en la dirección URL, la ruta coincide porque el carácter .
final es opcional. Las direcciones URL siguientes coinciden con esta ruta:
/files/myFile.txt
/files/myFile
Los parámetros de ruta pueden tener valores predeterminados designados mediante la especificación del valor predeterminado después del nombre de parámetro, separado por un signo igual (=
). Por ejemplo, {controller=Home}
define Home
como el valor predeterminado de controller
. El valor predeterminado se usa si no hay ningún valor en la dirección URL para el parámetro. Los parámetros de ruta se pueden convertir en opcionales si se anexa un signo de interrogación (?
) al final del nombre del parámetro. Por ejemplo: id?
. La diferencia entre los valores opcionales y los parámetros de ruta predeterminados es:
Los parámetros de ruta pueden tener restricciones que deben coincidir con el valor de ruta enlazado desde la dirección URL. Al agregar :
y un nombre de restricción después del nombre del parámetro de ruta, se especifica una restricción insertada en un parámetro de ruta. Si la restricción requiere argumentos, se incluyen entre paréntesis (...)
después del nombre de restricción. Se pueden especificar varias restricciones insertadas si se anexa otro carácter :
y un nombre de restricción.
El nombre de restricción y los argumentos se pasan al servicio IInlineConstraintResolver para crear una instancia de IRouteConstraint para su uso en el procesamiento de direcciones URL. Por ejemplo, la plantilla de ruta blog/{article:minlength(10)}
especifica una restricción minlength
con el argumento 10
. Para obtener más información sobre las restricciones de ruta y una lista de las restricciones proporcionadas por el marco, vea la sección Restricciones de ruta.
Los parámetros de ruta también pueden tener transformadores de parámetros. Los transformadores de parámetros transforman el valor de un parámetro al generar vínculos y hacer coincidir acciones y páginas con direcciones URL. Como sucede con las restricciones, los transformadores de parámetros se pueden agregar en línea a un parámetro de ruta mediante la incorporación un carácter :
y un nombre de transformador después del nombre del parámetro de ruta. Por ejemplo, la plantilla de ruta blog/{article:slugify}
especifica un transformador slugify
. Para obtener más información sobre los transformadores de parámetros, vea la sección Transformadores de parámetros.
En la tabla siguiente se muestran plantillas de ruta de ejemplo y su comportamiento:
Plantilla de ruta | URI coincidente de ejemplo | El URI de la solicitud... |
---|---|---|
hello |
/hello |
Solo coincide con la ruta de acceso única /hello . |
{Page=Home} |
/ |
Coincide y establece Page en Home . |
{Page=Home} |
/Contact |
Coincide y establece Page en Contact . |
{controller}/{action}/{id?} |
/Products/List |
Se asigna al controlador Products y la acción List . |
{controller}/{action}/{id?} |
/Products/Details/123 |
Se asigna al controlador Products y la acción Details con id establecido en 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Se asigna al controlador Home y al método Index . id se pasa por alto. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Se asigna al controlador Products y al método Index . id se pasa por alto. |
El uso de una plantilla suele ser el método de enrutamiento más sencillo. Las restricciones y los valores predeterminados también se pueden especificar fuera de la plantilla de ruta.
Los segmentos complejos se procesan mediante la búsqueda de coincidencias de delimitadores literales de derecha a izquierda de un modo no expansivo. Por ejemplo, [Route("/a{b}c{d}")]
es un segmento complejo.
Los segmentos complejos funcionan de una manera determinada que se debe entender para usarlos correctamente. En el ejemplo de esta sección se muestra por qué los segmentos complejos solo funcionan bien cuando el texto del delimitador no aparece dentro de los valores de los parámetros. En casos más complejos es necesario usar una expresión regular y extraer los valores de forma manual.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Este es un resumen de los pasos que realiza el enrutamiento con la plantilla /a{b}c{d}
y la ruta de dirección URL /abcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo:
c
. Por tanto, se busca en /abcd
desde la derecha y se encuentra /ab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /ab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.Este es un ejemplo de un caso negativo en el que se usa la misma plantilla /a{b}c{d}
y la ruta de dirección URL /aabcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo. Este caso no es una coincidencia, que se explica mediante el mismo algoritmo:
c
. Por tanto, se busca en /aabcd
desde la derecha y se encuentra /aab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /aab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /a|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.a
, pero el algoritmo se ha quedado sin plantilla de ruta para analizar, por lo que no es una coincidencia.Como el algoritmo de búsqueda de coincidencias es no expansivo:
Las expresiones regulares proporcionan un mayor control sobre el comportamiento de búsqueda de coincidencias.
La coincidencia expansiva, también conocida como coincidencia diferida, coincide con la cadena más grande posible. La búsqueda no expansiva coincide con la cadena más pequeña posible.
El enrutamiento con caracteres especiales puede dar lugar a resultados inesperados. Por ejemplo, considere un controlador con el siguiente método de acción:
[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;
}
Cuando string id
contiene los siguientes valores codificados, pueden producirse resultados inesperados:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Los parámetros de ruta no siempre están descodificados por URL. Este problema puede abordarse en el futuro. Para obtener más información, vea esta incidencia de GitHub.
Las restricciones de ruta se ejecutan cuando se ha producido una coincidencia con la dirección URL entrante y la ruta de dirección URL se convierte en tokens en valores de ruta. En general, las restricciones de ruta inspeccionan el valor de ruta asociado a través de la plantilla de ruta y deciden si el valor es aceptable o no. Algunas restricciones de ruta usan datos ajenos al valor de ruta para decidir si la solicitud se puede enrutar. Por ejemplo, HttpMethodRouteConstraint puede aceptar o rechazar una solicitud basada en su verbo HTTP. Las restricciones se usan en las solicitudes de enrutamiento y la generación de vínculos.
Aviso
No use las restricciones para la validación de entradas. Si se usan restricciones para la validación de entradas, las que no sean válidas generan una respuesta 404
No encontrado. Una entrada no válida debería generar 400
Solicitud incorrecta con un mensaje de error adecuado. Las restricciones de ruta se usan para eliminar la ambigüedad entre rutas similares, no para validar las entradas de una ruta determinada.
En la tabla siguiente se muestran restricciones de ruta de ejemplo y su comportamiento esperado:
restricción | Ejemplo | Coincidencias de ejemplo | Notas |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Coincide con cualquier entero |
bool |
{active:bool} |
true , FALSE |
Coincide con true o false . No distingue mayúsculas de minúsculas |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Coincide con un valor DateTime válido en la referencia cultural invariable. Vea la advertencia anterior. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Coincide con un valor decimal válido en la referencia cultural invariable. Vea la advertencia anterior. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Coincide con un valor double válido en la referencia cultural invariable. Vea la advertencia anterior. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Coincide con un valor float válido en la referencia cultural invariable. Vea la advertencia anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Coincide con un valor Guid válido |
long |
{ticks:long} |
123456789 , -123456789 |
Coincide con un valor long válido |
minlength(value) |
{username:minlength(4)} |
Rick |
La cadena debe tener al menos cuatro caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
La cadena no debe tener más de ocho caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
La cadena debe tener una longitud de exactamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
La cadena debe tener una longitud como mínimo de ocho caracteres y como máximo de 16 |
min(value) |
{age:min(18)} |
19 |
El valor entero debe ser como mínimo 18 |
max(value) |
{age:max(120)} |
91 |
El valor entero debe ser como máximo 120 |
range(min,max) |
{age:range(18,120)} |
91 |
El valor entero debe ser como mínimo 18 y máximo 120 |
alpha |
{name:alpha} |
Rick |
La cadena debe constar de uno o más caracteres alfabéticos, a -z y no distinguir mayúsculas de minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
La cadena debe coincidir con la expresión regular. Vea las sugerencias sobre cómo definir una expresión regular. |
required |
{name:required} |
Rick |
Se usa para exigir que un valor que no es de parámetro esté presente durante la generación de dirección URL |
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Es posible aplicar varias restricciones delimitadas por dos puntos a un único parámetro. Por ejemplo, la siguiente restricción permite limitar un parámetro a un valor entero de 1 o superior:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Aviso
Las restricciones de ruta que comprueban la dirección URL y que se convierten en un tipo CLR siempre usan la referencia cultural invariable. Por ejemplo, la conversión al tipo int
o DateTime
de CLR. Estas restricciones dan por supuesto que la dirección URL no es localizable. Las restricciones de ruta proporcionadas por el marco de trabajo no modifican los valores almacenados en los valores de ruta. Todos los valores de ruta analizados desde la dirección URL se almacenan como cadenas. Por ejemplo, la restricción float
intenta convertir el valor de ruta en un valor Float, pero el valor convertido se usa exclusivamente para comprobar que se puede convertir en Float.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Las expresiones regulares se pueden especificar como restricciones insertadas mediante la restricción de ruta regex(...)
. Los métodos de la familia MapControllerRoute también aceptan un literal de objeto de restricciones. Si se usa ese formato, los valores de cadena se interpretan como expresiones regulares.
En el código siguiente se usa una restricción de expresión regular insertada:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
En el código siguiente se usa un literal de objeto para especificar una restricción de expresión regular:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
El marco de trabajo de ASP.NET Core agrega RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
al constructor de expresiones regulares. Vea RegexOptions para obtener una descripción de estos miembros.
Las expresiones regulares usan delimitadores y tokens similares a los que usan el enrutamiento y el lenguaje C#. Es necesario usar secuencias de escape con los tokens de expresiones regulares. Para usar la expresión regular ^\d{3}-\d{2}-\d{4}$
en una restricción insertada, utilice una de las opciones siguientes:
\
proporcionados en la cadena como caracteres \\
en el archivo de código fuente de C# para aplicar secuencias de escape al carácter de escape de cadena \
.Para aplicar secuencias de escape a los caracteres delimitadores de parámetro de enrutamiento ({
, }
, [
y ]
), duplique los caracteres en la expresión, por ejemplo {{
, }}
, [[
y ]]
. En la tabla siguiente se muestra una expresión regular y su versión con la secuencia de escape:
Expresión regular | Expresión regular con secuencia de escape |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Las expresiones regulares que se usan en el enrutamiento suelen empezar con el carácter ^
y coincidir con la posición inicial de la cadena. Las expresiones suelen terminar con el carácter $
y coincidir con el final de la cadena. Los caracteres ^
y $
garantizan que la expresión regular coincide con el valor completo del parámetro de ruta. Sin los caracteres ^
y $
, la expresión regular coincide con cualquier subcadena de la cadena, lo que normalmente no es deseable. En la tabla siguiente se proporcionan ejemplos y se explica por qué coinciden o no:
Expresión | String | Coincidir con | Comentario |
---|---|---|---|
[a-z]{2} |
hello | Sí | Coincidencias de subcadenas |
[a-z]{2} |
123abc456 | Sí | Coincidencias de subcadenas |
[a-z]{2} |
mz | Sí | Coincide con la expresión |
[a-z]{2} |
MZ | Sí | No distingue mayúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Vea ^ y $ más arriba |
^[a-z]{2}$ |
123abc456 | No | Vea ^ y $ más arriba |
Para obtener más información sobre la sintaxis de expresiones regulares, vea Expresiones regulares de .NET Framework.
Para restringir un parámetro a un conjunto conocido de valores posibles, use una expresión regular. Por ejemplo, {action:regex(^(list|get|create)$)}
solo hace coincidir el valor de ruta action
con list
, get
o create
. Si se pasa al diccionario de restricciones, la cadena ^(list|get|create)$
es equivalente. Las restricciones que se pasan al diccionario de restricciones que no coinciden con una de las conocidas también se tratan como expresiones regulares. Las restricciones que se pasan en una plantilla y que no coinciden con una de las conocidas no se tratan como expresiones regulares.
Se pueden crear restricciones de ruta personalizadas mediante la implementación de la interfaz IRouteConstraint. La interfaz IRouteConstraint
contiene Match, que devuelve true
si se cumple la restricción, y false
en caso contrario.
Las restricciones de ruta personalizadas rara vez son necesarias. Antes de implementar una restricción de ruta personalizada, considere alternativas, como el enlace de modelos.
En la carpeta Constraints de ASP.NET Core se proporcionan buenos ejemplos de creación de restricciones. Por ejemplo, GuidRouteConstraint.
Para usar una restricción IRouteConstraint
personalizada, el tipo de restricción de ruta se debe registrar con el parámetro ConstraintMap de la aplicación en el contenedor de servicios. ConstraintMap
es un diccionario que asigna claves de restricciones de ruta a implementaciones de IRouteConstraint
que validen esas restricciones. El parámetro ConstraintMap
de una aplicación puede actualizarse en Program.cs
como parte de una llamada a AddRouting o configurando RouteOptions directamente con builder.Services.Configure<RouteOptions>
. Por ejemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
La restricción anterior se aplica en el código siguiente:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
La implementación de NoZeroesRouteConstraint
impide que 0
se use en un parámetro de ruta:
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);
}
}
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
El código anterior:
0
en el segmento {id}
de la ruta.El código siguiente es un enfoque mejor para impedir que se procese un valor id
que contenga 0
:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
El código anterior tiene las ventajas siguientes con respecto al enfoque de NoZeroesRouteConstraint
:
0
.Transformadores de parámetros:
Por ejemplo, un transformador de parámetros personalizado slugify
en el patrón de ruta blog\{article:slugify}
con Url.Action(new { article = "MyTestArticle" })
genera blog\my-test-article
.
Considere la siguiente implementación de IOutboundParameterTransformer
:
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 un transformador de parámetros en un patrón de ruta, configúrelo con ConstraintMap en Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
El marco ASP.NET Core usa los transformadores de parámetros para transformar el URI en el que se resuelve un punto de conexión. Por ejemplo, los transformadores de parámetros transforman los valores de ruta que se usan para hacer coincidir objetos area
, controller
, action
y page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Con la plantilla de ruta anterior, la acción SubscriptionManagementController.GetAll
coincide con el URI /subscription-management/get-all
. Un transformador de parámetros no cambia los valores de ruta usados para generar un vínculo. Por ejemplo, Url.Action("GetAll", "SubscriptionManagement")
genera /subscription-management/get-all
.
ASP.NET Core proporciona convenciones de API para usar transformadores de parámetros con rutas generadas:
Esta sección contiene una referencia para el algoritmo implementado por la generación de direcciones URL. En la práctica, los ejemplos más complejos de generación de direcciones URL usan controladores o Razor Pages. Vea Enrutamiento en controladores para obtener información adicional.
El proceso de generación de direcciones URL comienza con una llamada a LinkGenerator.GetPathByAddress o un método similar. Al método se le proporciona una dirección, un conjunto de valores de ruta y, opcionalmente, información sobre la solicitud actual de HttpContext
.
El primer paso consiste en usar la dirección para resolver un conjunto de puntos de conexión candidatos con una instancia de IEndpointAddressScheme<TAddress> que coincide con el tipo de la dirección.
Una vez que el esquema de direcciones encuentra el conjunto de candidatos, los puntos de conexión se ordenan y procesan de forma iterativa hasta que se realiza correctamente una operación de generación de direcciones URL. La generación de direcciones URL no comprueba si hay ambigüedades; el primer resultado devuelto es el resultado final.
El primer paso para solucionar problemas de generación de direcciones URL consiste en establecer el nivel de registro de Microsoft.AspNetCore.Routing
en TRACE
. LinkGenerator
registra muchos detalles sobre su procesamiento que pueden ser útiles para solucionar problemas.
Vea Referencia de generación de direcciones URL para obtener más información sobre la generación de direcciones URL.
Las direcciones son el concepto de la generación de direcciones URL que se usa para enlazar una llamada al generador de vínculos a un conjunto de puntos de conexión candidatos.
Las direcciones son un concepto extensible que incluyen dos implementaciones de forma predeterminada:
string
) como dirección: IUrlHelper
, aplicaciones auxiliares de etiquetas, aplicaciones auxiliares HTML, resultados de acciones, etc.El papel del esquema de direcciones consiste en establecer la asociación entre la dirección y los puntos de conexión coincidentes mediante criterios arbitrarios:
A partir de la solicitud actual, el enrutamiento accede a los valores de ruta del objeto HttpContext.Request.RouteValues
de la solicitud actual. Los valores asociados a la solicitud actual se conocen como valores de ambiente. Para mayor claridad, en la documentación se hace referencia a los valores de ruta que se pasan a los métodos como valores explícitos.
En el ejemplo siguiente se muestran valores de ambiente y valores explícitos. Proporciona valores de ambiente de la solicitud actual y 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);
}
// ...
El código anterior:
/Widget/Index/17
El código siguiente solo proporciona valores explícitos y valores sin ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
El método anterior devuelve /Home/Subscribe/17
.
El código siguiente en WidgetController
devuelve /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
En el código siguiente se proporciona el controlador a partir de los valores de ambiente de la solicitud actual y los valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
En el código anterior:
/Gadget/Edit/17
.action
especificado y los valores route
.En el código siguiente se proporcionan valores de ambiente de la solicitud actual y valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
En el código anterior se establece url
en /Edit/17
cuando la página de Razor de edición contiene la siguiente directiva de página:
@page "{id:int}"
Si la página de edición no contiene la plantilla de ruta "{id:int}"
, url
es /Edit?id=17
.
El comportamiento de IUrlHelper de MVC agrega una capa de complejidad además de las reglas descritas aquí:
IUrlHelper
siempre proporciona los valores de ruta de la solicitud actual como valores de ambiente.action
y controller
actuales como valores explícitos a menos que el desarrollador los invalide.page
actual como un valor explícito a menos que se invalide. IUrlHelper.Page
siempre invalida el valor de ruta handler
actual con null
como un valor explícito a menos que se invalide.A los usuarios a menudo les sorprenden los detalles del comportamiento de los valores de ambiente, ya que MVC no parece seguir sus propias reglas. Por motivos históricos y de compatibilidad, algunos valores de ruta como action
, controller
, page
y handler
tienen su propio comportamiento de caso especial.
La funcionalidad equivalente proporcionada por LinkGenerator.GetPathByAction
y LinkGenerator.GetPathByPage
duplica estas anomalías de IUrlHelper
por motivos de compatibilidad.
Una vez que se encuentra el conjunto de puntos de conexión candidatos, el algoritmo de generación de direcciones URL:
El primer paso de este proceso se denomina invalidación del valor de ruta. La invalidación del valor de ruta es el proceso por el que el enrutamiento decide qué valores de ruta de los valores de ambiente se deben usar y cuáles se deben omitir. Cada valor de ambiente se tiene en cuenta y se combina con los valores explícitos, o bien se pasa por alto.
La mejor manera de pensar en el rol de los valores de ambiente es que intentan ahorrar trabajo a los desarrolladores de aplicaciones, en algunos casos comunes. Tradicionalmente, los escenarios en los que los valores de ambiente son útiles están relacionados con MVC:
Las llamadas a LinkGenerator
o IUrlHelper
que devuelven null
se suelen deber a que no se comprende la invalidación del valor de ruta. Para solucionar problemas de invalidación del valor de ruta, especifique de forma explícita más valores de ruta para ver si eso resuelve el problema.
La invalidación del valor de ruta se basa en la suposición de que el esquema de direcciones URL de la aplicación es jerárquico, con una jerarquía formada de izquierda a derecha. Considere la posibilidad de usar la plantilla de ruta de controlador básica {controller}/{action}/{id?}
para hacerse una idea intuitiva de cómo funciona esto en la práctica. Un cambio en un valor invalida todos los valores de ruta que aparecen a la derecha. Esto refleja la suposición sobre la jerarquía. Si la aplicación tiene un valor de ambiente para id
y la operación especifica otro valor para controller
:
id
no se reutilizará porque {controller}
está a la izquierda de {id?}
.Algunos ejemplos demuestran este principio:
id
, se omite el valor de ambiente de id
. Se pueden usar los valores de ambiente para controller
y action
.action
, se omite cualquier valor de ambiente para action
. Se pueden usar los valores de ambiente para controller
. Si el valor explícito para action
es diferente del valor de ambiente para action
, no se usará el valor de id
. Si el valor explícito para action
es diferente del valor de ambiente para action
, se puede usar el valor de id
.controller
, se omite cualquier valor de ambiente para controller
. Si el valor explícito para controller
es diferente del valor de ambiente para controller
, no se usarán los valores de action
y id
. Si el valor explícito para controller
es igual que el valor de ambiente para controller
, se pueden usar los valores de action
y id
.Este proceso sea complica todavía más por la existencia de rutas de atributo y rutas convencionales dedicadas. Las rutas convencionales de controlador, como {controller}/{action}/{id?}
, especifican una jerarquía mediante parámetros de ruta. Para las rutas convencionales dedicadas y las rutas de atributo a controladores y Razor Pages:
En estos casos, la generación de direcciones URL define el concepto de valores necesarios. Los puntos de conexión creados por controladores y Razor Pages tienen valores necesarios especificados que permiten que la invalidación del valor de ruta funcione.
El algoritmo de invalidación del valor de ruta en detalle:
En este punto, la operación de generación de direcciones URL está lista para evaluar las restricciones de ruta. El conjunto de valores aceptados se combina con los valores predeterminados de parámetro, que se proporcionan a las restricciones. Si todas las restricciones son correctas, la operación continúa.
A continuación, se pueden usar los valores aceptados para expandir la plantilla de ruta. La plantilla de ruta se procesa:
Los valores que se proporcionan de forma explícita que no coinciden con un segmento de la ruta se agregan a la cadena de consulta. En la tabla siguiente se muestra el resultado cuando se usa la plantilla de ruta {controller}/{action}/{id?}
.
Valores de ambiente | Valores explícitos | Resultado |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
Los parámetros de ruta opcionales deben aparecer después de todos los parámetros de ruta obligatorios. En el código siguiente, los parámetros id
y name
deben aparecer después del parámetro color
:
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 ?? ""}");
}
}
En el código siguiente se muestra un ejemplo de un esquema de generación de direcciones URL que no es compatible con el enrutamiento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
En el código anterior, se usa el parámetro de ruta culture
para la localización. El objetivo es que el parámetro culture
siempre se acepte como valor de ambiente. Pero el parámetro culture
no se acepta como valor de ambiente debido al funcionamiento de los valores necesarios:
"default"
, el parámetro de ruta culture
está a la izquierda de controller
, por lo que los cambios en controller
no invalidarán culture
."blog"
, se considera que el parámetro de ruta culture
está a la derecha de controller
, que aparece en los valores necesarios.La clase LinkParser agrega compatibilidad con el análisis de una ruta de dirección URL en un conjunto de valores de ruta. El método ParsePathByEndpointName toma un nombre de punto de conexión y una ruta de dirección URL y devuelve un conjunto de valores de ruta extraídos de esta.
En el controlador de ejemplo siguiente, la acción GetProduct
usa una plantilla de ruta de api/Products/{id}
y tiene un valor de Name de GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
En la misma clase de controlador, la acción AddRelatedProduct
espera una ruta de dirección URL, pathToRelatedProduct
, que se puede proporcionar como parámetro de cadena 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"];
// ...
En el ejemplo anterior, la acción AddRelatedProduct
extrae el valor de ruta id
de la ruta de dirección URL. Por ejemplo, con una ruta de dirección URL de /api/Products/1
, el valor de relatedProductId
se establece en 1
. Este enfoque permite a los clientes de la API usar rutas de dirección URL al hacer referencia a recursos, sin necesidad de conocer cómo se estructura dicha dirección URL.
Los vínculos siguientes proporcionan información sobre cómo configurar los metadatos del punto de conexión:
[MinimumAgeAuthorize]
personalizadoRequireHost aplica una restricción a la ruta que requiere el host especificado. El parámetro RequireHost
o [Host] puede ser:
www.domain.com
, compara www.domain.com
con cualquier puerto.*.domain.com
, coincide con www.domain.com
, subdomain.domain.com
o www.subdomain.domain.com
en cualquier puerto.*:5000
, coincide con el puerto 5000 con cualquier host.www.domain.com:5000
o *.domain.com:5000
, coincide con el host y el puerto.Se pueden especificar varios parámetros mediante RequireHost
o [Host]
. La restricción coincide con los hosts válidos para cualquiera de los parámetros. Por ejemplo, [Host("domain.com", "*.domain.com")]
coincide con domain.com
, www.domain.com
y subdomain.domain.com
.
En el código siguiente se usa RequireHost
para requerir el host especificado en la ruta:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
En el código siguiente se usa el atributo [Host]
en el controlador para requerir cualquiera de los hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Cuando el atributo [Host]
se aplica al método de acción y al controlador:
El método de extensión MapGroup ayuda a organizar grupos de puntos de conexión con un prefijo común. Reduce el código repetitivo y permite personalizar grupos completos de puntos de conexión con una sola llamada a métodos como RequireAuthorization y WithMetadata, que agregan metadatos de punto de conexión.
Por ejemplo, el código siguiente crea dos grupos similares de puntos de conexión:
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;
}
En este escenario, puede usar una dirección relativa para el encabezado Location
en el 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);
}
El primer grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /public/todos
y que sean accesibles sin autenticación. El segundo grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /private/todos
y que requieran autenticación.
La fábrica de filtros de punto de conexiónQueryPrivateTodos
es una función local que modifica los parámetros TodoDb
del controlador de ruta para permitir el acceso y almacenar datos privados de tareas pendientes.
Los grupos de rutas también admiten grupos anidados y patrones de prefijo complejos con parámetros y restricciones de ruta. En el ejemplo siguiente, y el controlador de rutas asignado al grupo user
puede capturar los parámetros de ruta {org}
y {group}
definidos en los prefijos del grupo externo.
El prefijo también puede estar vacío. Esto puede ser útil para agregar metadatos de punto de conexión o filtros a un grupo de puntos de conexión sin cambiar el patrón de ruta.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
La adición de filtros o metadatos a un grupo se comporta del mismo modo que la adición individual a cada punto de conexión antes de agregar filtros o metadatos adicionales que quizás se hayan agregado a un grupo interno o a un punto de conexión 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);
});
En el ejemplo anterior, el filtro externo registrará la solicitud entrante antes que el filtro interno aunque se haya agregado en segundo lugar. Dado que los filtros se aplicaron a diferentes grupos, el orden en que se agregaron el uno con respecto al otro no es importante. El orden en que se agregan los filtros es importante si se aplican al mismo grupo o punto de conexión específico.
Una solicitud a /outer/inner/
registrará lo siguiente:
/outer group filter
/inner group filter
MapGet filter
Cuando una aplicación tiene problemas de rendimiento, a menudo se sospecha que el enrutamiento es el problema. El motivo es que marcos como los controladores y Razor Pages notifican la cantidad de tiempo empleado en el marco de trabajo en sus mensajes de registro. Cuando hay una diferencia significativa entre el tiempo notificado por los controladores y el tiempo total de la solicitud:
El rendimiento del enrutamiento se prueba mediante miles de puntos de conexión. No es probable que una aplicación típica detecte un problema de rendimiento simplemente por ser demasiado grande. La causa raíz más común del rendimiento lento del enrutamiento suele ser middleware personalizado con un comportamiento incorrecto.
En el ejemplo de código siguiente se muestra una técnica básica para limitar el origen del retraso:
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 controlar el tiempo del enrutamiento:
Se trata de una forma básica de reducir el retraso cuando es significativo, por ejemplo, de más de 10ms
. Al restar Time 2
de Time 1
se notifica el tiempo invertido dentro del middleware UseRouting
.
En el código siguiente se usa un enfoque más compacto del código de control de tiempo 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.");
En la lista siguiente se proporciona información sobre las características de enrutamiento que son relativamente costosas en comparación con las plantillas de ruta básicas:
{x}-{y}-{z}
): De forma predeterminada, ASP.NET Core utiliza un algoritmo de enrutamiento que compara la memoria con el tiempo de CPU. Esto genera un buen resultado por el hecho de que el tiempo de coincidencia de enrutamiento solo depende de la longitud de la ruta de acceso con la que debe coincidir y no del número de rutas. Sin embargo, este enfoque puede ser potencialmente problemático en algunos casos, cuando la aplicación tiene un gran número de rutas (miles) y hay una gran cantidad de prefijos de variable en las rutas. Por ejemplo, si las rutas tienen parámetros en los primeros segmentos de la ruta, como {parameter}/some/literal
.
Es poco probable que una aplicación se ejecute en una situación en la que esto sea un problema, a menos que:
Microsoft.AspNetCore.Routing.Matching.DfaNode
.Hay varias técnicas y optimizaciones que se pueden aplicar a las rutas que mejorarán en gran medida este escenario:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
, etc., siempre que sea posible.
MapDynamicControllerRoute
y MapDynamicPageRoute
.Esta sección contiene instrucciones para los autores de bibliotecas que realizan la compilación sobre el enrutamiento. Estos detalles están diseñados para garantizar que los desarrolladores de aplicaciones tengan una buena experiencia en el uso de bibliotecas y marcos que amplían el enrutamiento.
Para crear un marco que use el enrutamiento para la coincidencia de direcciones URL, empiece por definir una experiencia de usuario que se base en UseEndpoints.
REALICE la compilación sobre IEndpointRouteBuilder. Esto permite a los usuarios crear el marco de trabajo con otras características de ASP.NET Core sin confusión. Todas las plantillas de ASP.NET Core incluyen el enrutamiento. Asuma que el enrutamiento está presente y es familiar para los usuarios.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DEVUELVA un tipo concreto sellado a partir de una llamada a MapMyFramework(...)
que implemente IEndpointConventionBuilder. La mayoría de los métodos Map...
del marco siguen este patrón. La interfaz IEndpointConventionBuilder
:
La declaración de un tipo propio permite agregar funcionalidad específica del marco propia al generador. Es correcto encapsular un generador declarado por el marco y reenviarle llamadas.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE LA POSIBILIDAD de escribir un objeto EndpointDataSource propio. EndpointDataSource
es la primitiva de bajo nivel para declarar y actualizar una colección de puntos de conexión. EndpointDataSource
es una API eficaz que usan los controladores y Razor Pages.
Las pruebas de enrutamiento tienen un ejemplo básico de un origen de datos que no es de actualización.
CONSIDERE la posibilidad de implementar GetGroupedEndpoints. Esto proporciona control completo sobre la ejecución de convenciones de grupo y los metadatos finales en los puntos de conexión agrupados. Por ejemplo, esto permite que las implementaciones personalizadas de EndpointDataSource
ejecuten filtros de punto de conexión agregados a grupos.
NO intente registrar un objeto EndpointDataSource
de forma predeterminada. Exija a los usuarios que registren el marco en UseEndpoints. La filosofía del enrutamiento es que nada se incluye de forma predeterminada y que UseEndpoints
es el lugar donde se registran los puntos de conexión.
CONSIDERE LA POSIBILIDAD de definir tipos de metadatos como una interfaz.
PERMITA el uso de los tipos de metadatos como atributo en clases y métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Los marcos como los controladores y Razor Pages admiten la aplicación de atributos de metadatos a tipos y métodos. Si declara tipos de metadatos:
La declaración de un tipo de metadatos como una interfaz agrega otro nivel de flexibilidad:
PERMITA que los metadatos se puedan invalidar, como se muestra en el ejemplo siguiente:
[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() { }
}
La mejor manera de seguir estas instrucciones es evitar la definición de metadatos de marcador:
La colección de metadatos está ordenada y admite la invalidación por prioridad. En el caso de los controladores, los metadatos del método de acción son más específicos.
PERMITA que el middleware sea útil con y sin el enrutamiento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como ejemplo de esta instrucción, considere la posibilidad de usar el middleware UseAuthorization
. El middleware de autorización permite pasar una directiva de reserva. La directiva de reserva, si se especifica, se aplica a:
Esto hace que el middleware de autorización sea útil fuera del contexto del enrutamiento. El middleware de autorización se puede usar para la programación de middleware tradicional.
Para ver la salida detallada del diagnóstico de cálculo de ruta, establezca Logging:LogLevel:Microsoft
en Debug
. En el entorno de desarrollo, establezca el nivel de registro en appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
El enrutamiento es responsable de hacer coincidir las solicitudes HTTP entrantes y de enviarlas a los puntos de conexión ejecutables de la aplicación. Los puntos de conexión son las unidades de código de control de solicitudes ejecutable de la aplicación. Se definen en la aplicación y se configuran al iniciarla. El proceso de búsqueda de coincidencias de puntos de conexión puede extraer valores de la dirección URL de la solicitud y proporcionarlos para el procesamiento de la solicitud. Con la información de los puntos de conexión de la aplicación, el enrutamiento también puede generar direcciones URL que se asignan a los puntos de conexión.
Las aplicaciones pueden configurar el enrutamiento mediante:
En este artículo se describen los detalles de bajo nivel del enrutamiento de ASP.NET Core. Para obtener información sobre la configuración del enrutamiento:
En el código siguiente se muestra un ejemplo básico de enrutamiento:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
En el ejemplo anterior se incluye un único punto de conexión que usa el método MapGet:
GET
a la dirección URL raíz /
: Hello World!
en la respuesta HTTP.GET
o la dirección URL raíz no es /
, no se detecta ninguna ruta y se devuelve HTTP 404.El enrutamiento usa un par de middleware, registrado por UseRouting y UseEndpoints:
UseRouting
agrega coincidencia de rutas a la canalización de middleware. Este middleware examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia en función de la solicitud.UseEndpoints
agrega la ejecución del punto de conexión a la canalización de middleware. Ejecuta el delegado asociado al punto de conexión seleccionado.Normalmente, las aplicaciones no necesitan llamar a UseRouting
ni a UseEndpoints
. WebApplicationBuilder configura una canalización de middleware que encapsula el middleware agregado en Program.cs
con UseRouting
y UseEndpoints
. Sin embargo, las aplicaciones pueden cambiar el orden en que se ejecutan UseRouting
y UseEndpoints
llamando a estos métodos explícitamente. Por ejemplo, el código siguiente realiza una llamada explícita a UseRouting
:
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
En el código anterior:
app.Use
registra un middleware personalizado que se ejecuta al principio de la canalización.UseRouting
configura el middleware de coincidencia de rutas para que se ejecute después del middleware personalizado.MapGet
se ejecuta al final de la canalización.Si el ejemplo anterior no incluyese una llamada a UseRouting
, el middleware personalizado se ejecutaría después del middleware de coincidencia de rutas.
El método MapGet
se usa para definir un punto de conexión. Un punto de conexión es algo que se puede:
Los puntos de conexión que la aplicación puede ejecutar y hacer coincidir se configuran en UseEndpoints
. Por ejemplo, MapGet, MapPost y métodos similares conectan delegados de solicitud al sistema de enrutamiento. Se pueden usar métodos adicionales para conectar características del marco ASP.NET Core al sistema de enrutamiento:
En el ejemplo siguiente se muestra el enrutamiento con una plantilla de ruta más sofisticada:
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
La cadena /hello/{name:alpha}
es una plantilla de ruta. Se usa una plantilla de ruta para configurar la coincidencia del punto de conexión. En este caso, la plantilla coincide con:
/hello/Docs
./hello/
, seguido de una secuencia de caracteres alfabéticos. :alpha
aplica una restricción de ruta que solo coincide con caracteres alfabéticos. Las restricciones de ruta se explican más adelante en este artículo.El segundo segmento de la ruta de dirección URL, {name:alpha}
:
name
.En el ejemplo siguiente se muestra el enrutamiento con comprobaciones de estado y autorización:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
En el ejemplo anterior se muestra cómo:
La llamada a MapHealthChecks agrega un punto de conexión de comprobación de estado. Al encadenar RequireAuthorization a esta llamada, se adjunta una directiva de autorización al punto de conexión.
La llamada a UseAuthentication y UseAuthorization agrega el middleware de autenticación y autorización. Estos middleware se colocan entre UseRouting y UseEndpoints
para que puedan:
UseRouting
.En el ejemplo anterior, hay dos puntos de conexión, pero solo el de comprobación de estado tiene una directiva de autorización adjunta. Si la solicitud coincide con el punto de conexión de comprobación de estado, /healthz
, se realiza una comprobación de autorización. Esto demuestra que los puntos de conexión pueden tener datos adicionales adjuntos. Estos datos adicionales se denominan metadatos de punto de conexión:
El sistema de enrutamiento se basa en la canalización de middleware mediante la adición del eficaz concepto de punto de conexión. Los puntos de conexión representan unidades de la funcionalidad de la aplicación que son diferentes entre sí en cuanto al enrutamiento, la autorización y cualquier número de sistemas de ASP.NET Core.
Un punto de conexión de ASP.NET Core es:
En el código siguiente se muestra cómo recuperar e inspeccionar el punto de conexión que coincide con la solicitud actual:
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.");
El punto de conexión, si se selecciona, se puede recuperar de HttpContext
. Se pueden inspeccionar sus propiedades. Los objetos de punto de conexión son inmutables y no se pueden modificar después de crearlos. El tipo más común de punto de conexión es RouteEndpoint. RouteEndpoint
incluye información que permite que el sistema de enrutamiento lo seleccione.
En el código anterior, app.Use configura un middleware insertado.
En el código siguiente se muestra que, en función de dónde se llame a app.Use
en la canalización, es posible que no haya un punto de conexión:
// 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);
});
En el ejemplo anterior se agregan instrucciones Console.WriteLine
que muestran si se ha seleccionado un punto de conexión o no. Para mayor claridad, en el ejemplo se asigna un nombre para mostrar al punto de conexión /
proporcionado.
El ejemplo anterior también incluye llamadas a UseRouting
y UseEndpoints
para controlar exactamente cuándo se ejecuta este middleware dentro de la canalización.
Al ejecutar este código con una dirección URL de /
se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Al ejecutar este código con otra dirección URL se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Este resultado muestra que:
UseRouting
.UseRouting
y UseEndpoints.UseEndpoints
es terminal cuando se encuentra una coincidencia. El middleware de terminal se define más adelante en este artículo.UseEndpoints
solo se ejecuta cuando no se encuentra ninguna coincidencia.El middleware UseRouting
usa el método SetEndpoint para asociar el punto de conexión al contexto actual. Se puede reemplazar el middleware UseRouting
con lógica personalizada y seguir aprovechando las ventajas del uso de puntos de conexión. Los puntos de conexión son una primitiva de bajo nivel como middleware y no están unidos a la implementación de enrutamiento. La mayoría de las aplicaciones no necesitan reemplazar UseRouting
por lógica personalizada.
El middleware UseEndpoints
está diseñado para usarse junto con el middleware UseRouting
. La lógica básica para ejecutar un punto de conexión no es complicada. Use GetEndpoint para recuperar el punto de conexión y, después, invoque su propiedad RequestDelegate.
En el código siguiente se muestra cómo el middleware puede influir en el enrutamiento o reaccionar ante este:
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 { }
En el ejemplo anterior se muestran dos conceptos importantes:
UseRouting
para modificar los datos sobre los que funciona el enrutamiento.
UseRouting
y UseEndpoints para procesar los resultados del enrutamiento antes de que se ejecute el punto de conexión.
UseRouting
y UseEndpoints
: UseAuthorization
y UseCors
.En el código anterior se muestra un ejemplo de middleware personalizado que admite directivas por punto de conexión. El middleware escribe un registro de auditoría de acceso a datos confidenciales en la consola. El middleware se puede configurar para auditar un punto de conexión con los metadatos de RequiresAuditAttribute
. En este ejemplo se muestra un patrón opcional en el que solo se auditan los puntos de conexión marcados como confidenciales. Esta lógica se puede definir en orden inverso, para auditar todo lo que no esté marcado como seguro, por ejemplo. El sistema de metadatos de punto de conexión es flexible. Esta lógica se puede diseñar de la manera que mejor se adapte al caso de uso.
El código del ejemplo anterior está diseñado para mostrar los conceptos básicos de los puntos de conexión. No está pensado para su uso en producción. Una versión más completa de un middleware de registro de auditoría:
El valor RequiresAuditAttribute
de metadatos de directiva de auditoría se define como Attribute
para facilitar su uso con marcos basados en clases como los controladores y SignalR. Cuando se usa de ruta a código:
Los procedimientos recomendados para los tipos de metadatos son definirlos como interfaces o atributos. Las interfaces y los atributos permiten la reutilización del código. El sistema de metadatos es flexible y no impone ninguna limitación.
En el ejemplo siguiente se muestra el middleware de terminal y el enrutamiento:
// 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.");
El estilo de middleware que se muestra con Approach 1:
es middleware de terminal. Se denomina middleware de terminal porque realiza una operación de búsqueda de coincidencias:
Path == "/"
para el middleware y Path == "/Routing"
para el enrutamiento.next
.Se denomina middleware de terminal porque finaliza la búsqueda, ejecuta alguna funcionalidad y, después, devuelve un valor.
En la lista siguiente se compara el middleware de terminal con el enrutamiento:
next
.UseAuthorization
y UseCors
.
UseAuthorization
o UseCors
se necesita interactuar de forma manual con el sistema de autorización.Un punto de conexión define:
El middleware de terminal puede ser una herramienta eficaz, pero puede requerir:
Considere la posibilidad de realizar la integración con el enrutamiento antes de escribir middleware de terminal.
El middleware de terminal existente que se integra con Map o MapWhen normalmente se puede convertir en un punto de conexión compatible con el enrutamiento. MapHealthChecks muestra el patrón para enrutadores:
Map
y proporcione la nueva canalización de middleware.Map
desde el método de extensión.En el código siguiente se muestra el uso de MapHealthChecks:
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
En el ejemplo anterior se muestra la importancia de devolver el objeto de generador. Al devolver el objeto de generador, el desarrollador de aplicaciones puede configurar directivas como la autorización para el punto de conexión. En este ejemplo, el middleware de comprobaciones de estado no tiene una integración directa con el sistema de autorización.
El sistema de metadatos se ha creado como respuesta a los problemas detectados por los autores de extensibilidad mediante el middleware de terminal. El problema de cada middleware es implementar su propia integración con el sistema de autorización.
Cuando se ejecuta un middleware de enrutamiento, se establece un objeto Endpoint
y se enrutan los valores a una característica de solicitud en el objeto HttpContext desde la solicitud actual:
HttpRequest.RouteValues
obtiene la colección de valores de ruta.El middleware que se ejecuta después del middleware de enrutamiento puede inspeccionar el punto de conexión y tomar medidas. Por ejemplo, un middleware de autorización puede consultar la colección de metadatos del punto de conexión de una directiva de autorización. Después de que se ejecuta todo el middleware en la canalización de procesamiento de solicitudes, se invoca al delegado del punto de conexión seleccionado.
El sistema de enrutamiento en el enrutamiento de punto de conexión es responsable de todas las decisiones relativas al envío. Como el middleware aplica directivas en función del punto de conexión seleccionado, es importante que:
Aviso
En cuanto a la compatibilidad con versiones anteriores, cuando se ejecuta un delegado del punto de conexión de controlador o Razor Pages, las propiedades de RouteContext.RouteData se establecen en los valores adecuados en función del procesamiento de solicitudes realizado hasta el momento.
El tipo RouteContext
se marcará como obsoleto en una versión futura:
RouteData.Values
a HttpRequest.RouteValues
.RouteData.DataTokens
para recuperar IDataTokensMetadata de los metadatos del punto de conexión.La coincidencia de direcciones URL funciona en un conjunto configurable de fases. En cada fase, la salida es un conjunto de coincidencias. El conjunto de coincidencias se puede reducir más en la fase siguiente. La implementación de enrutamiento no garantiza un orden de procesamiento para los puntos de conexión coincidentes. Todas las coincidencias posibles se procesan a la vez. Las fases de coincidencia de direcciones URL se producen en el orden siguiente. ASP.NET Core:
La lista de puntos de conexión se prioriza según:
Todos los puntos de conexión coincidentes se procesan en cada fase hasta que se alcanza EndpointSelector. EndpointSelector
es la fase final. Elige el punto de conexión de prioridad más alta entre las coincidencias como la mejor coincidencia. Si hay otras coincidencias con la misma prioridad que la mejor, se inicia una excepción de coincidencia ambigua.
La prioridad de ruta se calcula en función de una plantilla de ruta más específica a la que se le asigna una prioridad más alta. Por ejemplo, considere las plantillas /hello
y /{message}
:
/hello
./hello
es más específica y, por tanto, tiene mayor prioridad.Por lo general, la precedencia de rutas realiza un buen trabajo de elegir la mejor coincidencia para los tipos de esquemas de dirección URL que se usan en la práctica. Use Order solo cuando sea necesario para evitar una ambigüedad.
Debido a los tipos de extensibilidad que proporciona el enrutamiento, el sistema de enrutamiento no puede calcular las rutas ambiguas por adelantado. Considere un ejemplo como las plantillas de ruta /{message:alpha}
y /{message:int}
:
alpha
solo coincide con caracteres alfabéticos.int
solo coincide con números.Aviso
El orden de las operaciones dentro de UseEndpoints no influye en el comportamiento del enrutamiento, con una excepción. MapControllerRoute y MapAreaRoute asignan de forma automática un valor de orden a sus puntos de conexión en función del orden en el que se hayan invocado. Esto simula el comportamiento a largo plazo de los controladores sin que el sistema de enrutamiento proporcione las mismas garantías que las implementaciones de enrutamiento anteriores.
Enrutamiento de puntos de conexión en ASP.NET Core:
La prioridad de la plantilla de ruta es un sistema que asigna a cada plantilla de ruta un valor en función de su especificidad. Precedencia de la plantilla de ruta:
Por ejemplo, considere las plantillas /Products/List
y /Products/{id}
. Sería razonable suponer que /Products/List
es una mejor coincidencia que /Products/{id}
para la ruta de dirección URL /Products/List
. Funciona porque el segmento literal /List
se considera que tiene una mayor prioridad que el segmento de parámetro /{id}
.
Los detalles de cómo funciona la precedencia están vinculados a cómo se definen las plantillas de ruta:
Generación de direcciones URL:
El enrutamiento de punto de conexión incluye la API LinkGenerator. LinkGenerator
es un servicio singleton disponible desde la DI. La API LinkGenerator
se puede usar fuera del contexto de una solicitud en ejecución. Mvc.IUrlHelper y los escenarios que dependen de IUrlHelper, como los asistentes de etiquetas, los de HTML y los resultados de acción, usan de forma interna la API LinkGenerator
para proporcionar funciones de generación de vínculos.
El generador de vínculos está respaldado por el concepto de una dirección y esquemas de direcciones. Un esquema de direcciones es una manera de determinar los puntos de conexión que se deben tener en cuenta para la generación de vínculos. Por ejemplo, los escenarios de nombre y valores de ruta de controladores y Razor Pages con los que muchos usuarios están familiarizados se implementan como un esquema de direcciones.
El generador de vínculos puede vincular a controladores y Razor Pages a través de los métodos de extensión siguientes:
Las sobrecargas de estos métodos aceptan argumentos que incluyan HttpContext
. Estos métodos son funcionalmente equivalentes a Url.Action y Url.Page, pero ofrecen flexibilidad y opciones adicionales.
Los métodos GetPath*
son más similares a Url.Action
y Url.Page
, dado que generan un URI que contiene una ruta de acceso absoluta. Los métodos GetUri*
siempre generan un URI absoluto que contiene un esquema y un host. Los métodos que aceptan HttpContext
generan un URI en el contexto de la solicitud que se ejecuta. A menos que se reemplacen, se usan los valores de ruta de ambiente, la ruta de acceso base de la dirección URL, el esquema y el host de la solicitud en ejecución.
Se llama a LinkGenerator con una dirección. La generación de un URI se produce en dos pasos:
Los métodos proporcionados por LinkGenerator admiten funciones estándar de generación de vínculos para cualquier tipo de dirección. La forma más práctica de usar el generador de vínculos es a través de métodos de extensión que realicen operaciones para un tipo de dirección específica:
Método de extensión | Descripción |
---|---|
GetPathByAddress | Genera un URI con una ruta de acceso absoluta en función de los valores proporcionados. |
GetUriByAddress | Genera un URI absoluto en función de los valores proporcionados. |
Aviso
Preste atención a las consecuencias siguientes de llamar a los métodos LinkGenerator:
Use los métodos de extensión GetUri*
con precaución en una configuración de aplicación en la que no se valide el encabezado Host
de las solicitudes entrantes. Si no se valida el encabezado Host
de las solicitudes entrantes, la entrada de la solicitud que no sea de confianza se puede devolver al cliente en los URI de una página o vista. Se recomienda que todas las aplicaciones de producción configuren su servidor para validar el encabezado Host
en función de valores válidos conocidos.
Use LinkGenerator con precaución en el middleware junto con Map
o MapWhen
. Map*
cambia la ruta de acceso base de la solicitud que se ejecuta, lo que afecta a la salida de la generación de vínculos. Todas las API de LinkGenerator permiten especificar una ruta de acceso base. Especifique una ruta de acceso base vacía para deshacer el efecto de Map*
en la generación de vínculos.
En el ejemplo siguiente, un middleware usa la API LinkGenerator para crear un vínculo a un método de acción que enumera los productos de la tienda. El uso del generador de vínculos mediante su inserción en una clase y la llamada a GenerateLink
está disponible para cualquier clase de una aplicación:
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.");
}
}
Los tokens de {}
definen parámetros de ruta que se enlazan si se encuentran coincidencias con la ruta. Se puede definir más de un parámetro de ruta en un segmento de ruta, pero deben estar separados por un valor literal. Por ejemplo:
{controller=Home}{action=Index}
No es una ruta válida, ya que no hay ningún valor literal entre {controller}
y {action}
. Los parámetros de ruta deben tener un nombre y, opcionalmente, atributos adicionales especificados.
El texto literal diferente de los parámetros de ruta (por ejemplo, {id}
) y el separador de ruta /
deben coincidir con el texto de la dirección URL. La coincidencia de texto no distingue mayúsculas de minúsculas y se basa en la representación descodificada de la ruta de las direcciones URL. Para que el delimitador de parámetro de ruta literal {
o }
coincida, repita el carácter para aplicar escape al carácter. Por ejemplo, {{
o }}
.
Asterisco *
o asterisco doble **
:
blog/{**slug}
: blog/
y después tenga cualquier valor.blog/
se asigna al valor de ruta slug.Aviso
Un parámetro catch-all puede relacionar rutas de forma incorrecta debido a un error en el enrutamiento. Las aplicaciones afectadas por este error tienen las características siguientes:
{**slug}"
)Para ver casos de ejemplo relacionados con este error, consulte los errores 18677 y 16579 en GitHub.
Se incluye una corrección de participación para este error en el SDK de .NET Core 3.1.301 y versiones posteriores. En el código que hay a continuación se establece un cambio interno que corrige este error:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Los parámetros comodín también pueden coincidir con una cadena vacía.
El parámetro comodín inserta los caracteres de escape correspondientes cuando se usa la ruta para generar una dirección URL, incluidos los caracteres /
de separación de ruta de acceso. Por ejemplo, la ruta foo/{*path}
con valores de ruta { path = "my/path" }
genera foo/my%2Fpath
. Tenga en cuenta la barra diagonal de escape. Para los caracteres separadores de ruta de acceso de ida y vuelta, use el prefijo de parámetro de ruta **
. La ruta foo/{**path}
con { path = "my/path" }
genera foo/my/path
.
Los patrones de dirección URL que intentan capturar un nombre de archivo con una extensión de archivo opcional tienen consideraciones adicionales. Por ejemplo, considere la plantilla files/{filename}.{ext?}
. Cuando existen valores para filename
y ext
, los dos valores se rellenan. Si solo existe un valor para filename
en la dirección URL, la ruta coincide porque el carácter .
final es opcional. Las direcciones URL siguientes coinciden con esta ruta:
/files/myFile.txt
/files/myFile
Los parámetros de ruta pueden tener valores predeterminados designados mediante la especificación del valor predeterminado después del nombre de parámetro, separado por un signo igual (=
). Por ejemplo, {controller=Home}
define Home
como el valor predeterminado de controller
. El valor predeterminado se usa si no hay ningún valor en la dirección URL para el parámetro. Los parámetros de ruta se pueden convertir en opcionales si se anexa un signo de interrogación (?
) al final del nombre del parámetro. Por ejemplo: id?
. La diferencia entre los valores opcionales y los parámetros de ruta predeterminados es:
Los parámetros de ruta pueden tener restricciones que deben coincidir con el valor de ruta enlazado desde la dirección URL. Al agregar :
y un nombre de restricción después del nombre del parámetro de ruta, se especifica una restricción insertada en un parámetro de ruta. Si la restricción requiere argumentos, se incluyen entre paréntesis (...)
después del nombre de restricción. Se pueden especificar varias restricciones insertadas si se anexa otro carácter :
y un nombre de restricción.
El nombre de restricción y los argumentos se pasan al servicio IInlineConstraintResolver para crear una instancia de IRouteConstraint para su uso en el procesamiento de direcciones URL. Por ejemplo, la plantilla de ruta blog/{article:minlength(10)}
especifica una restricción minlength
con el argumento 10
. Para obtener más información sobre las restricciones de ruta y una lista de las restricciones proporcionadas por el marco, vea la sección Restricciones de ruta.
Los parámetros de ruta también pueden tener transformadores de parámetros. Los transformadores de parámetros transforman el valor de un parámetro al generar vínculos y hacer coincidir acciones y páginas con direcciones URL. Como sucede con las restricciones, los transformadores de parámetros se pueden agregar en línea a un parámetro de ruta mediante la incorporación un carácter :
y un nombre de transformador después del nombre del parámetro de ruta. Por ejemplo, la plantilla de ruta blog/{article:slugify}
especifica un transformador slugify
. Para obtener más información sobre los transformadores de parámetros, vea la sección Transformadores de parámetros.
En la tabla siguiente se muestran plantillas de ruta de ejemplo y su comportamiento:
Plantilla de ruta | URI coincidente de ejemplo | El URI de la solicitud... |
---|---|---|
hello |
/hello |
Solo coincide con la ruta de acceso única /hello . |
{Page=Home} |
/ |
Coincide y establece Page en Home . |
{Page=Home} |
/Contact |
Coincide y establece Page en Contact . |
{controller}/{action}/{id?} |
/Products/List |
Se asigna al controlador Products y la acción List . |
{controller}/{action}/{id?} |
/Products/Details/123 |
Se asigna al controlador Products y la acción Details con id establecido en 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Se asigna al controlador Home y al método Index . id se pasa por alto. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Se asigna al controlador Products y al método Index . id se pasa por alto. |
El uso de una plantilla suele ser el método de enrutamiento más sencillo. Las restricciones y los valores predeterminados también se pueden especificar fuera de la plantilla de ruta.
Los segmentos complejos se procesan mediante la búsqueda de coincidencias de delimitadores literales de derecha a izquierda de un modo no expansivo. Por ejemplo, [Route("/a{b}c{d}")]
es un segmento complejo.
Los segmentos complejos funcionan de una manera determinada que se debe entender para usarlos correctamente. En el ejemplo de esta sección se muestra por qué los segmentos complejos solo funcionan bien cuando el texto del delimitador no aparece dentro de los valores de los parámetros. En casos más complejos es necesario usar una expresión regular y extraer los valores de forma manual.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Este es un resumen de los pasos que realiza el enrutamiento con la plantilla /a{b}c{d}
y la ruta de dirección URL /abcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo:
c
. Por tanto, se busca en /abcd
desde la derecha y se encuentra /ab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /ab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.Este es un ejemplo de un caso negativo en el que se usa la misma plantilla /a{b}c{d}
y la ruta de dirección URL /aabcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo. Este caso no es una coincidencia, que se explica mediante el mismo algoritmo:
c
. Por tanto, se busca en /aabcd
desde la derecha y se encuentra /aab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /aab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /a|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.a
, pero el algoritmo se ha quedado sin plantilla de ruta para analizar, por lo que no es una coincidencia.Como el algoritmo de búsqueda de coincidencias es no expansivo:
Las expresiones regulares proporcionan un mayor control sobre el comportamiento de búsqueda de coincidencias.
La coincidencia expansiva, también conocida como coincidencia diferida, coincide con la cadena más grande posible. La búsqueda no expansiva coincide con la cadena más pequeña posible.
El enrutamiento con caracteres especiales puede dar lugar a resultados inesperados. Por ejemplo, considere un controlador con el siguiente método de acción:
[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;
}
Cuando string id
contiene los siguientes valores codificados, pueden producirse resultados inesperados:
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
Los parámetros de ruta no siempre están descodificados por URL. Este problema puede abordarse en el futuro. Para obtener más información, vea esta incidencia de GitHub.
Las restricciones de ruta se ejecutan cuando se ha producido una coincidencia con la dirección URL entrante y la ruta de dirección URL se convierte en tokens en valores de ruta. En general, las restricciones de ruta inspeccionan el valor de ruta asociado a través de la plantilla de ruta y deciden si el valor es aceptable o no. Algunas restricciones de ruta usan datos ajenos al valor de ruta para decidir si la solicitud se puede enrutar. Por ejemplo, HttpMethodRouteConstraint puede aceptar o rechazar una solicitud basada en su verbo HTTP. Las restricciones se usan en las solicitudes de enrutamiento y la generación de vínculos.
Aviso
No use las restricciones para la validación de entradas. Si se usan restricciones para la validación de entradas, las que no sean válidas generan una respuesta 404
No encontrado. Una entrada no válida debería generar 400
Solicitud incorrecta con un mensaje de error adecuado. Las restricciones de ruta se usan para eliminar la ambigüedad entre rutas similares, no para validar las entradas de una ruta determinada.
En la tabla siguiente se muestran restricciones de ruta de ejemplo y su comportamiento esperado:
restricción | Ejemplo | Coincidencias de ejemplo | Notas |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Coincide con cualquier entero |
bool |
{active:bool} |
true , FALSE |
Coincide con true o false . No distingue mayúsculas de minúsculas |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Coincide con un valor DateTime válido en la referencia cultural invariable. Vea la advertencia anterior. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Coincide con un valor decimal válido en la referencia cultural invariable. Vea la advertencia anterior. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Coincide con un valor double válido en la referencia cultural invariable. Vea la advertencia anterior. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Coincide con un valor float válido en la referencia cultural invariable. Vea la advertencia anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Coincide con un valor Guid válido |
long |
{ticks:long} |
123456789 , -123456789 |
Coincide con un valor long válido |
minlength(value) |
{username:minlength(4)} |
Rick |
La cadena debe tener al menos cuatro caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
La cadena no debe tener más de ocho caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
La cadena debe tener una longitud de exactamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
La cadena debe tener una longitud como mínimo de ocho caracteres y como máximo de 16 |
min(value) |
{age:min(18)} |
19 |
El valor entero debe ser como mínimo 18 |
max(value) |
{age:max(120)} |
91 |
El valor entero debe ser como máximo 120 |
range(min,max) |
{age:range(18,120)} |
91 |
El valor entero debe ser como mínimo 18 y máximo 120 |
alpha |
{name:alpha} |
Rick |
La cadena debe constar de uno o más caracteres alfabéticos, a -z y no distinguir mayúsculas de minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
La cadena debe coincidir con la expresión regular. Vea las sugerencias sobre cómo definir una expresión regular. |
required |
{name:required} |
Rick |
Se usa para exigir que un valor que no es de parámetro esté presente durante la generación de dirección URL |
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Es posible aplicar varias restricciones delimitadas por dos puntos a un único parámetro. Por ejemplo, la siguiente restricción permite limitar un parámetro a un valor entero de 1 o superior:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Aviso
Las restricciones de ruta que comprueban la dirección URL y que se convierten en un tipo CLR siempre usan la referencia cultural invariable. Por ejemplo, la conversión al tipo int
o DateTime
de CLR. Estas restricciones dan por supuesto que la dirección URL no es localizable. Las restricciones de ruta proporcionadas por el marco de trabajo no modifican los valores almacenados en los valores de ruta. Todos los valores de ruta analizados desde la dirección URL se almacenan como cadenas. Por ejemplo, la restricción float
intenta convertir el valor de ruta en un valor Float, pero el valor convertido se usa exclusivamente para comprobar que se puede convertir en Float.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Las expresiones regulares se pueden especificar como restricciones insertadas mediante la restricción de ruta regex(...)
. Los métodos de la familia MapControllerRoute también aceptan un literal de objeto de restricciones. Si se usa ese formato, los valores de cadena se interpretan como expresiones regulares.
En el código siguiente se usa una restricción de expresión regular insertada:
app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");
En el código siguiente se usa un literal de objeto para especificar una restricción de expresión regular:
app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });
El marco de trabajo de ASP.NET Core agrega RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
al constructor de expresiones regulares. Vea RegexOptions para obtener una descripción de estos miembros.
Las expresiones regulares usan delimitadores y tokens similares a los que usan el enrutamiento y el lenguaje C#. Es necesario usar secuencias de escape con los tokens de expresiones regulares. Para usar la expresión regular ^\d{3}-\d{2}-\d{4}$
en una restricción insertada, utilice una de las opciones siguientes:
\
proporcionados en la cadena como caracteres \\
en el archivo de código fuente de C# para aplicar secuencias de escape al carácter de escape de cadena \
.Para aplicar secuencias de escape a los caracteres delimitadores de parámetro de enrutamiento ({
, }
, [
y ]
), duplique los caracteres en la expresión, por ejemplo {{
, }}
, [[
y ]]
. En la tabla siguiente se muestra una expresión regular y su versión con la secuencia de escape:
Expresión regular | Expresión regular con secuencia de escape |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Las expresiones regulares que se usan en el enrutamiento suelen empezar con el carácter ^
y coincidir con la posición inicial de la cadena. Las expresiones suelen terminar con el carácter $
y coincidir con el final de la cadena. Los caracteres ^
y $
garantizan que la expresión regular coincide con el valor completo del parámetro de ruta. Sin los caracteres ^
y $
, la expresión regular coincide con cualquier subcadena de la cadena, lo que normalmente no es deseable. En la tabla siguiente se proporcionan ejemplos y se explica por qué coinciden o no:
Expresión | String | Coincidir con | Comentario |
---|---|---|---|
[a-z]{2} |
hello | Sí | Coincidencias de subcadenas |
[a-z]{2} |
123abc456 | Sí | Coincidencias de subcadenas |
[a-z]{2} |
mz | Sí | Coincide con la expresión |
[a-z]{2} |
MZ | Sí | No distingue mayúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Vea ^ y $ más arriba |
^[a-z]{2}$ |
123abc456 | No | Vea ^ y $ más arriba |
Para obtener más información sobre la sintaxis de expresiones regulares, vea Expresiones regulares de .NET Framework.
Para restringir un parámetro a un conjunto conocido de valores posibles, use una expresión regular. Por ejemplo, {action:regex(^(list|get|create)$)}
solo hace coincidir el valor de ruta action
con list
, get
o create
. Si se pasa al diccionario de restricciones, la cadena ^(list|get|create)$
es equivalente. Las restricciones que se pasan al diccionario de restricciones que no coinciden con una de las conocidas también se tratan como expresiones regulares. Las restricciones que se pasan en una plantilla y que no coinciden con una de las conocidas no se tratan como expresiones regulares.
Se pueden crear restricciones de ruta personalizadas mediante la implementación de la interfaz IRouteConstraint. La interfaz IRouteConstraint
contiene Match, que devuelve true
si se cumple la restricción, y false
en caso contrario.
Las restricciones de ruta personalizadas rara vez son necesarias. Antes de implementar una restricción de ruta personalizada, considere alternativas, como el enlace de modelos.
En la carpeta Constraints de ASP.NET Core se proporcionan buenos ejemplos de creación de restricciones. Por ejemplo, GuidRouteConstraint.
Para usar una restricción IRouteConstraint
personalizada, el tipo de restricción de ruta se debe registrar con el parámetro ConstraintMap de la aplicación en el contenedor de servicios. ConstraintMap
es un diccionario que asigna claves de restricciones de ruta a implementaciones de IRouteConstraint
que validen esas restricciones. El parámetro ConstraintMap
de una aplicación puede actualizarse en Program.cs
como parte de una llamada a AddRouting o configurando RouteOptions directamente con builder.Services.Configure<RouteOptions>
. Por ejemplo:
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
La restricción anterior se aplica en el código siguiente:
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
La implementación de NoZeroesRouteConstraint
impide que 0
se use en un parámetro de ruta:
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);
}
}
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
El código anterior:
0
en el segmento {id}
de la ruta.El código siguiente es un enfoque mejor para impedir que se procese un valor id
que contenga 0
:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
El código anterior tiene las ventajas siguientes con respecto al enfoque de NoZeroesRouteConstraint
:
0
.Transformadores de parámetros:
Por ejemplo, un transformador de parámetros personalizado slugify
en el patrón de ruta blog\{article:slugify}
con Url.Action(new { article = "MyTestArticle" })
genera blog\my-test-article
.
Considere la siguiente implementación de IOutboundParameterTransformer
:
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 un transformador de parámetros en un patrón de ruta, configúrelo con ConstraintMap en Program.cs
:
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
El marco ASP.NET Core usa los transformadores de parámetros para transformar el URI en el que se resuelve un punto de conexión. Por ejemplo, los transformadores de parámetros transforman los valores de ruta que se usan para hacer coincidir objetos area
, controller
, action
y page
:
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Con la plantilla de ruta anterior, la acción SubscriptionManagementController.GetAll
coincide con el URI /subscription-management/get-all
. Un transformador de parámetros no cambia los valores de ruta usados para generar un vínculo. Por ejemplo, Url.Action("GetAll", "SubscriptionManagement")
genera /subscription-management/get-all
.
ASP.NET Core proporciona convenciones de API para usar transformadores de parámetros con rutas generadas:
Esta sección contiene una referencia para el algoritmo implementado por la generación de direcciones URL. En la práctica, los ejemplos más complejos de generación de direcciones URL usan controladores o Razor Pages. Vea Enrutamiento en controladores para obtener información adicional.
El proceso de generación de direcciones URL comienza con una llamada a LinkGenerator.GetPathByAddress o un método similar. Al método se le proporciona una dirección, un conjunto de valores de ruta y, opcionalmente, información sobre la solicitud actual de HttpContext
.
El primer paso consiste en usar la dirección para resolver un conjunto de puntos de conexión candidatos con una instancia de IEndpointAddressScheme<TAddress> que coincide con el tipo de la dirección.
Una vez que el esquema de direcciones encuentra el conjunto de candidatos, los puntos de conexión se ordenan y procesan de forma iterativa hasta que se realiza correctamente una operación de generación de direcciones URL. La generación de direcciones URL no comprueba si hay ambigüedades; el primer resultado devuelto es el resultado final.
El primer paso para solucionar problemas de generación de direcciones URL consiste en establecer el nivel de registro de Microsoft.AspNetCore.Routing
en TRACE
. LinkGenerator
registra muchos detalles sobre su procesamiento que pueden ser útiles para solucionar problemas.
Vea Referencia de generación de direcciones URL para obtener más información sobre la generación de direcciones URL.
Las direcciones son el concepto de la generación de direcciones URL que se usa para enlazar una llamada al generador de vínculos a un conjunto de puntos de conexión candidatos.
Las direcciones son un concepto extensible que incluyen dos implementaciones de forma predeterminada:
string
) como dirección: IUrlHelper
, aplicaciones auxiliares de etiquetas, aplicaciones auxiliares HTML, resultados de acciones, etc.El papel del esquema de direcciones consiste en establecer la asociación entre la dirección y los puntos de conexión coincidentes mediante criterios arbitrarios:
A partir de la solicitud actual, el enrutamiento accede a los valores de ruta del objeto HttpContext.Request.RouteValues
de la solicitud actual. Los valores asociados a la solicitud actual se conocen como valores de ambiente. Para mayor claridad, en la documentación se hace referencia a los valores de ruta que se pasan a los métodos como valores explícitos.
En el ejemplo siguiente se muestran valores de ambiente y valores explícitos. Proporciona valores de ambiente de la solicitud actual y 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);
}
// ...
El código anterior:
/Widget/Index/17
El código siguiente solo proporciona valores explícitos y valores sin ambiente:
var subscribePath = _linkGenerator.GetPathByAction(
"Subscribe", "Home", new { id = 17 })!;
El método anterior devuelve /Home/Subscribe/17
.
El código siguiente en WidgetController
devuelve /Widget/Subscribe/17
:
var subscribePath = _linkGenerator.GetPathByAction(
HttpContext, "Subscribe", null, new { id = 17 });
En el código siguiente se proporciona el controlador a partir de los valores de ambiente de la solicitud actual y los valores explícitos:
public class GadgetController : ControllerBase
{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}
En el código anterior:
/Gadget/Edit/17
.action
especificado y los valores route
.En el código siguiente se proporcionan valores de ambiente de la solicitud actual y valores explícitos:
public class IndexModel : PageModel
{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });
// ...
}
}
En el código anterior se establece url
en /Edit/17
cuando la página de Razor de edición contiene la siguiente directiva de página:
@page "{id:int}"
Si la página de edición no contiene la plantilla de ruta "{id:int}"
, url
es /Edit?id=17
.
El comportamiento de IUrlHelper de MVC agrega una capa de complejidad además de las reglas descritas aquí:
IUrlHelper
siempre proporciona los valores de ruta de la solicitud actual como valores de ambiente.action
y controller
actuales como valores explícitos a menos que el desarrollador los invalide.page
actual como un valor explícito a menos que se invalide. IUrlHelper.Page
siempre invalida el valor de ruta handler
actual con null
como un valor explícito a menos que se invalide.A los usuarios a menudo les sorprenden los detalles del comportamiento de los valores de ambiente, ya que MVC no parece seguir sus propias reglas. Por motivos históricos y de compatibilidad, algunos valores de ruta como action
, controller
, page
y handler
tienen su propio comportamiento de caso especial.
La funcionalidad equivalente proporcionada por LinkGenerator.GetPathByAction
y LinkGenerator.GetPathByPage
duplica estas anomalías de IUrlHelper
por motivos de compatibilidad.
Una vez que se encuentra el conjunto de puntos de conexión candidatos, el algoritmo de generación de direcciones URL:
El primer paso de este proceso se denomina invalidación del valor de ruta. La invalidación del valor de ruta es el proceso por el que el enrutamiento decide qué valores de ruta de los valores de ambiente se deben usar y cuáles se deben omitir. Cada valor de ambiente se tiene en cuenta y se combina con los valores explícitos, o bien se pasa por alto.
La mejor manera de pensar en el rol de los valores de ambiente es que intentan ahorrar trabajo a los desarrolladores de aplicaciones, en algunos casos comunes. Tradicionalmente, los escenarios en los que los valores de ambiente son útiles están relacionados con MVC:
Las llamadas a LinkGenerator
o IUrlHelper
que devuelven null
se suelen deber a que no se comprende la invalidación del valor de ruta. Para solucionar problemas de invalidación del valor de ruta, especifique de forma explícita más valores de ruta para ver si eso resuelve el problema.
La invalidación del valor de ruta se basa en la suposición de que el esquema de direcciones URL de la aplicación es jerárquico, con una jerarquía formada de izquierda a derecha. Considere la posibilidad de usar la plantilla de ruta de controlador básica {controller}/{action}/{id?}
para hacerse una idea intuitiva de cómo funciona esto en la práctica. Un cambio en un valor invalida todos los valores de ruta que aparecen a la derecha. Esto refleja la suposición sobre la jerarquía. Si la aplicación tiene un valor de ambiente para id
y la operación especifica otro valor para controller
:
id
no se reutilizará porque {controller}
está a la izquierda de {id?}
.Algunos ejemplos demuestran este principio:
id
, se omite el valor de ambiente de id
. Se pueden usar los valores de ambiente para controller
y action
.action
, se omite cualquier valor de ambiente para action
. Se pueden usar los valores de ambiente para controller
. Si el valor explícito para action
es diferente del valor de ambiente para action
, no se usará el valor de id
. Si el valor explícito para action
es diferente del valor de ambiente para action
, se puede usar el valor de id
.controller
, se omite cualquier valor de ambiente para controller
. Si el valor explícito para controller
es diferente del valor de ambiente para controller
, no se usarán los valores de action
y id
. Si el valor explícito para controller
es igual que el valor de ambiente para controller
, se pueden usar los valores de action
y id
.Este proceso sea complica todavía más por la existencia de rutas de atributo y rutas convencionales dedicadas. Las rutas convencionales de controlador, como {controller}/{action}/{id?}
, especifican una jerarquía mediante parámetros de ruta. Para las rutas convencionales dedicadas y las rutas de atributo a controladores y Razor Pages:
En estos casos, la generación de direcciones URL define el concepto de valores necesarios. Los puntos de conexión creados por controladores y Razor Pages tienen valores necesarios especificados que permiten que la invalidación del valor de ruta funcione.
El algoritmo de invalidación del valor de ruta en detalle:
En este punto, la operación de generación de direcciones URL está lista para evaluar las restricciones de ruta. El conjunto de valores aceptados se combina con los valores predeterminados de parámetro, que se proporcionan a las restricciones. Si todas las restricciones son correctas, la operación continúa.
A continuación, se pueden usar los valores aceptados para expandir la plantilla de ruta. La plantilla de ruta se procesa:
Los valores que se proporcionan de forma explícita que no coinciden con un segmento de la ruta se agregan a la cadena de consulta. En la tabla siguiente se muestra el resultado cuando se usa la plantilla de ruta {controller}/{action}/{id?}
.
Valores de ambiente | Valores explícitos | Resultado |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
En el código siguiente se muestra un ejemplo de un esquema de generación de direcciones URL que no es compatible con el enrutamiento:
app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
En el código anterior, se usa el parámetro de ruta culture
para la localización. El objetivo es que el parámetro culture
siempre se acepte como valor de ambiente. Pero el parámetro culture
no se acepta como valor de ambiente debido al funcionamiento de los valores necesarios:
"default"
, el parámetro de ruta culture
está a la izquierda de controller
, por lo que los cambios en controller
no invalidarán culture
."blog"
, se considera que el parámetro de ruta culture
está a la derecha de controller
, que aparece en los valores necesarios.La clase LinkParser agrega compatibilidad con el análisis de una ruta de dirección URL en un conjunto de valores de ruta. El método ParsePathByEndpointName toma un nombre de punto de conexión y una ruta de dirección URL y devuelve un conjunto de valores de ruta extraídos de esta.
En el controlador de ejemplo siguiente, la acción GetProduct
usa una plantilla de ruta de api/Products/{id}
y tiene un valor de Name de GetProduct
:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
En la misma clase de controlador, la acción AddRelatedProduct
espera una ruta de dirección URL, pathToRelatedProduct
, que se puede proporcionar como parámetro de cadena 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"];
// ...
En el ejemplo anterior, la acción AddRelatedProduct
extrae el valor de ruta id
de la ruta de dirección URL. Por ejemplo, con una ruta de dirección URL de /api/Products/1
, el valor de relatedProductId
se establece en 1
. Este enfoque permite a los clientes de la API usar rutas de dirección URL al hacer referencia a recursos, sin necesidad de conocer cómo se estructura dicha dirección URL.
Los vínculos siguientes proporcionan información sobre cómo configurar los metadatos del punto de conexión:
[MinimumAgeAuthorize]
personalizadoRequireHost aplica una restricción a la ruta que requiere el host especificado. El parámetro RequireHost
o [Host] puede ser:
www.domain.com
, compara www.domain.com
con cualquier puerto.*.domain.com
, coincide con www.domain.com
, subdomain.domain.com
o www.subdomain.domain.com
en cualquier puerto.*:5000
, coincide con el puerto 5000 con cualquier host.www.domain.com:5000
o *.domain.com:5000
, coincide con el host y el puerto.Se pueden especificar varios parámetros mediante RequireHost
o [Host]
. La restricción coincide con los hosts válidos para cualquiera de los parámetros. Por ejemplo, [Host("domain.com", "*.domain.com")]
coincide con domain.com
, www.domain.com
y subdomain.domain.com
.
En el código siguiente se usa RequireHost
para requerir el host especificado en la ruta:
app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");
app.MapHealthChecks("/healthz").RequireHost("*:8080");
En el código siguiente se usa el atributo [Host]
en el controlador para requerir cualquiera de los hosts especificados:
[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();
[Host("example.com")]
public IActionResult Example() =>
View();
}
Cuando el atributo [Host]
se aplica al método de acción y al controlador:
Cuando una aplicación tiene problemas de rendimiento, a menudo se sospecha que el enrutamiento es el problema. El motivo es que marcos como los controladores y Razor Pages notifican la cantidad de tiempo empleado en el marco de trabajo en sus mensajes de registro. Cuando hay una diferencia significativa entre el tiempo notificado por los controladores y el tiempo total de la solicitud:
El rendimiento del enrutamiento se prueba mediante miles de puntos de conexión. No es probable que una aplicación típica detecte un problema de rendimiento simplemente por ser demasiado grande. La causa raíz más común del rendimiento lento del enrutamiento suele ser middleware personalizado con un comportamiento incorrecto.
En el ejemplo de código siguiente se muestra una técnica básica para limitar el origen del retraso:
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 controlar el tiempo del enrutamiento:
Se trata de una forma básica de reducir el retraso cuando es significativo, por ejemplo, de más de 10ms
. Al restar Time 2
de Time 1
se notifica el tiempo invertido dentro del middleware UseRouting
.
En el código siguiente se usa un enfoque más compacto del código de control de tiempo 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.");
En la lista siguiente se proporciona información sobre las características de enrutamiento que son relativamente costosas en comparación con las plantillas de ruta básicas:
{x}-{y}-{z}
): De forma predeterminada, ASP.NET Core utiliza un algoritmo de enrutamiento que compara la memoria con el tiempo de CPU. Esto genera un buen resultado por el hecho de que el tiempo de coincidencia de enrutamiento solo depende de la longitud de la ruta de acceso con la que debe coincidir y no del número de rutas. Sin embargo, este enfoque puede ser potencialmente problemático en algunos casos, cuando la aplicación tiene un gran número de rutas (miles) y hay una gran cantidad de prefijos de variable en las rutas. Por ejemplo, si las rutas tienen parámetros en los primeros segmentos de la ruta, como {parameter}/some/literal
.
Es poco probable que una aplicación se ejecute en una situación en la que esto sea un problema, a menos que:
Microsoft.AspNetCore.Routing.Matching.DfaNode
.Hay varias técnicas y optimizaciones que se pueden aplicar a las rutas que mejorarán en gran medida este escenario:
{parameter:int}
, {parameter:guid}
, {parameter:regex(\\d+)}
, etc., siempre que sea posible.
MapDynamicControllerRoute
y MapDynamicPageRoute
.Esta sección contiene instrucciones para los autores de bibliotecas que realizan la compilación sobre el enrutamiento. Estos detalles están diseñados para garantizar que los desarrolladores de aplicaciones tengan una buena experiencia en el uso de bibliotecas y marcos que amplían el enrutamiento.
Para crear un marco que use el enrutamiento para la coincidencia de direcciones URL, empiece por definir una experiencia de usuario que se base en UseEndpoints.
REALICE la compilación sobre IEndpointRouteBuilder. Esto permite a los usuarios crear el marco de trabajo con otras características de ASP.NET Core sin confusión. Todas las plantillas de ASP.NET Core incluyen el enrutamiento. Asuma que el enrutamiento está presente y es familiar para los usuarios.
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
DEVUELVA un tipo concreto sellado a partir de una llamada a MapMyFramework(...)
que implemente IEndpointConventionBuilder. La mayoría de los métodos Map...
del marco siguen este patrón. La interfaz IEndpointConventionBuilder
:
La declaración de un tipo propio permite agregar funcionalidad específica del marco propia al generador. Es correcto encapsular un generador declarado por el marco y reenviarle llamadas.
// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
app.MapHealthChecks("/healthz");
CONSIDERE LA POSIBILIDAD de escribir un objeto EndpointDataSource propio. EndpointDataSource
es la primitiva de bajo nivel para declarar y actualizar una colección de puntos de conexión. EndpointDataSource
es una API eficaz que usan los controladores y Razor Pages.
Las pruebas de enrutamiento tienen un ejemplo básico de un origen de datos que no es de actualización.
NO intente registrar un objeto EndpointDataSource
de forma predeterminada. Exija a los usuarios que registren el marco en UseEndpoints. La filosofía del enrutamiento es que nada se incluye de forma predeterminada y que UseEndpoints
es el lugar donde se registran los puntos de conexión.
CONSIDERE LA POSIBILIDAD de definir tipos de metadatos como una interfaz.
PERMITA el uso de los tipos de metadatos como atributo en clases y métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Los marcos como los controladores y Razor Pages admiten la aplicación de atributos de metadatos a tipos y métodos. Si declara tipos de metadatos:
La declaración de un tipo de metadatos como una interfaz agrega otro nivel de flexibilidad:
PERMITA que los metadatos se puedan invalidar, como se muestra en el ejemplo siguiente:
[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() { }
}
La mejor manera de seguir estas instrucciones es evitar la definición de metadatos de marcador:
La colección de metadatos está ordenada y admite la invalidación por prioridad. En el caso de los controladores, los metadatos del método de acción son más específicos.
PERMITA que el middleware sea útil con y sin el enrutamiento:
app.UseAuthorization(new AuthorizationPolicy() { ... });
// Your framework
app.MapMyFramework(...).RequireAuthorization();
Como ejemplo de esta instrucción, considere la posibilidad de usar el middleware UseAuthorization
. El middleware de autorización permite pasar una directiva de reserva. La directiva de reserva, si se especifica, se aplica a:
Esto hace que el middleware de autorización sea útil fuera del contexto del enrutamiento. El middleware de autorización se puede usar para la programación de middleware tradicional.
Para ver la salida detallada del diagnóstico de cálculo de ruta, establezca Logging:LogLevel:Microsoft
en Debug
. En el entorno de desarrollo, establezca el nivel de registro en appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
El enrutamiento es responsable de hacer coincidir las solicitudes HTTP entrantes y de enviarlas a los puntos de conexión ejecutables de la aplicación. Los puntos de conexión son las unidades de código de control de solicitudes ejecutable de la aplicación. Se definen en la aplicación y se configuran al iniciarla. El proceso de búsqueda de coincidencias de puntos de conexión puede extraer valores de la dirección URL de la solicitud y proporcionarlos para el procesamiento de la solicitud. Con la información de los puntos de conexión de la aplicación, el enrutamiento también puede generar direcciones URL que se asignan a los puntos de conexión.
Las aplicaciones pueden configurar el enrutamiento mediante:
En este documento se describen los detalles de bajo nivel del enrutamiento de ASP.NET Core. Para obtener información sobre la configuración del enrutamiento:
El sistema de enrutamiento de puntos de conexión descrito en este documento se aplica a ASP.NET Core 3.0 y versiones posteriores. Para obtener información sobre el sistema de enrutamiento anterior basado en IRouter, seleccione la versión ASP.NET Core 2.1 mediante uno de los enfoques siguientes:
Vea o descargue el código de ejemplo (cómo descargarlo)
Los ejemplos de descarga para este documento están habilitados por una clase Startup
específica. Para ejecutar un ejemplo concreto, modifique Program.cs
para llamar a la clase Startup
deseada.
Todas las plantillas de ASP.NET Core incluyen el enrutamiento en el código generado. El enrutamiento se registra en la canalización de middleware en Startup.Configure
.
En el código siguiente se muestra un ejemplo básico de enrutamiento:
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!");
});
});
}
El enrutamiento usa un par de middleware, registrado por UseRouting y UseEndpoints:
UseRouting
agrega coincidencia de rutas a la canalización de middleware. Este middleware examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia en función de la solicitud.UseEndpoints
agrega la ejecución del punto de conexión a la canalización de middleware. Ejecuta el delegado asociado al punto de conexión seleccionado.En el ejemplo anterior se incluye un único punto de conexión de ruta a código a través del método MapGet:
GET
a la dirección URL raíz /
: Hello World!
en la respuesta HTTP. De forma predeterminada, la dirección URL raíz /
es https://localhost:5001/
.GET
o la dirección URL raíz no es /
, no se detecta ninguna ruta y se devuelve HTTP 404.El método MapGet
se usa para definir un punto de conexión. Un punto de conexión es algo que se puede:
Los puntos de conexión que la aplicación puede ejecutar y hacer coincidir se configuran en UseEndpoints
. Por ejemplo, MapGet, MapPost y métodos similares conectan delegados de solicitud al sistema de enrutamiento. Se pueden usar métodos adicionales para conectar características del marco ASP.NET Core al sistema de enrutamiento:
En el ejemplo siguiente se muestra el enrutamiento con una plantilla de ruta más sofisticada:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello/{name:alpha}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
});
La cadena /hello/{name:alpha}
es una plantilla de ruta. Se usa para configurar cómo se hace coincidir el punto de conexión. En este caso, la plantilla coincide con:
/hello/Ryan
./hello/
, seguido de una secuencia de caracteres alfabéticos. :alpha
aplica una restricción de ruta que solo coincide con caracteres alfabéticos. Las restricciones de ruta se explican más adelante en este documento.El segundo segmento de la ruta de dirección URL, {name:alpha}
:
name
.El sistema de enrutamiento de puntos de conexión descrito en este documento es nuevo desde ASP.NET Core 3.0. Sin embargo, todas las versiones de ASP.NET Core admiten el mismo conjunto de características de plantilla de ruta y restricciones de ruta.
En el ejemplo siguiente se muestra el enrutamiento con comprobaciones de estado y autorización:
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!");
});
});
}
Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.
En el ejemplo anterior se muestra cómo:
La llamada a MapHealthChecks agrega un punto de conexión de comprobación de estado. Al encadenar RequireAuthorization a esta llamada, se adjunta una directiva de autorización al punto de conexión.
La llamada a UseAuthentication y UseAuthorization agrega el middleware de autenticación y autorización. Estos middleware se colocan entre UseRouting y UseEndpoints
para que puedan:
UseRouting
.En el ejemplo anterior, hay dos puntos de conexión, pero solo el de comprobación de estado tiene una directiva de autorización adjunta. Si la solicitud coincide con el punto de conexión de comprobación de estado, /healthz
, se realiza una comprobación de autorización. Esto demuestra que los puntos de conexión pueden tener datos adicionales adjuntos. Estos datos adicionales se denominan metadatos de punto de conexión:
El sistema de enrutamiento se basa en la canalización de middleware mediante la adición del eficaz concepto de punto de conexión. Los puntos de conexión representan unidades de la funcionalidad de la aplicación que son diferentes entre sí en cuanto al enrutamiento, la autorización y cualquier número de sistemas de ASP.NET Core.
Un punto de conexión de ASP.NET Core es:
En el código siguiente se muestra cómo recuperar e inspeccionar el punto de conexión que coincide con la solicitud actual:
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!");
});
});
}
El punto de conexión, si se selecciona, se puede recuperar de HttpContext
. Se pueden inspeccionar sus propiedades. Los objetos de punto de conexión son inmutables y no se pueden modificar después de crearlos. El tipo más común de punto de conexión es RouteEndpoint. RouteEndpoint
incluye información que permite que el sistema de enrutamiento lo seleccione.
En el código anterior, app.Use configura un middleware insertado.
En el código siguiente se muestra que, en función de dónde se llame a app.Use
en la canalización, es posible que no haya un punto de conexión:
// 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);
});
En este ejemplo se agregan instrucciones Console.WriteLine
que muestran si se ha seleccionado un punto de conexión o no. Para mayor claridad, en el ejemplo se asigna un nombre para mostrar al punto de conexión /
proporcionado.
Al ejecutar este código con una dirección URL de /
se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
Al ejecutar este código con otra dirección URL se muestra lo siguiente:
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
Este resultado muestra que:
UseRouting
.UseRouting
y UseEndpoints.UseEndpoints
es terminal cuando se encuentra una coincidencia. El middleware de terminal se define más adelante en este documento.UseEndpoints
solo se ejecuta cuando no se encuentra ninguna coincidencia.El middleware UseRouting
usa el método SetEndpoint para asociar el punto de conexión al contexto actual. Se puede reemplazar el middleware UseRouting
con lógica personalizada y seguir aprovechando las ventajas del uso de puntos de conexión. Los puntos de conexión son una primitiva de bajo nivel como middleware y no están unidos a la implementación de enrutamiento. La mayoría de las aplicaciones no necesitan reemplazar UseRouting
por lógica personalizada.
El middleware UseEndpoints
está diseñado para usarse junto con el middleware UseRouting
. La lógica básica para ejecutar un punto de conexión no es complicada. Use GetEndpoint para recuperar el punto de conexión y, después, invoque su propiedad RequestDelegate.
En el código siguiente se muestra cómo el middleware puede influir en el enrutamiento o reaccionar ante este:
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; }
}
En el ejemplo anterior se muestran dos conceptos importantes:
UseRouting
para modificar los datos sobre los que funciona el enrutamiento.
UseRouting
y UseEndpoints para procesar los resultados del enrutamiento antes de que se ejecute el punto de conexión.
UseRouting
y UseEndpoints
: UseAuthorization
y UseCors
.En el código anterior se muestra un ejemplo de middleware personalizado que admite directivas por punto de conexión. El middleware escribe un registro de auditoría de acceso a datos confidenciales en la consola. El middleware se puede configurar para auditar un punto de conexión con los metadatos de AuditPolicyAttribute
. En este ejemplo se muestra un patrón opcional en el que solo se auditan los puntos de conexión marcados como confidenciales. Esta lógica se puede definir en orden inverso, para auditar todo lo que no esté marcado como seguro, por ejemplo. El sistema de metadatos de punto de conexión es flexible. Esta lógica se puede diseñar de la manera que mejor se adapte al caso de uso.
El código del ejemplo anterior está diseñado para mostrar los conceptos básicos de los puntos de conexión. No está pensado para su uso en producción. Una versión más completa de un middleware de registro de auditoría:
El valor AuditPolicyAttribute
de metadatos de directiva de auditoría se define como Attribute
para facilitar su uso con marcos basados en clases como los controladores y SignalR. Cuando se usa de ruta a código:
Los procedimientos recomendados para los tipos de metadatos son definirlos como interfaces o atributos. Las interfaces y los atributos permiten la reutilización del código. El sistema de metadatos es flexible y no impone ninguna limitación.
En el ejemplo de código siguiente se compara el uso de middleware con el del enrutamiento:
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!");
});
});
}
El estilo de middleware que se muestra con Approach 1:
es middleware de terminal. Se denomina middleware de terminal porque realiza una operación de búsqueda de coincidencias:
Path == "/"
para el middleware y Path == "/Movie"
para el enrutamiento.next
.Se denomina middleware de terminal porque finaliza la búsqueda, ejecuta alguna funcionalidad y, después, devuelve un valor.
Comparación entre un middleware de terminal y el enrutamiento:
next
.UseAuthorization
y UseCors
.
UseAuthorization
o UseCors
se necesita interactuar de forma manual con el sistema de autorización.Un punto de conexión define:
El middleware de terminal puede ser una herramienta eficaz, pero puede requerir:
Considere la posibilidad de realizar la integración con el enrutamiento antes de escribir middleware de terminal.
El middleware de terminal existente que se integra con Map o MapWhen normalmente se puede convertir en un punto de conexión compatible con el enrutamiento. MapHealthChecks muestra el patrón para enrutadores:
Map
y proporcione la nueva canalización de middleware.Map
desde el método de extensión.En el código siguiente se muestra el 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!");
});
});
}
En el ejemplo anterior se muestra la importancia de devolver el objeto de generador. Al devolver el objeto de generador, el desarrollador de aplicaciones puede configurar directivas como la autorización para el punto de conexión. En este ejemplo, el middleware de comprobaciones de estado no tiene una integración directa con el sistema de autorización.
El sistema de metadatos se ha creado como respuesta a los problemas detectados por los autores de extensibilidad mediante el middleware de terminal. El problema de cada middleware es implementar su propia integración con el sistema de autorización.
Cuando se ejecuta un middleware de enrutamiento, se establece un objeto Endpoint
y se enrutan los valores a una característica de solicitud en el objeto HttpContext desde la solicitud actual:
HttpRequest.RouteValues
obtiene la colección de valores de ruta.El middleware que se ejecuta después del middleware de enrutamiento puede inspeccionar el punto de conexión y tomar medidas. Por ejemplo, un middleware de autorización puede consultar la colección de metadatos del punto de conexión de una directiva de autorización. Después de que se ejecuta todo el middleware en la canalización de procesamiento de solicitudes, se invoca al delegado del punto de conexión seleccionado.
El sistema de enrutamiento en el enrutamiento de punto de conexión es responsable de todas las decisiones relativas al envío. Como el middleware aplica directivas en función del punto de conexión seleccionado, es importante que:
Aviso
En cuanto a la compatibilidad con versiones anteriores, cuando se ejecuta un delegado del punto de conexión de controlador o Razor Pages, las propiedades de RouteContext.RouteData se establecen en los valores adecuados en función del procesamiento de solicitudes realizado hasta el momento.
El tipo RouteContext
se marcará como obsoleto en una versión futura:
RouteData.Values
a HttpRequest.RouteValues
.RouteData.DataTokens
para recuperar IDataTokensMetadata de los metadatos del punto de conexión.La coincidencia de direcciones URL funciona en un conjunto configurable de fases. En cada fase, la salida es un conjunto de coincidencias. El conjunto de coincidencias se puede reducir más en la fase siguiente. La implementación de enrutamiento no garantiza un orden de procesamiento para los puntos de conexión coincidentes. Todas las coincidencias posibles se procesan a la vez. Las fases de coincidencia de direcciones URL se producen en el orden siguiente. ASP.NET Core:
La lista de puntos de conexión se prioriza según:
Todos los puntos de conexión coincidentes se procesan en cada fase hasta que se alcanza EndpointSelector. EndpointSelector
es la fase final. Elige el punto de conexión de prioridad más alta entre las coincidencias como la mejor coincidencia. Si hay otras coincidencias con la misma prioridad que la mejor, se inicia una excepción de coincidencia ambigua.
La prioridad de ruta se calcula en función de una plantilla de ruta más específica a la que se le asigna una prioridad más alta. Por ejemplo, considere las plantillas /hello
y /{message}
:
/hello
./hello
es más específica y, por tanto, tiene mayor prioridad.Por lo general, la precedencia de rutas realiza un buen trabajo de elegir la mejor coincidencia para los tipos de esquemas de dirección URL que se usan en la práctica. Use Order solo cuando sea necesario para evitar una ambigüedad.
Debido a los tipos de extensibilidad que proporciona el enrutamiento, el sistema de enrutamiento no puede calcular las rutas ambiguas por adelantado. Considere un ejemplo como las plantillas de ruta /{message:alpha}
y /{message:int}
:
alpha
solo coincide con caracteres alfabéticos.int
solo coincide con números.Aviso
El orden de las operaciones dentro de UseEndpoints no influye en el comportamiento del enrutamiento, con una excepción. MapControllerRoute y MapAreaRoute asignan de forma automática un valor de orden a sus puntos de conexión en función del orden en el que se hayan invocado. Esto simula el comportamiento a largo plazo de los controladores sin que el sistema de enrutamiento proporcione las mismas garantías que las implementaciones de enrutamiento anteriores.
En la implementación heredada de enrutamiento, es posible implementar la extensibilidad de enrutamiento que tiene una dependencia en el orden de procesamiento de las rutas. Enrutamiento de puntos de conexión en ASP.NET Core 3.0 y versiones posteriores:
La prioridad de la plantilla de ruta es un sistema que asigna a cada plantilla de ruta un valor en función de su especificidad. Precedencia de la plantilla de ruta:
Por ejemplo, considere las plantillas /Products/List
y /Products/{id}
. Sería razonable suponer que /Products/List
es una mejor coincidencia que /Products/{id}
para la ruta de dirección URL /Products/List
. Funciona porque el segmento literal /List
se considera que tiene una mayor prioridad que el segmento de parámetro /{id}
.
Los detalles de cómo funciona la precedencia están vinculados a cómo se definen las plantillas de ruta:
Vea el código fuente en GitHub para obtener una referencia de los valores exactos.
Generación de direcciones URL:
El enrutamiento de punto de conexión incluye la API LinkGenerator. LinkGenerator
es un servicio singleton disponible desde la DI. La API LinkGenerator
se puede usar fuera del contexto de una solicitud en ejecución. Mvc.IUrlHelper y los escenarios que dependen de IUrlHelper, como los asistentes de etiquetas, los de HTML y los resultados de acción, usan de forma interna la API LinkGenerator
para proporcionar funciones de generación de vínculos.
El generador de vínculos está respaldado por el concepto de una dirección y esquemas de direcciones. Un esquema de direcciones es una manera de determinar los puntos de conexión que se deben tener en cuenta para la generación de vínculos. Por ejemplo, los escenarios de nombre y valores de ruta de controladores y Razor Pages con los que muchos usuarios están familiarizados se implementan como un esquema de direcciones.
El generador de vínculos puede vincular a controladores y Razor Pages a través de los métodos de extensión siguientes:
Las sobrecargas de estos métodos aceptan argumentos que incluyan HttpContext
. Estos métodos son funcionalmente equivalentes a Url.Action y Url.Page, pero ofrecen flexibilidad y opciones adicionales.
Los métodos GetPath*
son más similares a Url.Action
y Url.Page
, dado que generan un URI que contiene una ruta de acceso absoluta. Los métodos GetUri*
siempre generan un URI absoluto que contiene un esquema y un host. Los métodos que aceptan HttpContext
generan un URI en el contexto de la solicitud que se ejecuta. A menos que se reemplacen, se usan los valores de ruta de ambiente, la ruta de acceso base de la dirección URL, el esquema y el host de la solicitud en ejecución.
Se llama a LinkGenerator con una dirección. La generación de un URI se produce en dos pasos:
Los métodos proporcionados por LinkGenerator admiten funciones estándar de generación de vínculos para cualquier tipo de dirección. La forma más práctica de usar el generador de vínculos es a través de métodos de extensión que realicen operaciones para un tipo de dirección específica:
Método de extensión | Descripción |
---|---|
GetPathByAddress | Genera un URI con una ruta de acceso absoluta en función de los valores proporcionados. |
GetUriByAddress | Genera un URI absoluto en función de los valores proporcionados. |
Aviso
Preste atención a las consecuencias siguientes de llamar a los métodos LinkGenerator:
Use los métodos de extensión GetUri*
con precaución en una configuración de aplicación en la que no se valide el encabezado Host
de las solicitudes entrantes. Si no se valida el encabezado Host
de las solicitudes entrantes, la entrada de la solicitud que no sea de confianza se puede devolver al cliente en los URI de una página o vista. Se recomienda que todas las aplicaciones de producción configuren su servidor para validar el encabezado Host
en función de valores válidos conocidos.
Use LinkGenerator con precaución en el middleware junto con Map
o MapWhen
. Map*
cambia la ruta de acceso base de la solicitud que se ejecuta, lo que afecta a la salida de la generación de vínculos. Todas las API de LinkGenerator permiten especificar una ruta de acceso base. Especifique una ruta de acceso base vacía para deshacer el efecto de Map*
en la generación de vínculos.
En el ejemplo siguiente, un middleware usa la API LinkGenerator para crear un vínculo a un método de acción que enumera los productos de la tienda. El uso del generador de vínculos mediante su inserción en una clase y la llamada a GenerateLink
está disponible para cualquier clase de una aplicación:
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.");
}
}
Los tokens de {}
definen parámetros de ruta que se enlazan si se encuentran coincidencias con la ruta. Se puede definir más de un parámetro de ruta en un segmento de ruta, pero deben estar separados por un valor literal. Por ejemplo, {controller=Home}{action=Index}
no es una ruta válida, ya que no hay ningún valor literal entre {controller}
y {action}
. Los parámetros de ruta deben tener un nombre y, opcionalmente, atributos adicionales especificados.
El texto literal diferente de los parámetros de ruta (por ejemplo, {id}
) y el separador de ruta /
deben coincidir con el texto de la dirección URL. La coincidencia de texto no distingue mayúsculas de minúsculas y se basa en la representación descodificada de la ruta de las direcciones URL. Para que el delimitador de parámetro de ruta literal {
o }
coincida, repita el carácter para aplicar escape al carácter. Por ejemplo, {{
o }}
.
Asterisco *
o asterisco doble **
:
blog/{**slug}
: /blog
y después tenga cualquier valor./blog
se asigna al valor de ruta slug.Aviso
Un parámetro catch-all puede relacionar rutas de forma incorrecta debido a un error en el enrutamiento. Las aplicaciones afectadas por este error tienen las características siguientes:
{**slug}"
)Para ver casos de ejemplo relacionados con este error, consulte los errores 18677 y 16579 en GitHub.
Se incluye una corrección de participación para este error en el SDK de .NET Core 3.1.301 y versiones posteriores. En el código que hay a continuación se establece un cambio interno que corrige este error:
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
Los parámetros comodín también pueden coincidir con una cadena vacía.
El parámetro comodín inserta los caracteres de escape correspondientes cuando se usa la ruta para generar una dirección URL, incluidos los caracteres /
de separación de ruta de acceso. Por ejemplo, la ruta foo/{*path}
con valores de ruta { path = "my/path" }
genera foo/my%2Fpath
. Tenga en cuenta la barra diagonal de escape. Para los caracteres separadores de ruta de acceso de ida y vuelta, use el prefijo de parámetro de ruta **
. La ruta foo/{**path}
con { path = "my/path" }
genera foo/my/path
.
Los patrones de dirección URL que intentan capturar un nombre de archivo con una extensión de archivo opcional tienen consideraciones adicionales. Por ejemplo, considere la plantilla files/{filename}.{ext?}
. Cuando existen valores para filename
y ext
, los dos valores se rellenan. Si solo existe un valor para filename
en la dirección URL, la ruta coincide porque el carácter .
final es opcional. Las direcciones URL siguientes coinciden con esta ruta:
/files/myFile.txt
/files/myFile
Los parámetros de ruta pueden tener valores predeterminados designados mediante la especificación del valor predeterminado después del nombre de parámetro, separado por un signo igual (=
). Por ejemplo, {controller=Home}
define Home
como el valor predeterminado de controller
. El valor predeterminado se usa si no hay ningún valor en la dirección URL para el parámetro. Los parámetros de ruta se pueden convertir en opcionales si se anexa un signo de interrogación (?
) al final del nombre del parámetro. Por ejemplo: id?
. La diferencia entre los valores opcionales y los parámetros de ruta predeterminados es:
Los parámetros de ruta pueden tener restricciones que deben coincidir con el valor de ruta enlazado desde la dirección URL. Al agregar :
y un nombre de restricción después del nombre del parámetro de ruta, se especifica una restricción insertada en un parámetro de ruta. Si la restricción requiere argumentos, se incluyen entre paréntesis (...)
después del nombre de restricción. Se pueden especificar varias restricciones insertadas si se anexa otro carácter :
y un nombre de restricción.
El nombre de restricción y los argumentos se pasan al servicio IInlineConstraintResolver para crear una instancia de IRouteConstraint para su uso en el procesamiento de direcciones URL. Por ejemplo, la plantilla de ruta blog/{article:minlength(10)}
especifica una restricción minlength
con el argumento 10
. Para obtener más información sobre las restricciones de ruta y una lista de las restricciones proporcionadas por el marco de trabajo, vea la sección Referencia de restricciones de ruta.
Los parámetros de ruta también pueden tener transformadores de parámetros. Los transformadores de parámetros transforman el valor de un parámetro al generar vínculos y hacer coincidir acciones y páginas con direcciones URL. Como sucede con las restricciones, los transformadores de parámetros se pueden agregar en línea a un parámetro de ruta mediante la incorporación un carácter :
y un nombre de transformador después del nombre del parámetro de ruta. Por ejemplo, la plantilla de ruta blog/{article:slugify}
especifica un transformador slugify
. Para obtener más información sobre los transformadores de parámetros, vea la sección Referencia de transformadores de parámetros.
En la tabla siguiente se muestran plantillas de ruta de ejemplo y su comportamiento:
Plantilla de ruta | URI coincidente de ejemplo | El URI de la solicitud... |
---|---|---|
hello |
/hello |
Solo coincide con la ruta de acceso única /hello . |
{Page=Home} |
/ |
Coincide y establece Page en Home . |
{Page=Home} |
/Contact |
Coincide y establece Page en Contact . |
{controller}/{action}/{id?} |
/Products/List |
Se asigna al controlador Products y la acción List . |
{controller}/{action}/{id?} |
/Products/Details/123 |
Se asigna al controlador Products y la acción Details con id establecido en 123. |
{controller=Home}/{action=Index}/{id?} |
/ |
Se asigna al controlador Home y al método Index . id se pasa por alto. |
{controller=Home}/{action=Index}/{id?} |
/Products |
Se asigna al controlador Products y al método Index . id se pasa por alto. |
El uso de una plantilla suele ser el método de enrutamiento más sencillo. Las restricciones y los valores predeterminados también se pueden especificar fuera de la plantilla de ruta.
Los segmentos complejos se procesan mediante la búsqueda de coincidencias de delimitadores literales de derecha a izquierda de un modo no expansivo. Por ejemplo, [Route("/a{b}c{d}")]
es un segmento complejo.
Los segmentos complejos funcionan de una manera determinada que se debe entender para usarlos correctamente. En el ejemplo de esta sección se muestra por qué los segmentos complejos solo funcionan bien cuando el texto del delimitador no aparece dentro de los valores de los parámetros. En casos más complejos es necesario usar una expresión regular y extraer los valores de forma manual.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Este es un resumen de los pasos que realiza el enrutamiento con la plantilla /a{b}c{d}
y la ruta de dirección URL /abcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo:
c
. Por tanto, se busca en /abcd
desde la derecha y se encuentra /ab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /ab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.Este es un ejemplo de un caso negativo en el que se usa la misma plantilla /a{b}c{d}
y la ruta de dirección URL /aabcd
. |
se usa para ayudar a visualizar cómo funciona el algoritmo. Este caso no es una coincidencia, que se explica mediante el mismo algoritmo:
c
. Por tanto, se busca en /aabcd
desde la derecha y se encuentra /aab|c|d
.d
) coincide ahora con el parámetro de ruta {d}
.a
. Por tanto, se busca en /aab|c|d
a partir de donde se ha parado antes, después a
y se encuentra /a|a|b|c|d
.b
) coincide ahora con el parámetro de ruta {b}
.a
, pero el algoritmo se ha quedado sin plantilla de ruta para analizar, por lo que no es una coincidencia.Como el algoritmo de búsqueda de coincidencias es no expansivo:
Las expresiones regulares proporcionan un mayor control sobre el comportamiento de búsqueda de coincidencias.
La coincidencia expansiva, también conocida como coincidencia diferida, coincide con la cadena más grande posible. La búsqueda no expansiva coincide con la cadena más pequeña posible.
Las restricciones de ruta se ejecutan cuando se ha producido una coincidencia con la dirección URL entrante y la ruta de dirección URL se convierte en tokens en valores de ruta. En general, las restricciones de ruta inspeccionan el valor de ruta asociado a través de la plantilla de ruta y deciden si el valor es aceptable o no. Algunas restricciones de ruta usan datos ajenos al valor de ruta para decidir si la solicitud se puede enrutar. Por ejemplo, HttpMethodRouteConstraint puede aceptar o rechazar una solicitud basada en su verbo HTTP. Las restricciones se usan en las solicitudes de enrutamiento y la generación de vínculos.
Aviso
No use las restricciones para la validación de entradas. Si se usan restricciones para la validación de entradas, las que no sean válidas generan una respuesta 404
No encontrado. Una entrada no válida debería generar 400
Solicitud incorrecta con un mensaje de error adecuado. Las restricciones de ruta se usan para eliminar la ambigüedad entre rutas similares, no para validar las entradas de una ruta determinada.
En la tabla siguiente se muestran restricciones de ruta de ejemplo y su comportamiento esperado:
restricción | Ejemplo | Coincidencias de ejemplo | Notas |
---|---|---|---|
int |
{id:int} |
123456789 , -123456789 |
Coincide con cualquier entero |
bool |
{active:bool} |
true , FALSE |
Coincide con true o false . No distingue mayúsculas de minúsculas |
datetime |
{dob:datetime} |
2016-12-31 , 2016-12-31 7:32pm |
Coincide con un valor DateTime válido en la referencia cultural invariable. Vea la advertencia anterior. |
decimal |
{price:decimal} |
49.99 , -1,000.01 |
Coincide con un valor decimal válido en la referencia cultural invariable. Vea la advertencia anterior. |
double |
{weight:double} |
1.234 , -1,001.01e8 |
Coincide con un valor double válido en la referencia cultural invariable. Vea la advertencia anterior. |
float |
{weight:float} |
1.234 , -1,001.01e8 |
Coincide con un valor float válido en la referencia cultural invariable. Vea la advertencia anterior. |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
Coincide con un valor Guid válido |
long |
{ticks:long} |
123456789 , -123456789 |
Coincide con un valor long válido |
minlength(value) |
{username:minlength(4)} |
Rick |
La cadena debe tener al menos cuatro caracteres |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
La cadena no debe tener más de ocho caracteres |
length(length) |
{filename:length(12)} |
somefile.txt |
La cadena debe tener una longitud de exactamente 12 caracteres |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
La cadena debe tener una longitud como mínimo de ocho caracteres y como máximo de 16 |
min(value) |
{age:min(18)} |
19 |
El valor entero debe ser como mínimo 18 |
max(value) |
{age:max(120)} |
91 |
El valor entero debe ser como máximo 120 |
range(min,max) |
{age:range(18,120)} |
91 |
El valor entero debe ser como mínimo 18 y máximo 120 |
alpha |
{name:alpha} |
Rick |
La cadena debe constar de uno o más caracteres alfabéticos, a -z y no distinguir mayúsculas de minúsculas. |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
La cadena debe coincidir con la expresión regular. Vea las sugerencias sobre cómo definir una expresión regular. |
required |
{name:required} |
Rick |
Se usa para exigir que un valor que no es de parámetro esté presente durante la generación de dirección URL |
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Es posible aplicar varias restricciones delimitadas por dos puntos a un único parámetro. Por ejemplo, la siguiente restricción permite limitar un parámetro a un valor entero de 1 o superior:
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
Aviso
Las restricciones de ruta que comprueban la dirección URL y que se convierten en un tipo CLR siempre usan la referencia cultural invariable. Por ejemplo, la conversión al tipo int
o DateTime
de CLR. Estas restricciones dan por supuesto que la dirección URL no es localizable. Las restricciones de ruta proporcionadas por el marco de trabajo no modifican los valores almacenados en los valores de ruta. Todos los valores de ruta analizados desde la dirección URL se almacenan como cadenas. Por ejemplo, la restricción float
intenta convertir el valor de ruta en un valor Float, pero el valor convertido se usa exclusivamente para comprobar que se puede convertir en Float.
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Las expresiones regulares se pueden especificar como restricciones insertadas mediante la restricción de ruta regex(...)
. Los métodos de la familia MapControllerRoute también aceptan un literal de objeto de restricciones. Si se usa ese formato, los valores de cadena se interpretan como expresiones regulares.
En el código siguiente se usa una restricción de expresión regular insertada:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
context =>
{
return context.Response.WriteAsync("inline-constraint match");
});
});
En el código siguiente se usa un literal de objeto para especificar una restricción de expresión regular:
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", });
});
El marco de trabajo de ASP.NET Core agrega RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
al constructor de expresiones regulares. Vea RegexOptions para obtener una descripción de estos miembros.
Las expresiones regulares usan delimitadores y tokens similares a los que usan el enrutamiento y el lenguaje C#. Es necesario usar secuencias de escape con los tokens de expresiones regulares. Para usar la expresión regular ^\d{3}-\d{2}-\d{4}$
en una restricción insertada, utilice una de las opciones siguientes:
\
proporcionados en la cadena como caracteres \\
en el archivo de código fuente de C# para aplicar secuencias de escape al carácter de escape de cadena \
.Para aplicar secuencias de escape a los caracteres delimitadores de parámetro de enrutamiento ({
, }
, [
y ]
), duplique los caracteres en la expresión, por ejemplo {{
, }}
, [[
y ]]
. En la tabla siguiente se muestra una expresión regular y su versión con la secuencia de escape:
Expresión regular | Expresión regular con secuencia de escape |
---|---|
^\d{3}-\d{2}-\d{4}$ |
^\\d{{3}}-\\d{{2}}-\\d{{4}}$ |
^[a-z]{2}$ |
^[[a-z]]{{2}}$ |
Las expresiones regulares que se usan en el enrutamiento suelen empezar con el carácter ^
y coincidir con la posición inicial de la cadena. Las expresiones suelen terminar con el carácter $
y coincidir con el final de la cadena. Los caracteres ^
y $
garantizan que la expresión regular coincide con el valor completo del parámetro de ruta. Sin los caracteres ^
y $
, la expresión regular coincide con cualquier subcadena de la cadena, lo que normalmente no es deseable. En la tabla siguiente se proporcionan ejemplos y se explica por qué coinciden o no:
Expresión | String | Coincidir con | Comentario |
---|---|---|---|
[a-z]{2} |
hello | Sí | Coincidencias de subcadenas |
[a-z]{2} |
123abc456 | Sí | Coincidencias de subcadenas |
[a-z]{2} |
mz | Sí | Coincide con la expresión |
[a-z]{2} |
MZ | Sí | No distingue mayúsculas de minúsculas |
^[a-z]{2}$ |
hello | No | Vea ^ y $ más arriba |
^[a-z]{2}$ |
123abc456 | No | Vea ^ y $ más arriba |
Para obtener más información sobre la sintaxis de expresiones regulares, vea Expresiones regulares de .NET Framework.
Para restringir un parámetro a un conjunto conocido de valores posibles, use una expresión regular. Por ejemplo, {action:regex(^(list|get|create)$)}
solo hace coincidir el valor de ruta action
con list
, get
o create
. Si se pasa al diccionario de restricciones, la cadena ^(list|get|create)$
es equivalente. Las restricciones que se pasan al diccionario de restricciones que no coinciden con una de las conocidas también se tratan como expresiones regulares. Las restricciones que se pasan en una plantilla y que no coinciden con una de las conocidas no se tratan como expresiones regulares.
Se pueden crear restricciones de ruta personalizadas mediante la implementación de la interfaz IRouteConstraint. La interfaz IRouteConstraint
contiene Match, que devuelve true
si se cumple la restricción, y false
en caso contrario.
Las restricciones de ruta personalizadas rara vez son necesarias. Antes de implementar una restricción de ruta personalizada, considere alternativas, como el enlace de modelos.
En la carpeta Constraints de ASP.NET Core se proporcionan buenos ejemplos de creación de restricciones. Por ejemplo, GuidRouteConstraint.
Para usar una restricción IRouteConstraint
personalizada, el tipo de restricción de ruta se debe registrar con el parámetro ConstraintMap de la aplicación en el contenedor de servicios. ConstraintMap
es un diccionario que asigna claves de restricciones de ruta a implementaciones de IRouteConstraint
que validen esas restricciones. El parámetro ConstraintMap
de una aplicación puede actualizarse en Startup.ConfigureServices
como parte de una llamada a services.AddRouting o configurando RouteOptions directamente con services.Configure<RouteOptions>
. Por ejemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});
}
La restricción anterior se aplica en el código siguiente:
[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);
}
}
El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.
La implementación de MyCustomConstraint
impide que 0
se aplique a un parámetro de ruta:
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;
}
}
Aviso
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar entradas a RegularExpressions
y provocar un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
El código anterior:
0
en el segmento {id}
de la ruta.El código siguiente es un enfoque mejor para impedir que se procese un valor id
que contenga 0
:
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return ControllerContext.MyDisplayRouteInfo(id);
}
El código anterior tiene las ventajas siguientes con respecto al enfoque de MyCustomConstraint
:
0
.Transformadores de parámetros:
Por ejemplo, un transformador de parámetros personalizado slugify
en el patrón de ruta blog\{article:slugify}
con Url.Action(new { article = "MyTestArticle" })
genera blog\my-test-article
.
Considere la siguiente implementación de IOutboundParameterTransformer
:
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 un transformador de parámetros en un patrón de ruta, configúrelo con ConstraintMap en Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
}
El marco ASP.NET Core usa los transformadores de parámetros para transformar el URI en el que se resuelve un punto de conexión. Por ejemplo, los transformadores de parámetros transforman los valores de ruta que se usan para hacer coincidir objetos area
, controller
, action
y page
.
routes.MapControllerRoute(
name: "default",
template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
Con la plantilla de ruta anterior, la acción SubscriptionManagementController.GetAll
coincide con el URI /subscription-management/get-all
. Un transformador de parámetros no cambia los valores de ruta usados para generar un vínculo. Por ejemplo, Url.Action("GetAll", "SubscriptionManagement")
genera /subscription-management/get-all
.
ASP.NET Core proporciona convenciones de API para usar transformadores de parámetros con rutas generadas:
Esta sección contiene una referencia para el algoritmo implementado por la generación de direcciones URL. En la práctica, los ejemplos más complejos de generación de direcciones URL usan controladores o Razor Pages. Vea Enrutamiento en controladores para obtener información adicional.
El proceso de generación de direcciones URL comienza con una llamada a LinkGenerator.GetPathByAddress o un método similar. Al método se le proporciona una dirección, un conjunto de valores de ruta y, opcionalmente, información sobre la solicitud actual de HttpContext
.
El primer paso consiste en usar la dirección para resolver un conjunto de puntos de conexión candidatos con una instancia de IEndpointAddressScheme<TAddress>
que coincide con el tipo de la dirección.
Una vez que el esquema de direcciones encuentra el conjunto de candidatos, los puntos de conexión se ordenan y procesan de forma iterativa hasta que se realiza correctamente una operación de generación de direcciones URL. La generación de direcciones URL no comprueba si hay ambigüedades; el primer resultado devuelto es el resultado final.
El primer paso para solucionar problemas de generación de direcciones URL consiste en establecer el nivel de registro de Microsoft.AspNetCore.Routing
en TRACE
. LinkGenerator
registra muchos detalles sobre su procesamiento que pueden ser útiles para solucionar problemas.
Vea Referencia de generación de direcciones URL para obtener más información sobre la generación de direcciones URL.
Las direcciones son el concepto de la generación de direcciones URL que se usa para enlazar una llamada al generador de vínculos a un conjunto de puntos de conexión candidatos.
Las direcciones son un concepto extensible que incluyen dos implementaciones de forma predeterminada:
string
) como dirección: IUrlHelper
, aplicaciones auxiliares de etiquetas, aplicaciones auxiliares HTML, resultados de acciones, etc.El papel del esquema de direcciones consiste en establecer la asociación entre la dirección y los puntos de conexión coincidentes mediante criterios arbitrarios:
A partir de la solicitud actual, el enrutamiento accede a los valores de ruta del objeto HttpContext.Request.RouteValues
de la solicitud actual. Los valores asociados a la solicitud actual se conocen como valores de ambiente. Para mayor claridad, en la documentación se hace referencia a los valores de ruta que se pasan a los métodos como valores explícitos.
En el ejemplo siguiente se muestran valores de ambiente y valores explícitos. Proporciona valores de ambiente de la solicitud actual y 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);
}
El código anterior:
/Widget/Index/17
En el código siguiente no se proporcionan valores de ambiente y valores explícitos, { controller = "Home", action = "Subscribe", id = 17, }
:
public IActionResult Index2()
{
var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
new { id = 17, });
return Content(url);
}
El método anterior devuelve /Home/Subscribe/17
.
El código siguiente en WidgetController
devuelve /Widget/Subscribe/17
:
var url = _linkGenerator.GetPathByAction("Subscribe", null,
new { id = 17, });
En el código siguiente se proporciona el controlador a partir de los valores de ambiente de la solicitud actual y los 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);
}
En el código anterior:
/Gadget/Edit/17
.action
especificado y los valores route
.En el código siguiente se proporcionan valores de ambiente de la solicitud actual y 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;
}
}
En el código anterior se establece url
en /Edit/17
cuando la página de Razor de edición contiene la siguiente directiva de página:
@page "{id:int}"
Si la página de edición no contiene la plantilla de ruta "{id:int}"
, url
es /Edit?id=17
.
El comportamiento de IUrlHelper de MVC agrega una capa de complejidad además de las reglas descritas aquí:
IUrlHelper
siempre proporciona los valores de ruta de la solicitud actual como valores de ambiente.action
y controller
actuales como valores explícitos a menos que el desarrollador los invalide.page
actual como un valor explícito a menos que se invalide. IUrlHelper.Page
siempre invalida el valor de ruta handler
actual con null
como un valor explícito a menos que se invalide.A los usuarios a menudo les sorprenden los detalles del comportamiento de los valores de ambiente, ya que MVC no parece seguir sus propias reglas. Por motivos históricos y de compatibilidad, algunos valores de ruta como action
, controller
, page
y handler
tienen su propio comportamiento de caso especial.
La funcionalidad equivalente proporcionada por LinkGenerator.GetPathByAction
y LinkGenerator.GetPathByPage
duplica estas anomalías de IUrlHelper
por motivos de compatibilidad.
Una vez que se encuentra el conjunto de puntos de conexión candidatos, el algoritmo de generación de direcciones URL:
El primer paso de este proceso se denomina invalidación del valor de ruta. La invalidación del valor de ruta es el proceso por el que el enrutamiento decide qué valores de ruta de los valores de ambiente se deben usar y cuáles se deben omitir. Cada valor de ambiente se tiene en cuenta y se combina con los valores explícitos, o bien se pasa por alto.
La mejor manera de pensar en el rol de los valores de ambiente es que intentan ahorrar trabajo a los desarrolladores de aplicaciones, en algunos casos comunes. Tradicionalmente, los escenarios en los que los valores de ambiente son útiles están relacionados con MVC:
Las llamadas a LinkGenerator
o IUrlHelper
que devuelven null
se suelen deber a que no se comprende la invalidación del valor de ruta. Para solucionar problemas de invalidación del valor de ruta, especifique de forma explícita más valores de ruta para ver si eso resuelve el problema.
La invalidación del valor de ruta se basa en la suposición de que el esquema de direcciones URL de la aplicación es jerárquico, con una jerarquía formada de izquierda a derecha. Considere la posibilidad de usar la plantilla de ruta de controlador básica {controller}/{action}/{id?}
para hacerse una idea intuitiva de cómo funciona esto en la práctica. Un cambio en un valor invalida todos los valores de ruta que aparecen a la derecha. Esto refleja la suposición sobre la jerarquía. Si la aplicación tiene un valor de ambiente para id
y la operación especifica otro valor para controller
:
id
no se reutilizará porque {controller}
está a la izquierda de {id?}
.Algunos ejemplos demuestran este principio:
id
, se omite el valor de ambiente de id
. Se pueden usar los valores de ambiente para controller
y action
.action
, se omite cualquier valor de ambiente para action
. Se pueden usar los valores de ambiente para controller
. Si el valor explícito para action
es diferente del valor de ambiente para action
, no se usará el valor de id
. Si el valor explícito para action
es diferente del valor de ambiente para action
, se puede usar el valor de id
.controller
, se omite cualquier valor de ambiente para controller
. Si el valor explícito para controller
es diferente del valor de ambiente para controller
, no se usarán los valores de action
y id
. Si el valor explícito para controller
es igual que el valor de ambiente para controller
, se pueden usar los valores de action
y id
.Este proceso sea complica todavía más por la existencia de rutas de atributo y rutas convencionales dedicadas. Las rutas convencionales de controlador, como {controller}/{action}/{id?}
, especifican una jerarquía mediante parámetros de ruta. Para las rutas convencionales dedicadas y las rutas de atributo a controladores y Razor Pages:
En estos casos, la generación de direcciones URL define el concepto de valores necesarios. Los puntos de conexión creados por controladores y Razor Pages tienen valores necesarios especificados que permiten que la invalidación del valor de ruta funcione.
El algoritmo de invalidación del valor de ruta en detalle:
En este punto, la operación de generación de direcciones URL está lista para evaluar las restricciones de ruta. El conjunto de valores aceptados se combina con los valores predeterminados de parámetro, que se proporcionan a las restricciones. Si todas las restricciones son correctas, la operación continúa.
A continuación, se pueden usar los valores aceptados para expandir la plantilla de ruta. La plantilla de ruta se procesa:
Los valores que se proporcionan de forma explícita que no coinciden con un segmento de la ruta se agregan a la cadena de consulta. En la tabla siguiente se muestra el resultado cuando se usa la plantilla de ruta {controller}/{action}/{id?}
.
Valores de ambiente | Valores explícitos | Resultado |
---|---|---|
controller = "Home" | action = "About" | /Home/About |
controller = "Home" | controller = "Order", action = "About" | /Order/About |
controller = "Home", color = "Red" | action = "About" | /Home/About |
controller = "Home" | action = "About", color = "Red" | /Home/About?color=Red |
A partir de ASP.NET Core 3.0, algunos esquemas de generación de direcciones URL que se usaban en versiones anteriores de ASP.NET Core no funcionan bien con la generación de direcciones URL. El equipo de ASP.NET Core planea agregar características para abordar estas necesidades en una versión futura. Por ahora, la mejor solución consiste en usar el enrutamiento heredado.
En el código siguiente se muestra un ejemplo de un esquema de generación de direcciones URL que no es compatible con el enrutamiento.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default",
"{culture}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("blog", "{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost", });
});
En el código anterior, se usa el parámetro de ruta culture
para la localización. El objetivo es que el parámetro culture
siempre se acepte como valor de ambiente. Pero el parámetro culture
no se acepta como valor de ambiente debido al funcionamiento de los valores necesarios:
"default"
, el parámetro de ruta culture
está a la izquierda de controller
, por lo que los cambios en controller
no invalidarán culture
."blog"
, se considera que el parámetro de ruta culture
está a la derecha de controller
, que aparece en los valores necesarios.Los vínculos siguientes proporcionan información sobre la configuración de metadatos de punto de conexión:
[MinimumAgeAuthorize]
personalizadoRequireHost aplica una restricción a la ruta que requiere el host especificado. El parámetro RequireHost
o [Host] puede ser:
www.domain.com
, compara www.domain.com
con cualquier puerto.*.domain.com
, coincide con www.domain.com
, subdomain.domain.com
o www.subdomain.domain.com
en cualquier puerto.*:5000
, coincide con el puerto 5000 con cualquier host.www.domain.com:5000
o *.domain.com:5000
, coincide con el host y el puerto.Se pueden especificar varios parámetros mediante RequireHost
o [Host]
. La restricción coincide con los hosts válidos para cualquiera de los parámetros. Por ejemplo, [Host("domain.com", "*.domain.com")]
coincide con domain.com
, www.domain.com
y subdomain.domain.com
.
En el código siguiente se usa RequireHost
para requerir el host especificado en la ruta:
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");
});
}
En el código siguiente se usa el atributo [Host]
en el controlador para requerir cualquiera de los 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();
}
}
Cuando el atributo [Host]
se aplica al método de acción y al controlador:
La mayor parte del enrutamiento se ha actualizado en ASP.NET Core 3.0 para aumentar el rendimiento.
Cuando una aplicación tiene problemas de rendimiento, a menudo se sospecha que el enrutamiento es el problema. El motivo es que marcos como los controladores y Razor Pages notifican la cantidad de tiempo empleado en el marco de trabajo en sus mensajes de registro. Cuando hay una diferencia significativa entre el tiempo notificado por los controladores y el tiempo total de la solicitud:
El rendimiento del enrutamiento se prueba mediante miles de puntos de conexión. No es probable que una aplicación típica detecte un problema de rendimiento simplemente por ser demasiado grande. La causa raíz más común del rendimiento lento del enrutamiento suele ser middleware personalizado con un comportamiento incorrecto.
En el ejemplo de código siguiente se muestra una técnica básica para limitar el origen del retraso:
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 controlar el tiempo del enrutamiento:
Se trata de una forma básica de reducir el retraso cuando es significativo, por ejemplo, de más de 10ms
. Al restar Time 2
de Time 1
se notifica el tiempo invertido dentro del middleware UseRouting
.
En el código siguiente se usa un enfoque más compacto del código de control de tiempo 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.");
});
});
}
En la lista siguiente se proporciona información sobre las características de enrutamiento que son relativamente costosas en comparación con las plantillas de ruta básicas:
{x}-{y}-{z}
): Esta sección contiene instrucciones para los autores de bibliotecas que realizan la compilación sobre el enrutamiento. Estos detalles están diseñados para garantizar que los desarrolladores de aplicaciones tengan una buena experiencia en el uso de bibliotecas y marcos que amplían el enrutamiento.
Para crear un marco que use el enrutamiento para la coincidencia de direcciones URL, empiece por definir una experiencia de usuario que se base en UseEndpoints.
REALICE la compilación sobre IEndpointRouteBuilder. Esto permite a los usuarios crear el marco de trabajo con otras características de ASP.NET Core sin confusión. Todas las plantillas de ASP.NET Core incluyen el enrutamiento. Asuma que el enrutamiento está presente y es familiar para los usuarios.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...);
endpoints.MapHealthChecks("/healthz");
});
DEVUELVA un tipo concreto sellado a partir de una llamada a MapMyFramework(...)
que implemente IEndpointConventionBuilder. La mayoría de los métodos Map...
del marco siguen este patrón. La interfaz IEndpointConventionBuilder
:
La declaración de un tipo propio permite agregar funcionalidad específica del marco propia al generador. Es correcto encapsular un generador declarado por el marco y reenviarle llamadas.
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);
endpoints.MapHealthChecks("/healthz");
});
CONSIDERE LA POSIBILIDAD de escribir un objeto EndpointDataSource propio. EndpointDataSource
es la primitiva de bajo nivel para declarar y actualizar una colección de puntos de conexión. EndpointDataSource
es una API eficaz que usan los controladores y Razor Pages.
Las pruebas de enrutamiento tienen un ejemplo básico de un origen de datos que no es de actualización.
NO intente registrar un objeto EndpointDataSource
de forma predeterminada. Exija a los usuarios que registren el marco en UseEndpoints. La filosofía del enrutamiento es que nada se incluye de forma predeterminada y que UseEndpoints
es el lugar donde se registran los puntos de conexión.
CONSIDERE LA POSIBILIDAD de definir tipos de metadatos como una interfaz.
PERMITA el uso de los tipos de metadatos como atributo en clases y métodos.
public interface ICoolMetadata
{
bool IsCool { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}
Los marcos como los controladores y Razor Pages admiten la aplicación de atributos de metadatos a tipos y métodos. Si declara tipos de metadatos:
La declaración de un tipo de metadatos como una interfaz agrega otro nivel de flexibilidad:
PERMITA que los metadatos se puedan invalidar, como se muestra en el ejemplo siguiente:
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() { }
}
La mejor manera de seguir estas instrucciones es evitar la definición de metadatos de marcador:
La colección de metadatos está ordenada y admite la invalidación por prioridad. En el caso de los controladores, los metadatos del método de acción son más específicos.
PERMITA que el middleware sea útil con y sin el enrutamiento.
app.UseRouting();
app.UseAuthorization(new AuthorizationPolicy() { ... });
app.UseEndpoints(endpoints =>
{
// Your framework
endpoints.MapMyFramework(...).RequireAuthorization();
});
Como ejemplo de esta instrucción, considere la posibilidad de usar el middleware UseAuthorization
. El middleware de autorización permite pasar una directiva de reserva. La directiva de reserva, si se especifica, se aplica a:
Esto hace que el middleware de autorización sea útil fuera del contexto del enrutamiento. El middleware de autorización se puede usar para la programación de middleware tradicional.
Para ver la salida detallada del diagnóstico de cálculo de ruta, establezca Logging:LogLevel:Microsoft
en Debug
. En el entorno de desarrollo, establezca el nivel de registro en appsettings.Development.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Comentarios de ASP.NET Core
ASP.NET Core é un proxecto de código aberto. Selecciona unha ligazón para ofrecer comentarios:
Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis informaciónFormación
Módulo
Uso de páginas, enrutamiento y diseños para mejorar la navegación de Blazor - Training
Obtenga información sobre cómo optimizar la navegación de la aplicación, usar parámetros de la dirección URL y crear diseños reutilizables en una aplicación web Blazor.