Enrutar a acciones de controlador de ASP.NET Core

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, vea la versión ASP.NET Core 8.0 de este artículo.

Los controladores de ASP.NET Core utilizan el middleware Routing para hacer coincidir las URL de las solicitudes entrantes y asignarlas a acciones. Plantillas de ruta:

  • Se definen al inicio en Program.cs o en atributos.
  • Describir cómo se hacen coincidir las rutas de acceso URL con las acciones.
  • Se usan para generar direcciones URL para vínculos. Normalmente, los vínculos generados se devuelven en las respuestas.

Las acciones se enrutan bien mediante convención o bien mediante atributos. Colocar una ruta en el controlador o la acción hace que se enrute mediante atributos. Consulte Enrutamiento mixto para obtener más información.

Este documento:

  • Explica las interacciones entre MVC y el enrutamiento:
    • Cómo las aplicaciones MVC típicas usan las características de enrutamiento.
    • Cubre ambos:
      • El enrutamiento convencional se usa normalmente con controladores y vistas.
      • Enrutamiento de atributos usado con REST las API. Si está interesado principalmente en el enrutamiento de REST las API, vaya a la sección Enrutamiento de atributos para REST LAS API .
    • Consulte Enrutamiento para obtener más información sobre enrutamiento avanzado.
  • Hace referencia al sistema de enrutamiento predeterminado denominado enrutamiento de puntos de conexión. Es posible usar controladores con la versión anterior de enrutamiento con fines de compatibilidad. Consulte la guía de migración 2.2-3.0 para obtener instrucciones.

Configuración de una ruta convencional

La plantilla ASP.NET Core MVC genera código de enrutamiento convencional similar al siguiente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

MapControllerRoute se usa para crear una única ruta. La ruta única se denomina default ruta. La mayoría de las aplicaciones con controladores y vistas utilizan una plantilla de ruta similar a la ruta default. REST Las API deben usar el enrutamiento de atributos.

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

La plantilla de ruta"{controller=Home}/{action=Index}/{id?}":

  • Coincide con una ruta de acceso url como /Products/Details/5

  • Extrae los valores { controller = Products, action = Details, id = 5 } de ruta mediante la tokenización de la ruta de acceso. La extracción de valores de ruta da como resultado una coincidencia si la aplicación tiene un controlador denominado ProductsController y una Details acción:

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

    El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

  • /Products/Details/5 model enlaza el valor de id = 5 para establecer el id parámetro en 5. Consulte Enlace de modelos para obtener más detalles.

  • {controller=Home} define Home como el valor controller predeterminado.

  • {action=Index} define Index como el valor action predeterminado.

  • El caracter ? en {id?} define id como opcional.

    • No es necesario que los parámetros de ruta opcionales y predeterminados estén presentes en la ruta de dirección URL para una coincidencia. Consulte Referencia de plantilla de ruta para obtener una descripción detallada de la sintaxis de la plantilla de ruta.
  • Devuelve la ruta de acceso de la dirección URL /.

  • Genera los valores { controller = Home, action = Index }de ruta .

Los valores de controller y action hacen uso de los valores predeterminados. id no produce ningún valor ya que no hay ningún segmento correspondiente en la ruta URL. / solo coincide si existe una HomeController acción y Index:

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

Usando esta definición de controlador y la plantilla de ruta, la acción HomeController.Index se ejecutaría para cualquiera de las rutas de dirección URL siguientes:

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

La ruta de acceso URL / usa los controladores y Home la acción predeterminados Index de la plantilla de ruta. La ruta URL /Home utliza la acción Index por defecto de la plantilla de ruta.

El método de conveniencia MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Reemplaza a:

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

Importante

El enrutamiento se configura mediante el middleware UseRouting y UseEndpoints . Para usar controladores:

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. Para obtener más información, consulte Enrutamiento en ASP.NET Core.

Enrutamiento convencional

El enrutamiento convencional se usa con controladores y vistas. La ruta default:

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

El ejemplo anterior es un ejemplo de una ruta convencional. Se denomina enrutamiento convencional porque establece una convención para las rutas URL:

  • El primer segmento de la ruta {controller=Home} asigna el nombre de controlador.
  • El segundo segmento, {action=Index}, asigna el nombre de la acción.
  • El tercer segmento, {id?} se usa para un opcional id. El ? en {id?} lo hace opcional. id se usa para asignar a una entidad de modelo.

Con esta default ruta, la ruta de acceso url:

  • /Products/List se asigna a la ProductsController.List acción.
  • /Blog/Article/17 se asigna a BlogController.Article y normalmente enlaza el id parámetro a 17.

Esta asignación:

  • Solo se basa en los nombres de controlador y acción.
  • No se basa en espacios de nombres, ubicaciones de archivos de origen ni parámetros de método.

Utilizar el enrutamiento convencional con la ruta por defecto permite crear la aplicación sin tener que idear un nuevo patrón de URL para cada acción. Para una aplicación con acciones de estilo CRUD , con coherencia para las direcciones URL entre controladores:

  • Ayuda a simplificar el código.
  • Hace que la interfaz de usuario sea más predecible.

Advertencia

El id elemento del código anterior se define como opcional en la plantilla de ruta. Las acciones se pueden ejecutar sin el identificador opcional proporcionado como parte de la dirección URL. Por lo general, cuando id se omite de la dirección URL:

  • id se establece en 0 mediante el enlace de modelos.
  • No se encuentra ninguna entidad en la base de datos que coincida con id == 0.

El enrutamiento de atributos proporciona un control preciso para hacer que el ID sea necesario para algunas acciones y no para otras. Por convención, la documentación incluirá parámetros opcionales como id cuando sea más probable que su uso sea correcto.

La mayoría de las aplicaciones deben elegir un esquema de enrutamiento básico y descriptivo para que las direcciones URL sean legibles y significativas. La ruta convencional predeterminada {controller=Home}/{action=Index}/{id?}:

  • Admite un esquema de enrutamiento básico y descriptivo.
  • Se trata de un punto de partida útil para las aplicaciones basadas en la interfaz de usuario.
  • Es la única plantilla de ruta necesaria para muchas aplicaciones de interfaz de usuario web. Para aplicaciones de interfaz de usuario web de mayor tamaño, a menudo basta con utilizar Areas.

MapControllerRoute y MapAreaRoute :

  • Asignar automáticamente un valor de orden a sus puntos finales en función del orden en que son invocados.

Enrutamiento de puntos de conexión en ASP.NET Core:

  • No tiene un concepto de rutas.
  • No proporciona garantías de ordenación para la ejecución de extensibilidad, todos los puntos de conexión se procesan a la vez.

Habilite el registro para ver de qué forma las implementaciones de enrutamiento integradas, como Route, coinciden con las solicitudes.

El enrutamiento de atributos se explica más adelante en este documento.

Varias rutas convencionales

Se pueden configurar varias rutas convencionales agregando más llamadas a MapControllerRoute y MapAreaControllerRoute. Hacerlo permite definir múltiples convenciones, o añadir rutas convencionales que se dedican a una acción específica , como:

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

La blog ruta del código anterior es una ruta convencional dedicada. Se denomina ruta convencional dedicada porque:

Como controller y action no aparecen en la plantilla "blog/{*article}" de ruta como parámetros:

  • Solo pueden tener los valores predeterminados { controller = "Blog", action = "Article" }.
  • Esta ruta siempre se asigna a la acción BlogController.Article.

/Blog, /Blog/Article y /Blog/{any-string} son las únicas rutas de dirección URL que coinciden con la ruta del blog.

Ejemplo anterior:

  • blog route tiene una prioridad más alta para las coincidencias que la default ruta porque se agrega primero.
  • Es un ejemplo de enrutamiento de estilo Slug en el que es habitual tener un nombre de artículo como parte de la dirección URL.

Advertencia

En ASP.NET Core, el enrutamiento no:

  • Defina un concepto denominado ruta. UseRouting agrega coincidencia de rutas a la canalización de middleware. El middleware UseRouting examina el conjunto de puntos finales definidos en la aplicación y selecciona el mejor punto final en función de la solicitud.
  • Proporcione garantías sobre el orden de ejecución de extensibilidad como IRouteConstraint o IActionConstraint.

Consulte Enrutamiento para obtener material de referencia sobre el enrutamiento.

Orden de enrutamiento convencional

El enrutamiento convencional solo coincide con una combinación de acción y controlador que la aplicación define. Con ello se pretende simplificar los casos en que las rutas convencionales se solapan. Al añadir rutas utilizando MapControllerRoute, MapDefaultControllerRoute y MapAreaControllerRoute se asigna automáticamente un valor de orden a sus puntos finales en función del orden en que se invocan. Las coincidencias de una ruta que aparece anteriormente tienen una prioridad más alta. El enrutamiento convencional depende del orden. En general, las rutas con áreas deben colocarse antes, ya que son más específicas que las rutas sin área. Las rutas convencionales dedicadas con parámetros de ruta catch-all, como {*article} , pueden hacer que una ruta sea demasiado expansivas, lo que significa que coincide con las direcciones URL que pretende que coincidan con otras rutas. Coloque las rutas expansivas más adelante en la tabla de rutas para evitar coincidencias expansivas.

Advertencia

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:

  • Una ruta catch-all (por ejemplo, {**slug}")
  • La ruta catch-all causa un error al relacionar solicitudes que sí que debería relacionar.
  • Al quitar otras rutas, la ruta catch-all empieza a funcionar.

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.

Resolución de acciones ambiguas

Cuando dos puntos de conexión coinciden a través del enrutamiento, el enrutamiento debe realizar una de las siguientes acciones:

  • Elija el mejor candidato.
  • Iniciar una excepción.

Por ejemplo:

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

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

El controlador anterior define dos acciones que coinciden con:

  • Ruta de acceso de la URL/Products33/Edit/17
  • Datos de ruta { controller = Products33, action = Edit, id = 17 }.

Este es un patrón típico para los controladores MVC:

  • Edit(int) muestra un formulario para editar un producto.
  • Edit(int, Product) procesa el formulario publicado.

Para resolver la ruta correcta:

  • Edit(int, Product) es seleccionada cuando la solicitud es una POST HTTP.
  • Edit(int) se selecciona cuando el verbo HTTP es cualquier otra cosa. Edit(int) normalmente se llama a través de GET.

HttpPostAttribute, [HttpPost]se proporciona al enrutamiento para que pueda elegir en función del método HTTP de la solicitud. El HttpPostAttribute hace de Edit(int, Product) una mejor combinación que Edit(int).

Es importante comprender el rol de atributos como HttpPostAttribute. Los atributos similares se definen para otros verbos HTTP. En el enrutamiento convencional es común que las acciones utilicen el mismo nombre de acción cuando son parte de un flujo de trabajo. Por ejemplo, vea Examinar los dos métodos de acción Editar.

Si el enrutamiento no puede elegir un mejor candidato, se produce una AmbiguousMatchException excepción , que enumera los varios puntos de conexión coincidentes.

Nombres de ruta convencionales

Las cadenas "blog" y "default" en los siguientes ejemplos son nombres de ruta convencionales:

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

Los nombres de ruta asignan un nombre lógico a la ruta. La ruta con nombre puede utilizarse para la generación de URL. El uso de una ruta con nombre simplifica la creación de URL cuando el orden de las rutas podría complicar la generación de URL. Los nombres de ruta deben ser únicos en toda la aplicación.

Nombres de ruta:

  • No tiene ningún impacto en la coincidencia de direcciones URL ni en el control de las solicitudes.
  • Solo se usan para la generación de direcciones URL.

El concepto de nombre de ruta se representa en el enrutamiento como IEndpointNameMetadata. Los términos nombre de ruta y nombre del punto de conexión:

  • Son intercambiables.
  • El que se usa en la documentación y el código depende de la API que se describe.

Enrutamiento de atributos para REST las API

Las API REST deben usar el enrutamiento mediante atributos para modelar la funcionalidad de la aplicación como un conjunto de recursos donde las operaciones se representan mediante verbos HTTP.

El enrutamiento mediante atributos utiliza un conjunto de atributos para asignar acciones directamente a las plantillas de ruta. El siguiente código es típico para una API REST y se utiliza en el siguiente ejemplo:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

En el código anterior, MapControllers se llama a para asignar controladores enrutados de atributo.

En el ejemplo siguiente:

  • HomeController coincide con un conjunto de URL similares a las que coincide la ruta convencional {controller=Home}/{action=Index}/{id?} por defecto.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

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

La acción HomeController.Index se ejecuta para cualquiera de las rutas URL /, /Home, /Home/Index o /Home/Index/3.

Este ejemplo resalta una diferencia clave de programación entre el enrutamiento mediante atributos y el enrutamiento convencional. El enrutamiento de atributos requiere más entrada para especificar una ruta. La ruta predeterminada convencional controla las rutas más concisamente. Pero el enrutamiento mediante atributos permite (y requiere) un control preciso de las plantillas de ruta que se aplicarán a cada acción.

Con el enrutamiento de atributos, los nombres de controlador y acción no desempeñan ninguna parte en la que coincide la acción, a menos que se use el reemplazo de tokens . En el ejemplo siguiente se coinciden las mismas direcciones URL que el ejemplo anterior:

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

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

En el código siguiente se usa el reemplazo de tokens para action y controller:

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

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

El siguiente código aplica [Route("[controller]/[action]")] al controlador:

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

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

En el código anterior, las Index plantillas de método deben anteponer / o ~/ a las plantillas de ruta. Las plantillas de ruta aplicadas a una acción que comienzan por / o ~/ no se combinan con las plantillas de ruta que se aplican al controlador.

Consulte Precedencia de la plantilla de ruta para obtener información sobre la selección de plantillas de ruta.

Nombres de enrutamientos reservados

Las siguientes palabras clave son nombres de parámetros de ruta reservados al usar Controladores o Razor páginas:

  • action
  • area
  • controller
  • handler
  • page

El uso page de como parámetro de ruta con enrutamiento de atributos es un error común. Esto da como resultado un comportamiento incoherente y confuso con la generación de direcciones URL.

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

La generación de direcciones URL usa los nombres de parámetros especiales para determinar si una operación de generación de direcciones URL hace referencia a una Razor página o a un controlador.

Las palabras clave siguientes se reservan en el contexto de una vista de Razor o de una página de Razor:

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

Estas palabras clave no deben utilizarse para generaciones de enlaces, parámetros vinculados al modelo o propiedades de nivel superior.

Plantillas de verbo HTTP

ASP.NET Core tiene las siguientes plantillas de verbo HTTP:

Plantillas de ruta

ASP.NET Core tiene las siguientes plantillas de ruta:

Enrutamiento mediante atributos con atributos de verbo Http

Considere el siguiente controlador:

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

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

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

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

En el código anterior:

  • Cada acción contiene el [HttpGet] atributo , que restringe la coincidencia solo con las solicitudes HTTP GET.
  • La GetProduct acción incluye la "{id}" plantilla, por lo tanto id , se anexa a la "api/[controller]" plantilla en el controlador. La plantilla de métodos es "api/[controller]/{id}". Por lo tanto, esta acción solo coincide con las solicitudes GET para el formulario /api/test2/xyz,/api/test2/123/api/test2/{any string} , etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • La GetIntProduct acción contiene la "int/{id:int}" plantilla. La :int parte de la plantilla restringe los id valores de ruta a cadenas que se pueden convertir en un entero. Una solicitud de obtención para /api/test2/int/abc:
    • No coincide con esta acción.
    • Devuelve un error 404 No encontrado.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • La GetInt2Product acción contiene {id} en la plantilla, pero no limita id a los valores que se pueden convertir en un entero. Una solicitud de obtención para /api/test2/int2/abc:
    • Coincide con esta ruta.
    • El enlace de modelos no se puede convertir abc en un entero. El id parámetro del método es entero.
    • Devuelve un error 400 solicitud incorrecta porque el enlace de modelos no pudo convertir abc en un entero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

El enrutamiento por atributos HttpMethodAttribute puede utilizar atributos como HttpPostAttribute, HttpPutAttribute y HttpDeleteAttribute. Todos los atributos del verbo HTTP aceptan una plantilla de ruta. Este ejemplo muestra dos acciones que coinciden con la misma plantilla de ruta:

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

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

Uso de la ruta de acceso url /products3:

  • La MyProductsController.ListProducts acción se ejecuta cuando el verbo HTTP es GET.
  • La MyProductsController.CreateProduct acción se ejecuta cuando el verbo HTTP es POST.

Cuando se construye una API REST, es raro que necesites utilizar [Route(...)] en un método de acción porque la acción acepta todos los métodos HTTP. Es mejor usar más específicos para precisar lo que es compatible con la API. Se espera que los clientes de las API de REST sepan qué rutas y verbos HTTP corresponden a operaciones lógicas específicas.

Las API REST deben usar el enrutamiento mediante atributos para modelar la funcionalidad de la aplicación como un conjunto de recursos donde las operaciones se representan mediante verbos HTTP. Esto significa que muchas operaciones, por ejemplo, GET y POST, del mismo recurso lógico usan la misma dirección URL. El enrutamiento mediante atributos proporciona un nivel de control que es necesario para diseñar cuidadosamente un diseño de puntos de conexión públicos de la API.

Puesto que una ruta de atributo se aplica a una acción específica, es fácil crear parámetros necesarios como parte de la definición de plantilla de ruta. En este ejemplo, se requiere id como parte de la ruta de dirección de URL:

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

La acción Products2ApiController.GetProduct(int):

  • Se ejecuta con la ruta de acceso URL, como /products2/3
  • No se ejecuta con la ruta de acceso URL /products2.

El atributo [Consumes] permite que una acción limite los tipos de contenido de la solicitud compatibles. Para obtener más información, consulte Definir los tipos de contenido de solicitud compatibles con el atributo Consumes.

Consulte Enrutamiento para obtener una descripción completa de las plantillas de ruta y las opciones relacionadas.

Para obtener más información sobre [ApiController], vea Atributo ApiController.

Nombre de ruta

El siguiente código define un nombre de ruta Products_List:

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

Los nombres de ruta se pueden utilizar para generar una dirección URL basada en una ruta específica. Nombres de ruta:

  • No tienen ningún impacto en el comportamiento de coincidencia de URL del enrutamiento.
  • Solo se usan para la generación de direcciones URL.

Los nombres de ruta deben ser únicos en toda la aplicación.

Contrasta el código anterior con la ruta por defecto convencional, que define el parámetro idcomo opcional ({id?}). Esta capacidad de especificar con precisión las API tiene sus ventajas, como permitir que /products y /products/5 se envíen a acciones diferentes.

Combinación de rutas de atributo

Para que el enrutamiento mediante atributos sea menos repetitivo, los atributos de ruta del controlador se combinan con los atributos de ruta de las acciones individuales. Las plantillas de ruta definidas en el controlador se anteponen a las plantillas de ruta de las acciones. La colocación de un atributo de ruta en el controlador hace que todas las acciones del controlador usen el enrutamiento mediante atributos.

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

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

En el ejemplo anterior:

  • La ruta URL /products puede coincidir con ProductsApi.ListProducts
  • La ruta URL /products/5 puede coincidir con ProductsApi.GetProduct(int).

Ambas acciones sólo coinciden con HTTP GETporque están marcadas con el atributo [HttpGet].

Las plantillas de ruta aplicadas a una acción que comienzan por / o ~/ no se combinan con las plantillas de ruta que se aplican al controlador. El siguiente ejemplo coincide con un conjunto de rutas URL similares a la ruta por defecto.

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

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

En la tabla siguiente se explican los [Route] atributos del código anterior:

Atributo Combina con [Route("Home")] Define la plantilla de ruta
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] No ""
[Route("About")] "Home/About"

Orden de ruta de atributo

El enrutamiento crea un árbol y coincide con todos los puntos de conexión simultáneamente:

  • Las entradas de ruta se comportan como si se colocara en una ordenación ideal.
  • Las rutas más específicas tienen la oportunidad de ejecutarse antes que las rutas más generales.

Por ejemplo, una ruta de atributos como blog/search/{topic} es más específica que una ruta de atributos como blog/{*article}. La blog/search/{topic} ruta tiene una prioridad más alta, de forma predeterminada, porque es más específica. En el enrutamiento convencional, el desarrollador es responsable de colocar las rutas en el orden deseado.

Las rutas de atributo pueden configurar un pedido mediante la Order propiedad . Todos los atributos de ruta proporcionados por el marco incluyen Order . Las rutas se procesan de acuerdo con el orden ascendente de la propiedad Order. El orden predeterminado es 0. El establecimiento de una ruta Order = -1 utilizando se ejecuta antes que las rutas que no establecen un orden. Si una ruta se configura con Order = 1, se ejecutará después del orden de rutas predeterminado.

Evite depender de Order. Si su espacio de direcciones URL requiere unos valores de orden explícitos para un enrutamiento correcto, es probable que también sea confuso para los clientes. Por lo general, el enrutamiento mediante atributos seleccionará la ruta correcta con la coincidencia de dirección URL. Si el orden predeterminado que se usa para la generación de direcciones URL no funciona, normalmente es más sencillo utilizar el nombre de ruta como una invalidación que aplicar la propiedad Order.

Tenga en cuenta los dos controladores siguientes que definen la ruta que coincide /homecon:

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

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

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

La solicitud de /home con el código anterior lanza una excepción similar a la siguiente:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

Agregar Order a uno de los atributos de ruta resuelve la ambigüedad:

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

Con el código anterior, /home ejecuta el punto de HomeController.Index conexión. Para llegar a MyDemoController.MyIndex, solicite /home/MyIndex. Nota:

  • El código anterior es un ejemplo o un diseño de enrutamiento deficiente. Se usó para ilustrar la Order propiedad.
  • La Order propiedad solo resuelve la ambigüedad, que no se puede hacer coincidir con esa plantilla. Sería mejor quitar la [Route("Home")] plantilla.

Consulte Razor Enrutamiento de páginas y convenciones de aplicación: Orden de ruta para obtener información sobre el orden de ruta con Razor Pages.

En algunos casos, se devuelve un error HTTP 500 con rutas ambiguas. Use el registro para ver qué puntos de conexión provocaron AmbiguousMatchException.

Reemplazo de tokens en plantillas de ruta [controller], [action], [area]

Para mayor comodidad, las rutas de atributo admiten reemplazo de token. Para ello, incluyen un token entre corchetes ([, ]). Los tokens [action], [area] y [controller] se reemplazan con los valores del nombre de la acción, el nombre del área y el nombre del controlador de la acción donde se define la ruta:

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


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

En el código anterior:

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

El reemplazo del token se produce como último paso de la creación de las rutas de atributo. El ejemplo anterior se comporta igual que el código siguiente:

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

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

Si está leyendo este contenido en un idioma que no es el inglés, díganos si le gustaría que los comentarios sobre el código estuviesen en escritos en su idioma. Puede hacerlo en este artículo de discusión de GitHub.

Las rutas de atributo también se pueden combinar con la herencia. Esto es potente combinado con la sustitución de fichas. El reemplazo de token también se aplica a los nombres de ruta definidos por las rutas de atributo. [Route("[controller]/[action]", Name="[controller]_[action]")] genera un nombre de ruta único para cada acción:

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

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

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

Para que el delimitador de reemplazo de token [ o ] coincida, repita el carácter ([[ o ]]) para usarlo como secuencia de escape.

Usar un transformador de parámetro para personalizar el reemplazo de tokens

El reemplazo de tokens se puede personalizarse mediante un transformador de parámetro. Un transformador de parámetro implementa IOutboundParameterTransformer y transforma el valor de parámetros. Por ejemplo, un transformador de parámetros SlugifyParameterTransformer personalizado cambia el valor de ruta SubscriptionManagement a subscription-management:

using System.Text.RegularExpressions;

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

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

RouteTokenTransformerConvention es una convención de modelo de aplicación que:

  • Aplica un transformador de parámetro a todas las rutas de atributo en una aplicación.
  • Personaliza los valores de token de ruta de atributo que se reemplazan.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

El método ListAll anterior coincide con /subscription-management/list-all.

El RouteTokenTransformerConvention está registrado como opción:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

Consulte la documentación web de MDN en Slug para obtener la definición de Slug.

Advertencia

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.

Varias rutas de atributos

El enrutamiento mediante atributos permite definir varias rutas que llegan a la misma acción. El uso más común de esto es imitar el comportamiento de la ruta convencional predeterminada tal como se muestra en el ejemplo siguiente:

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

Poner múltiples atributos de ruta en el controlador significa que cada uno se combina con cada uno de los atributos de ruta en los métodos de acción:

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

Todas las restricciones de ruta de verbo HTTP implementan IActionConstraint.

Cuando se colocan varios atributos de ruta que implementan IActionConstraint en una acción:

  • Cada restricción de acción se combina con la plantilla de ruta aplicada al controlador.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

El uso de varias rutas en acciones puede parecer útil y eficaz, es mejor mantener el espacio de direcciones URL de la aplicación básico y bien definido. Utilice varias rutas en acciones solo cuando sea necesario, por ejemplo, para admitir a clientes existentes.

Especificación de parámetros opcionales de ruta de atributo, valores predeterminados y restricciones

Las rutas de atributo admiten la misma sintaxis en línea que las rutas convencionales para especificar parámetros opcionales, valores predeterminados y restricciones.

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

En el código anterior, [HttpPost("product14/{id:int}")] aplica una restricción de ruta. La Products14Controller.ShowProduct acción solo coincide con las rutas de dirección URL, como /product14/3. La parte {id:int} de la plantilla de ruta restringe ese segmento a solo enteros.

Consulte Referencia de plantilla de ruta para obtener una descripción detallada de la sintaxis de la plantilla de ruta.

Atributos de ruta personalizados mediante IRouteTemplateProvider

Todos los atributos de ruta implementan IRouteTemplateProvider. El entorno de ejecución de ASP.NET Core:

  • Busca atributos en clases de controlador y métodos de acción cuando se inicia la aplicación.
  • Utiliza los atributos que implementan IRouteTemplateProvider para construir el conjunto inicial de rutas.

Implemente IRouteTemplateProvider para definir atributos de ruta personalizados. Cada IRouteTemplateProvider le permite definir una única ruta con una plantilla de ruta, un orden y un nombre personalizados:

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

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

El Getmétodo anterior devuelve Order = 2, Template = api/MyTestApi.

Uso del modelo de aplicación para personalizar las rutas de atributo

El modelo de aplicación:

  • Es un modelo de objetos creado al iniciar en Program.cs.
  • Contiene todos los metadatos usados por ASP.NET Core para enrutar y ejecutar las acciones en una aplicación.

El modelo de aplicación incluye todos los datos recogidos de los atributos de ruta. La implementación proporciona los datos de los atributos de IRouteTemplateProvider ruta. Convenciones:

  • Se puede escribir para modificar el modelo de aplicación para personalizar el comportamiento del enrutamiento.
  • Se leen en el inicio de la aplicación.

Esta sección muestra un ejemplo básico de personalización del enrutamiento utilizando el modelo de aplicación. El código siguiente hace que las rutas se alinean aproximadamente con la estructura de carpetas del proyecto.

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

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

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

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

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

El código siguiente impide que la namespace convención se aplique a los controladores que están enrutados por atributos:

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

Por ejemplo, el controlador siguiente no usa NamespaceRoutingConvention:

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

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

El método NamespaceRoutingConvention.Apply realiza las acciones siguientes:

  • No hace nada si el controlador está enrutado por atributos.
  • Establece la plantilla de controlador basada en namespace, con la base namespace eliminada.

El NamespaceRoutingConvention puede aplicarse en Program.cs:

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Por ejemplo, considere el código siguiente:

using Microsoft.AspNetCore.Mvc;

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

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

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

En el código anterior:

  • La base namespace es My.Application.
  • El nombre completo del controlador anterior es My.Application.Admin.Controllers.UsersController.
  • NamespaceRoutingConvention establece la plantilla de controladores en Admin/Controllers/Users/[action]/{id?.

NamespaceRoutingConvention También se puede aplicar como un atributo en un controlador:

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

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

Enrutamiento mixto: enrutamiento mediante atributos frente a enrutamiento convencional

Las aplicaciones ASP.NET Core pueden mezclar el uso del enrutamiento convencional y el enrutamiento por atributos. Es habitual usar las rutas convencionales para controladores que sirven páginas HTML para los exploradores, y el enrutamiento mediante atributos para los controladores que sirven las API de REST.

Las acciones se enrutan bien mediante convención o bien mediante atributos. Colocar una ruta en el controlador o la acción hace que se enrute mediante atributos. Las acciones que definen rutas de atributo no se pueden alcanzar a través de las rutas convencionales y viceversa. Cualquier atributo de ruta en el controlador hace que todas las acciones del controlador se enruten mediante atributos.

El enrutamiento de atributos y el enrutamiento convencional usan el mismo motor de enrutamiento.

Enrutamiento con caracteres especiales

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.

Generación de direcciones URL y valores ambientales

Las aplicaciones pueden utilizar las funciones de generación de URL de enrutamiento para generar enlaces URL a las acciones. La generación de URL elimina la necesidad de codificarlas, lo que hace que el código sea más robusto y fácil de mantener. Esta sección se centra en las características de generación de direcciones URL proporcionadas por MVC y solo aborda los conceptos básicos de su funcionamiento. Consulte Enrutamiento para obtener una descripción detallada de la generación de direcciones URL.

La interfaz IUrlHelper es la pieza subyacente de la infraestructura entre MVC y el enrutamiento para la generación de direcciones URL. Encontrará una instancia de IUrlHelper disponible a través de la propiedad Url en los controladores, las vistas y los componentes de vista.

En el siguiente ejemplo, la interfaz IUrlHelper se utiliza a través de la propiedad Controller.Url para generar una URL a otra acción.

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

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

Si la aplicación está usando la ruta convencional predeterminada, el valor de la variable url será la cadena de ruta de dirección URL /UrlGeneration/Destination. Esta ruta de acceso URL se crea mediante el enrutamiento mediante la combinación de:

  • Los valores de ruta de la solicitud actual, que se denominan valores de ambiente.
  • Los valores pasados a Url.Action y sustituyendo esos valores a la plantilla de ruta:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

El valor de cada uno de los parámetros de ruta incluidos en la plantilla de ruta se sustituye por nombres que coincidan con los valores y los valores de ambiente. Un parámetro de ruta que no tiene un valor puede:

  • Use un valor predeterminado si tiene uno.
  • Se omitirá si es opcional. Por ejemplo, el id de la plantilla de ruta {controller}/{action}/{id?}.

La generación de URL falla si algún parámetro de ruta requerido no tiene un valor correspondiente. Si se produce un error en la generación de direcciones URL para una ruta, se prueba con la ruta siguiente hasta que se hayan probado todas las rutas o se encuentra una coincidencia.

En el ejemplo anterior de Url.Action supone el enrutamiento convencional. La generación de URL funciona de forma similar al enrutamiento de atributos, aunque los conceptos son diferentes. Con enrutamiento convencional:

  • Los valores de ruta se usan para expandir una plantilla.
  • Los valores de ruta de controller y action normalmente aparecen en esa plantilla. Esto funciona porque las direcciones URL coincidentes con el enrutamiento cumplen una convención.

En el ejemplo siguiente se usa el enrutamiento de atributos:

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

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

La Source acción del código anterior genera custom/url/to/destination.

LinkGeneratorse agregó en ASP.NET Core 3.0 como alternativa a IUrlHelper. LinkGenerator ofrece una funcionalidad similar pero más flexible. Cada método de IUrlHelper tiene también una familia de métodos correspondiente.LinkGenerator

Generación de direcciones URL por nombre de acción

Url.Action, LinkGenerator.GetPathByAction y todas las sobrecargas relacionadas están diseñadas para generar el punto de conexión de destino especificando un nombre de controlador y un nombre de acción.

Cuando se usa Url.Action, el tiempo de ejecución proporciona los valores de ruta actuales para controller y action:

  • El valor de controller y action forman parte de ambos valores de ambiente. El método Url.Action siempre utiliza los valores actuales de action y controller y genera una ruta de dirección URL que dirige a la acción actual.

El enrutamiento intenta utilizar los valores del entorno para rellenar la información que no se proporcionó al generar una URL. Considere una ruta como {a}/{b}/{c}/{d} con los valores { a = Alice, b = Bob, c = Carol, d = David }de ambiente:

  • El enrutamiento tiene suficiente información para generar una dirección URL sin valores adicionales.
  • El enrutamiento tiene suficiente información porque todos los parámetros de ruta tienen un valor.

Si se añade el valor { d = Donovan }:

  • El valor { d = David } se omite.
  • La ruta de acceso URL generada es Alice/Bob/Carol/Donovan.

Advertencia: Las rutas URL son jerárquicas. En el ejemplo anterior, si se agrega el valor { c = Cheryl }:

  • Ambos valores { c = Carol, d = David } se omiten.
  • Ya no hay un valor para d y se produce un error en la generación de direcciones URL.
  • Los valores deseados de c y d deben especificarse para generar una dirección URL.

Es posible que se produzca este problema con la ruta {controller}/{action}/{id?} predeterminada. Este problema es poco frecuente en la práctica porque Url.Action siempre especifica explícitamente un controller valor y action .

Varias sobrecargas de Url.Action toman un objeto de valores de ruta para proporcionar valores para parámetros de ruta distintos de controller y action. El objeto de valores de ruta se usa con frecuencia con id. Por ejemplo: Url.Action("Buy", "Products", new { id = 17 }). Objeto de valores de ruta:

  • Por convención, normalmente es un objeto de tipo anónimo.
  • Puede ser IDictionary<> o POCO).

Los valores de ruta adicionales que no coinciden con los parámetros de ruta se colocan en la cadena de consulta.

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

El código anterior genera /Products/Buy/17?color=red.

El código siguiente genera una dirección URL absoluta:

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

Para crear una dirección URL absoluta, use una de las siguientes opciones:

  • Una sobrecarga que acepta un objeto protocol. Por ejemplo, el código anterior.
  • LinkGenerator.GetUriByAction, que genera URI absolutos de forma predeterminada.

Generación de direcciones URL por ruta

El código anterior muestra la generación de una URL pasando el controlador y el nombre de la acción. IUrlHelper también proporciona la familia de métodos Url.RouteUrl. Estos métodos son similares a Url.Action, pero no copian los valores actuales de action y a los valores de la ruta controller. El uso más común de Url.RouteUrl:

  • Especifica un nombre de ruta para generar la dirección URL.
  • Por lo general, no especifica un nombre de controlador o acción.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

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

El siguiente Razor archivo genera un vínculo HTML a Destination_Route:

<h1>Test Links</h1>

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

Generar direcciones URL en HTML y Razor

IHtmlHelper proporciona los métodos HtmlHelperHtml.BeginForm y Html.ActionLink para generar lo elementos <form> y <a> respectivamente. Estos métodos utilizan el método Url.Action para generar una URL y aceptan argumentos similares. Los métodos Url.RouteUrl complementarios de HtmlHelper son Html.BeginRouteForm y Html.RouteLink, cuya funcionalidad es similar.

Las TagHelper generan direcciones URL a través de la TagHelper form y la TagHelper <a>. Ambos usan IUrlHelper para su implementación. Consulte Asistentes de etiquetas en formularios para obtener más información.

Dentro de las vistas, IUrlHelper está disponible a través de la propiedad Url para una generación de direcciones URL ad hoc no cubierta por los pasos anteriores.

Generación de direcciones URL en los resultados de la acción

En los ejemplos anteriores se muestra el uso IUrlHelper de en un controlador. El uso más común en un controlador es generar una URL como parte del resultado de una acción.

Las clases base ControllerBase y Controller proporcionan métodos de conveniencia para los resultados de acción que hacen referencia a otra acción. Un uso típico es redirigir después de aceptar la entrada del usuario:

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

Los métodos de fábrica de resultados de acciones como RedirectToAction y CreatedAtAction siguen un patrón similar al de los métodos en IUrlHelper.

Caso especial para rutas convencionales dedicadas

El enrutamiento convencional puede utilizar un tipo especial de definición de ruta denominada ruta convencional dedicada. En el ejemplo siguiente, la ruta llamada blog es una ruta convencional dedicada:

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

Con las definiciones de ruta anteriores, Url.Action("Index", "Home") genera la ruta / de acceso url mediante la default ruta, pero ¿por qué? Se puede suponer que los valores de ruta { controller = Home, action = Index } son suficientes para generar una dirección URL utilizando blog, con el resultado /blog?action=Index&controller=Home.

Las rutas convencionales dedicadas se basan en un comportamiento especial de los valores predeterminados que no tienen un parámetro de ruta correspondiente que impida que la ruta sea demasiado expansiva con la generación de direcciones URL. En este caso, los valores predeterminados son { controller = Blog, action = Article }, y ni controller ni action aparecen como un parámetro de ruta. Cuando el enrutamiento realiza la generación de direcciones URL, los valores proporcionados deben coincidir con los valores predeterminados. La generación de direcciones URL con blog producirá un error porque los valores { controller = Home, action = Index } no coinciden con { controller = Blog, action = Article }. Después, el enrutamiento vuelve para probar default, operación que se realiza correctamente.

Áreas

Las áreas son una característica MVC utilizada para organizar funcionalidades relacionadas en un grupo como separadas:

  • Espacio de nombres de enrutamiento para acciones del controlador.
  • Estructura de carpetas para vistas.

El uso de áreas permite que una aplicación tenga varios controladores con el mismo nombre, siempre que tengan áreas diferentes. El uso de áreas crea una jerarquía para el enrutamiento mediante la adición de otro parámetro de ruta, area, a controller y action. En esta sección se describe cómo interactúa el enrutamiento con las áreas. Consulte Áreas para obtener más información sobre cómo se utilizan las áreas con las vistas.

En el ejemplo siguiente se configura MVC para usar la ruta predeterminada convencional y una arearuta de áreaarea para un área denominada Blog:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

En el código anterior, MapAreaControllerRoute se llama a para crear ."blog_route" El segundo parámetro, "Blog", es el nombre del área.

Cuando coincide con una ruta de acceso de dirección URL como /Manage/Users/AddUser, la "blog_route" ruta genera los valores { area = Blog, controller = Users, action = AddUser } de ruta . El area valor de ruta se genera con un valor predeterminado para area. La ruta creada por MapAreaControllerRoute es equivalente a la siguiente:

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

MapAreaControllerRoute utiliza el nombre de área proporcionado, que en este caso es Blog, para crear una ruta con un valor predeterminado y una restricción para area. El valor predeterminado garantiza que la ruta siempre produce { area = Blog, ... }; la restricción requiere el valor { area = Blog, ... } para la generación de la dirección URL.

El enrutamiento convencional depende del orden. En general, las rutas con áreas deben colocarse antes, ya que son más específicas que las rutas sin área.

Utilizando el ejemplo anterior, los valores de ruta { area = Blog, controller = Users, action = AddUser } coinciden con la siguiente acción:

using Microsoft.AspNetCore.Mvc;

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

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

El atributo [Área] es el que indica que un controlador forma parte de un área. Este controlador está en el Blog área. Los controladores sin un atributo [Area] no son miembros de ningún área y no coincidirán cuando el enrutamiento proporcione el valor de ruta area. En el ejemplo siguiente, solo el primer controlador enumerado puede coincidir con los valores de ruta { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

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

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

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

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

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

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

El espacio de nombres de cada controlador se muestra aquí por integridad. Si los controladores anteriores usaban el mismo espacio de nombres, se generaría un error del compilador. Los espacios de nombres de clase no tienen ningún efecto en el enrutamiento de MVC.

Los dos primeros controladores son miembros de las áreas y solo coinciden cuando el valor de ruta area proporciona su respectivo nombre de área. El tercer controlador no es miembro de ningún área y solo puede coincidir cuando el enrutamiento no proporciona ningún valor para area.

En términos de búsqueda de coincidencias de ningún valor, la ausencia del valor area es igual que si el valor de area fuese null o una cadena vacía.

Al ejecutar una acción en un área, el valor de ruta para area estará disponible como un valor de ambiente para que el enrutamiento pueda usarlo en la generación de direcciones URL. Esto significa que, de forma predeterminada, las áreas actúan de forma adhesiva para la generación de direcciones URL, tal como se muestra en el ejemplo siguiente.

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

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

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

El siguiente código genera una URL para /Zebra/Users/AddUser:

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

Definición de acción

Los métodos públicos de un controlador, excepto aquellos con el atributo NonAction, son acciones.

Código de ejemplo

Diagnóstico de depuración

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

Los controladores de ASP.NET Core utilizan el middleware Routing para hacer coincidir las URL de las solicitudes entrantes y asignarlas a acciones. Plantillas de ruta:

  • Se definen en el código de inicio o en los atributos.
  • Describir cómo se hacen coincidir las rutas de acceso URL con las acciones.
  • Se usan para generar direcciones URL para vínculos. Normalmente, los vínculos generados se devuelven en las respuestas.

Las acciones se enrutan bien mediante convención o bien mediante atributos. Colocar una ruta en el controlador o la acción hace que se enrute mediante atributos. Consulte Enrutamiento mixto para obtener más información.

Este documento:

  • Explica las interacciones entre MVC y el enrutamiento:
    • Cómo las aplicaciones MVC típicas usan las características de enrutamiento.
    • Cubre ambos:
      • El enrutamiento convencional se usa normalmente con controladores y vistas.
      • Enrutamiento de atributos usado con REST las API. Si está interesado principalmente en el enrutamiento de REST las API, vaya a la sección Enrutamiento de atributos para REST LAS API .
    • Consulte Enrutamiento para obtener más información sobre enrutamiento avanzado.
  • Hace referencia al sistema de enrutamiento predeterminado agregado en ASP.NET Core 3.0, denominado enrutamiento de puntos de conexión. Es posible usar controladores con la versión anterior de enrutamiento con fines de compatibilidad. Consulte la guía de migración 2.2-3.0 para obtener instrucciones. Consulte la versión 2.2 de este documento para obtener material de referencia sobre el sistema de enrutamiento heredado.

Configuración de una ruta convencional

Startup.Configure Normalmente, tiene código similar al siguiente cuando se usa el enrutamiento convencional:

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

Dentro de la llamada a UseEndpoints, MapControllerRoute se utiliza para crear una única ruta. La ruta única se denomina default ruta. La mayoría de las aplicaciones con controladores y vistas utilizan una plantilla de ruta similar a la ruta default. REST Las API deben usar el enrutamiento de atributos.

La plantilla de ruta"{controller=Home}/{action=Index}/{id?}":

  • Coincide con una ruta de acceso url como /Products/Details/5

  • Extrae los valores { controller = Products, action = Details, id = 5 } de ruta mediante la tokenización de la ruta de acceso. La extracción de valores de ruta da como resultado una coincidencia si la aplicación tiene un controlador denominado ProductsController y una Details acción:

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

    El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

  • /Products/Details/5 model enlaza el valor de id = 5 para establecer el id parámetro en 5. Consulte Enlace de modelos para obtener más detalles.

  • {controller=Home} define Home como el valor controller predeterminado.

  • {action=Index} define Index como el valor action predeterminado.

  • El caracter ? en {id?} define id como opcional.

  • No es necesario que los parámetros de ruta opcionales y predeterminados estén presentes en la ruta de dirección URL para una coincidencia. Consulte Referencia de plantilla de ruta para obtener una descripción detallada de la sintaxis de la plantilla de ruta.

  • Devuelve la ruta de acceso de la dirección URL /.

  • Genera los valores { controller = Home, action = Index }de ruta .

Los valores de controller y action hacen uso de los valores predeterminados. id no produce ningún valor ya que no hay ningún segmento correspondiente en la ruta URL. / solo coincide si existe una HomeController acción y Index:

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

Usando esta definición de controlador y la plantilla de ruta, la acción HomeController.Index se ejecutaría para cualquiera de las rutas de dirección URL siguientes:

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

La ruta de acceso URL / usa los controladores y Home la acción predeterminados Index de la plantilla de ruta. La ruta URL /Home utliza la acción Index por defecto de la plantilla de ruta.

El método de conveniencia MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Reemplaza a:

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

Importante

El enrutamiento se configura mediante el middleware UseRouting, MapControllerRoute y MapAreaControllerRoute. Para usar controladores:

Enrutamiento convencional

El enrutamiento convencional se usa con controladores y vistas. La ruta default:

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

El ejemplo anterior es un ejemplo de una ruta convencional. Se denomina enrutamiento convencional porque establece una convención para las rutas URL:

  • El primer segmento de la ruta {controller=Home} asigna el nombre de controlador.
  • El segundo segmento, {action=Index}, asigna el nombre de la acción.
  • El tercer segmento, {id?} se usa para un opcional id. El ? en {id?} lo hace opcional. id se usa para asignar a una entidad de modelo.

Con esta default ruta, la ruta de acceso url:

  • /Products/List se asigna a la ProductsController.List acción.
  • /Blog/Article/17 se asigna a BlogController.Article y normalmente enlaza el id parámetro a 17.

Esta asignación:

  • Solo se basa en los nombres de controlador y acción.
  • No se basa en espacios de nombres, ubicaciones de archivos de origen ni parámetros de método.

Utilizar el enrutamiento convencional con la ruta por defecto permite crear la aplicación sin tener que idear un nuevo patrón de URL para cada acción. Para una aplicación con acciones de estilo CRUD , con coherencia para las direcciones URL entre controladores:

  • Ayuda a simplificar el código.
  • Hace que la interfaz de usuario sea más predecible.

Advertencia

El id elemento del código anterior se define como opcional en la plantilla de ruta. Las acciones se pueden ejecutar sin el identificador opcional proporcionado como parte de la dirección URL. Por lo general, cuando id se omite de la dirección URL:

  • id se establece en 0 mediante el enlace de modelos.
  • No se encuentra ninguna entidad en la base de datos que coincida con id == 0.

El enrutamiento de atributos proporciona un control preciso para hacer que el ID sea necesario para algunas acciones y no para otras. Por convención, la documentación incluirá parámetros opcionales como id cuando sea más probable que su uso sea correcto.

La mayoría de las aplicaciones deben elegir un esquema de enrutamiento básico y descriptivo para que las direcciones URL sean legibles y significativas. La ruta convencional predeterminada {controller=Home}/{action=Index}/{id?}:

  • Admite un esquema de enrutamiento básico y descriptivo.
  • Se trata de un punto de partida útil para las aplicaciones basadas en la interfaz de usuario.
  • Es la única plantilla de ruta necesaria para muchas aplicaciones de interfaz de usuario web. Para aplicaciones de interfaz de usuario web de mayor tamaño, a menudo basta con utilizar Areas.

MapControllerRoute y MapAreaRoute :

  • Asignar automáticamente un valor de orden a sus puntos finales en función del orden en que son invocados.

Enrutamiento de puntos de conexión en ASP.NET Core 3.0 y versiones posteriores:

  • No tiene un concepto de rutas.
  • No proporciona garantías de ordenación para la ejecución de extensibilidad, todos los puntos de conexión se procesan a la vez.

Habilite el registro para ver de qué forma las implementaciones de enrutamiento integradas, como Route, coinciden con las solicitudes.

El enrutamiento de atributos se explica más adelante en este documento.

Varias rutas convencionales

Se pueden añadir múltiples rutas convencionales dentro de UseEndpoints añadiendo más llamadas a MapControllerRoute y MapAreaControllerRoute. Hacerlo permite definir múltiples convenciones, o añadir rutas convencionales que se dedican a una acción específica , como:

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

La blog ruta del código anterior es una ruta convencional dedicada. Se denomina ruta convencional dedicada porque:

Como controller y action no aparecen en la plantilla "blog/{*article}" de ruta como parámetros:

  • Solo pueden tener los valores predeterminados { controller = "Blog", action = "Article" }.
  • Esta ruta siempre se asigna a la acción BlogController.Article.

/Blog, /Blog/Article y /Blog/{any-string} son las únicas rutas de dirección URL que coinciden con la ruta del blog.

Ejemplo anterior:

  • blog route tiene una prioridad más alta para las coincidencias que la default ruta porque se agrega primero.
  • Es un ejemplo de enrutamiento de estilo Slug en el que es habitual tener un nombre de artículo como parte de la dirección URL.

Advertencia

En ASP.NET Core 3.0 y versiones posteriores, el enrutamiento no:

  • Defina un concepto denominado ruta. UseRouting agrega coincidencia de rutas a la canalización de middleware. El middleware UseRouting examina el conjunto de puntos finales definidos en la aplicación y selecciona el mejor punto final en función de la solicitud.
  • Proporcione garantías sobre el orden de ejecución de extensibilidad como IRouteConstraint o IActionConstraint.

Consulte Enrutamiento para obtener material de referencia sobre el enrutamiento.

Orden de enrutamiento convencional

El enrutamiento convencional solo coincide con una combinación de acción y controlador que la aplicación define. Con ello se pretende simplificar los casos en que las rutas convencionales se solapan. Al añadir rutas utilizando MapControllerRoute, MapDefaultControllerRoute y MapAreaControllerRoute se asigna automáticamente un valor de orden a sus puntos finales en función del orden en que se invocan. Las coincidencias de una ruta que aparece anteriormente tienen una prioridad más alta. El enrutamiento convencional depende del orden. En general, las rutas con áreas deben colocarse antes, ya que son más específicas que las rutas sin área. Las rutas convencionales dedicadas con parámetros de ruta catch-all, como {*article} , pueden hacer que una ruta sea demasiado expansivas, lo que significa que coincide con las direcciones URL que pretende que coincidan con otras rutas. Coloque las rutas expansivas más adelante en la tabla de rutas para evitar coincidencias expansivas.

Advertencia

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:

  • Una ruta catch-all (por ejemplo, {**slug}")
  • La ruta catch-all causa un error al relacionar solicitudes que sí que debería relacionar.
  • Al quitar otras rutas, la ruta catch-all empieza a funcionar.

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.

Resolución de acciones ambiguas

Cuando dos puntos de conexión coinciden a través del enrutamiento, el enrutamiento debe realizar una de las siguientes acciones:

  • Elija el mejor candidato.
  • Iniciar una excepción.

Por ejemplo:

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

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

El controlador anterior define dos acciones que coinciden con:

  • Ruta de acceso de la URL/Products33/Edit/17
  • Datos de ruta { controller = Products33, action = Edit, id = 17 }.

Este es un patrón típico para los controladores MVC:

  • Edit(int) muestra un formulario para editar un producto.
  • Edit(int, Product) procesa el formulario publicado.

Para resolver la ruta correcta:

  • Edit(int, Product) es seleccionada cuando la solicitud es una POST HTTP.
  • Edit(int) se selecciona cuando el verbo HTTP es cualquier otra cosa. Edit(int) normalmente se llama a través de GET.

HttpPostAttribute, [HttpPost]se proporciona al enrutamiento para que pueda elegir en función del método HTTP de la solicitud. El HttpPostAttribute hace de Edit(int, Product) una mejor combinación que Edit(int).

Es importante comprender el rol de atributos como HttpPostAttribute. Los atributos similares se definen para otros verbos HTTP. En el enrutamiento convencional es común que las acciones utilicen el mismo nombre de acción cuando son parte de un flujo de trabajo. Por ejemplo, vea Examinar los dos métodos de acción Editar.

Si el enrutamiento no puede elegir un mejor candidato, se produce una AmbiguousMatchException excepción , que enumera los varios puntos de conexión coincidentes.

Nombres de ruta convencionales

Las cadenas "blog" y "default" en los siguientes ejemplos son nombres de ruta convencionales:

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

Los nombres de ruta asignan un nombre lógico a la ruta. La ruta con nombre puede utilizarse para la generación de URL. El uso de una ruta con nombre simplifica la creación de URL cuando el orden de las rutas podría complicar la generación de URL. Los nombres de ruta deben ser únicos en toda la aplicación.

Nombres de ruta:

  • No tiene ningún impacto en la coincidencia de direcciones URL ni en el control de las solicitudes.
  • Solo se usan para la generación de direcciones URL.

El concepto de nombre de ruta se representa en el enrutamiento como IEndpointNameMetadata. Los términos nombre de ruta y nombre del punto de conexión:

  • Son intercambiables.
  • El que se usa en la documentación y el código depende de la API que se describe.

Enrutamiento de atributos para REST las API

Las API REST deben usar el enrutamiento mediante atributos para modelar la funcionalidad de la aplicación como un conjunto de recursos donde las operaciones se representan mediante verbos HTTP.

El enrutamiento mediante atributos utiliza un conjunto de atributos para asignar acciones directamente a las plantillas de ruta. El siguiente código StartUp.Configure es típico para una API REST y se utiliza en el siguiente ejemplo:

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

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

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

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

En el código anterior, MapControllers se llama a UseEndpoints para asignar controladores enrutados de atributo.

En el ejemplo siguiente:

  • HomeController coincide con un conjunto de URL similares a las que coincide la ruta convencional {controller=Home}/{action=Index}/{id?} por defecto.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

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

La acción HomeController.Index se ejecuta para cualquiera de las rutas URL /, /Home, /Home/Index o /Home/Index/3.

Este ejemplo resalta una diferencia clave de programación entre el enrutamiento mediante atributos y el enrutamiento convencional. El enrutamiento de atributos requiere más entrada para especificar una ruta. La ruta predeterminada convencional controla las rutas más concisamente. Pero el enrutamiento mediante atributos permite (y requiere) un control preciso de las plantillas de ruta que se aplicarán a cada acción.

Con el enrutamiento de atributos, los nombres de controlador y acción no desempeñan ninguna parte en la que coincide la acción, a menos que se use el reemplazo de tokens . En el ejemplo siguiente se coinciden las mismas direcciones URL que el ejemplo anterior:

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

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

En el código siguiente se usa el reemplazo de tokens para action y controller:

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

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

El siguiente código aplica [Route("[controller]/[action]")] al controlador:

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

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

En el código anterior, las Index plantillas de método deben anteponer / o ~/ a las plantillas de ruta. Las plantillas de ruta aplicadas a una acción que comienzan por / o ~/ no se combinan con las plantillas de ruta que se aplican al controlador.

Consulte Precedencia de la plantilla de ruta para obtener información sobre la selección de plantillas de ruta.

Nombres de enrutamientos reservados

Las siguientes palabras clave son nombres de parámetros de ruta reservados al usar Controladores o Razor páginas:

  • action
  • area
  • controller
  • handler
  • page

El uso page de como parámetro de ruta con enrutamiento de atributos es un error común. Esto da como resultado un comportamiento incoherente y confuso con la generación de direcciones URL.

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

La generación de direcciones URL usa los nombres de parámetros especiales para determinar si una operación de generación de direcciones URL hace referencia a una Razor página o a un controlador.

Las palabras clave siguientes se reservan en el contexto de una vista de Razor o de una página de Razor:

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

Estas palabras clave no deben utilizarse para generaciones de enlaces, parámetros vinculados al modelo o propiedades de nivel superior.

Plantillas de verbo HTTP

ASP.NET Core tiene las siguientes plantillas de verbo HTTP:

Plantillas de ruta

ASP.NET Core tiene las siguientes plantillas de ruta:

Enrutamiento mediante atributos con atributos de verbo Http

Considere el siguiente controlador:

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

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

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

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

En el código anterior:

  • Cada acción contiene el [HttpGet] atributo , que restringe la coincidencia solo con las solicitudes HTTP GET.
  • La GetProduct acción incluye la "{id}" plantilla, por lo tanto id , se anexa a la "api/[controller]" plantilla en el controlador. La plantilla de métodos es "api/[controller]/{id}". Por lo tanto, esta acción solo coincide con las solicitudes GET para el formulario /api/test2/xyz,/api/test2/123/api/test2/{any string} , etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • La GetIntProduct acción contiene la "int/{id:int}" plantilla. La :int parte de la plantilla restringe los id valores de ruta a cadenas que se pueden convertir en un entero. Una solicitud de obtención para /api/test2/int/abc:
    • No coincide con esta acción.
    • Devuelve un error 404 No encontrado.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • La GetInt2Product acción contiene {id} en la plantilla, pero no limita id a los valores que se pueden convertir en un entero. Una solicitud de obtención para /api/test2/int2/abc:
    • Coincide con esta ruta.
    • El enlace de modelos no se puede convertir abc en un entero. El id parámetro del método es entero.
    • Devuelve un error 400 solicitud incorrecta porque el enlace de modelos no pudo convertir abc en un entero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

El enrutamiento por atributos HttpMethodAttribute puede utilizar atributos como HttpPostAttribute, HttpPutAttribute y HttpDeleteAttribute. Todos los atributos del verbo HTTP aceptan una plantilla de ruta. Este ejemplo muestra dos acciones que coinciden con la misma plantilla de ruta:

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

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

Uso de la ruta de acceso url /products3:

  • La MyProductsController.ListProducts acción se ejecuta cuando el verbo HTTP es GET.
  • La MyProductsController.CreateProduct acción se ejecuta cuando el verbo HTTP es POST.

Cuando se construye una API REST, es raro que necesites utilizar [Route(...)] en un método de acción porque la acción acepta todos los métodos HTTP. Es mejor usar más específicos para precisar lo que es compatible con la API. Se espera que los clientes de las API de REST sepan qué rutas y verbos HTTP corresponden a operaciones lógicas específicas.

Las API REST deben usar el enrutamiento mediante atributos para modelar la funcionalidad de la aplicación como un conjunto de recursos donde las operaciones se representan mediante verbos HTTP. Esto significa que muchas operaciones, por ejemplo, GET y POST, del mismo recurso lógico usan la misma dirección URL. El enrutamiento mediante atributos proporciona un nivel de control que es necesario para diseñar cuidadosamente un diseño de puntos de conexión públicos de la API.

Puesto que una ruta de atributo se aplica a una acción específica, es fácil crear parámetros necesarios como parte de la definición de plantilla de ruta. En este ejemplo, se requiere id como parte de la ruta de dirección de URL:

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

La acción Products2ApiController.GetProduct(int):

  • Se ejecuta con la ruta de acceso URL, como /products2/3
  • No se ejecuta con la ruta de acceso URL /products2.

El atributo [Consumes] permite que una acción limite los tipos de contenido de la solicitud compatibles. Para obtener más información, consulte Definir los tipos de contenido de solicitud compatibles con el atributo Consumes.

Consulte Enrutamiento para obtener una descripción completa de las plantillas de ruta y las opciones relacionadas.

Para obtener más información sobre [ApiController], vea Atributo ApiController.

Nombre de ruta

El siguiente código define un nombre de ruta Products_List:

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

Los nombres de ruta se pueden utilizar para generar una dirección URL basada en una ruta específica. Nombres de ruta:

  • No tienen ningún impacto en el comportamiento de coincidencia de URL del enrutamiento.
  • Solo se usan para la generación de direcciones URL.

Los nombres de ruta deben ser únicos en toda la aplicación.

Contrasta el código anterior con la ruta por defecto convencional, que define el parámetro idcomo opcional ({id?}). Esta capacidad de especificar con precisión las API tiene sus ventajas, como permitir que /products y /products/5 se envíen a acciones diferentes.

Combinación de rutas de atributo

Para que el enrutamiento mediante atributos sea menos repetitivo, los atributos de ruta del controlador se combinan con los atributos de ruta de las acciones individuales. Las plantillas de ruta definidas en el controlador se anteponen a las plantillas de ruta de las acciones. La colocación de un atributo de ruta en el controlador hace que todas las acciones del controlador usen el enrutamiento mediante atributos.

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

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

En el ejemplo anterior:

  • La ruta URL /products puede coincidir con ProductsApi.ListProducts
  • La ruta URL /products/5 puede coincidir con ProductsApi.GetProduct(int).

Ambas acciones sólo coinciden con HTTP GETporque están marcadas con el atributo [HttpGet].

Las plantillas de ruta aplicadas a una acción que comienzan por / o ~/ no se combinan con las plantillas de ruta que se aplican al controlador. El siguiente ejemplo coincide con un conjunto de rutas URL similares a la ruta por defecto.

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

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

En la tabla siguiente se explican los [Route] atributos del código anterior:

Atributo Combina con [Route("Home")] Define la plantilla de ruta
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] No ""
[Route("About")] "Home/About"

Orden de ruta de atributo

El enrutamiento crea un árbol y coincide con todos los puntos de conexión simultáneamente:

  • Las entradas de ruta se comportan como si se colocara en una ordenación ideal.
  • Las rutas más específicas tienen la oportunidad de ejecutarse antes que las rutas más generales.

Por ejemplo, una ruta de atributos como blog/search/{topic} es más específica que una ruta de atributos como blog/{*article}. La blog/search/{topic} ruta tiene una prioridad más alta, de forma predeterminada, porque es más específica. En el enrutamiento convencional, el desarrollador es responsable de colocar las rutas en el orden deseado.

Las rutas de atributo pueden configurar un pedido mediante la Order propiedad . Todos los atributos de ruta proporcionados por el marco incluyen Order . Las rutas se procesan de acuerdo con el orden ascendente de la propiedad Order. El orden predeterminado es 0. El establecimiento de una ruta Order = -1 utilizando se ejecuta antes que las rutas que no establecen un orden. Si una ruta se configura con Order = 1, se ejecutará después del orden de rutas predeterminado.

Evite depender de Order. Si su espacio de direcciones URL requiere unos valores de orden explícitos para un enrutamiento correcto, es probable que también sea confuso para los clientes. Por lo general, el enrutamiento mediante atributos seleccionará la ruta correcta con la coincidencia de dirección URL. Si el orden predeterminado que se usa para la generación de direcciones URL no funciona, normalmente es más sencillo utilizar el nombre de ruta como una invalidación que aplicar la propiedad Order.

Tenga en cuenta los dos controladores siguientes que definen la ruta que coincide /homecon:

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

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

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

La solicitud de /home con el código anterior lanza una excepción similar a la siguiente:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

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

Agregar Order a uno de los atributos de ruta resuelve la ambigüedad:

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

Con el código anterior, /home ejecuta el punto de HomeController.Index conexión. Para llegar a MyDemoController.MyIndex, solicite /home/MyIndex. Nota:

  • El código anterior es un ejemplo o un diseño de enrutamiento deficiente. Se usó para ilustrar la Order propiedad.
  • La Order propiedad solo resuelve la ambigüedad, que no se puede hacer coincidir con esa plantilla. Sería mejor quitar la [Route("Home")] plantilla.

Consulte Razor Enrutamiento de páginas y convenciones de aplicación: Orden de ruta para obtener información sobre el orden de ruta con Razor Pages.

En algunos casos, se devuelve un error HTTP 500 con rutas ambiguas. Use el registro para ver qué puntos de conexión provocaron AmbiguousMatchException.

Reemplazo de tokens en plantillas de ruta [controller], [action], [area]

Para mayor comodidad, las rutas de atributo admiten reemplazo de token. Para ello, incluyen un token entre corchetes ([, ]). Los tokens [action], [area] y [controller] se reemplazan con los valores del nombre de la acción, el nombre del área y el nombre del controlador de la acción donde se define la ruta:

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


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

En el código anterior:

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

El reemplazo del token se produce como último paso de la creación de las rutas de atributo. El ejemplo anterior se comporta igual que el código siguiente:

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

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

Si está leyendo este contenido en un idioma que no es el inglés, díganos si le gustaría que los comentarios sobre el código estuviesen en escritos en su idioma. Puede hacerlo en este artículo de discusión de GitHub.

Las rutas de atributo también se pueden combinar con la herencia. Esto es potente combinado con la sustitución de fichas. El reemplazo de token también se aplica a los nombres de ruta definidos por las rutas de atributo. [Route("[controller]/[action]", Name="[controller]_[action]")] genera un nombre de ruta único para cada acción:

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

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

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

Para que el delimitador de reemplazo de token [ o ] coincida, repita el carácter ([[ o ]]) para usarlo como secuencia de escape.

Usar un transformador de parámetro para personalizar el reemplazo de tokens

El reemplazo de tokens se puede personalizarse mediante un transformador de parámetro. Un transformador de parámetro implementa IOutboundParameterTransformer y transforma el valor de parámetros. Por ejemplo, un transformador de parámetros SlugifyParameterTransformer personalizado cambia el valor de ruta SubscriptionManagement a subscription-management:

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

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

RouteTokenTransformerConvention es una convención de modelo de aplicación que:

  • Aplica un transformador de parámetro a todas las rutas de atributo en una aplicación.
  • Personaliza los valores de token de ruta de atributo que se reemplazan.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

El método ListAll anterior coincide con /subscription-management/list-all.

RouteTokenTransformerConvention está registrado como una opción en ConfigureServices.

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

Consulte la documentación web de MDN en Slug para obtener la definición de Slug.

Advertencia

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.

Varias rutas de atributos

El enrutamiento mediante atributos permite definir varias rutas que llegan a la misma acción. El uso más común de esto es imitar el comportamiento de la ruta convencional predeterminada tal como se muestra en el ejemplo siguiente:

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

Poner múltiples atributos de ruta en el controlador significa que cada uno se combina con cada uno de los atributos de ruta en los métodos de acción:

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

Todas las restricciones de ruta de verbo HTTP implementan IActionConstraint.

Cuando se colocan varios atributos de ruta que implementan IActionConstraint en una acción:

  • Cada restricción de acción se combina con la plantilla de ruta aplicada al controlador.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

El uso de varias rutas en acciones puede parecer útil y eficaz, es mejor mantener el espacio de direcciones URL de la aplicación básico y bien definido. Utilice varias rutas en acciones solo cuando sea necesario, por ejemplo, para admitir a clientes existentes.

Especificación de parámetros opcionales de ruta de atributo, valores predeterminados y restricciones

Las rutas de atributo admiten la misma sintaxis en línea que las rutas convencionales para especificar parámetros opcionales, valores predeterminados y restricciones.

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

En el código anterior, [HttpPost("product14/{id:int}")] aplica una restricción de ruta. La Products14Controller.ShowProduct acción solo coincide con las rutas de dirección URL, como /product14/3. La parte {id:int} de la plantilla de ruta restringe ese segmento a solo enteros.

Consulte Referencia de plantilla de ruta para obtener una descripción detallada de la sintaxis de la plantilla de ruta.

Atributos de ruta personalizados mediante IRouteTemplateProvider

Todos los atributos de ruta implementan IRouteTemplateProvider. El entorno de ejecución de ASP.NET Core:

  • Busca atributos en clases de controlador y métodos de acción cuando se inicia la aplicación.
  • Utiliza los atributos que implementan IRouteTemplateProvider para construir el conjunto inicial de rutas.

Implemente IRouteTemplateProvider para definir atributos de ruta personalizados. Cada IRouteTemplateProvider le permite definir una única ruta con una plantilla de ruta, un orden y un nombre personalizados:

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

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

El Getmétodo anterior devuelve Order = 2, Template = api/MyTestApi.

Uso del modelo de aplicación para personalizar las rutas de atributo

El modelo de aplicación:

  • Es un modelo de objetos creado en el inicio.
  • Contiene todos los metadatos usados por ASP.NET Core para enrutar y ejecutar las acciones en una aplicación.

El modelo de aplicación incluye todos los datos recogidos de los atributos de ruta. La implementación proporciona los datos de los atributos de IRouteTemplateProvider ruta. Convenciones:

  • Se puede escribir para modificar el modelo de aplicación para personalizar el comportamiento del enrutamiento.
  • Se leen en el inicio de la aplicación.

Esta sección muestra un ejemplo básico de personalización del enrutamiento utilizando el modelo de aplicación. El código siguiente hace que las rutas se alinean aproximadamente con la estructura de carpetas del proyecto.

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

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

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

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

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

El código siguiente impide que la namespace convención se aplique a los controladores que están enrutados por atributos:

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

Por ejemplo, el controlador siguiente no usa NamespaceRoutingConvention:

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

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

El método NamespaceRoutingConvention.Apply realiza las acciones siguientes:

  • No hace nada si el controlador está enrutado por atributos.
  • Establece la plantilla de controlador basada en namespace, con la base namespace eliminada.

El NamespaceRoutingConvention puede aplicarse en Startup.ConfigureServices:

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

        public IConfiguration Configuration { get; }

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

Por ejemplo, considere el código siguiente:

using Microsoft.AspNetCore.Mvc;

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

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

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

En el código anterior:

  • La base namespace es My.Application.
  • El nombre completo del controlador anterior es My.Application.Admin.Controllers.UsersController.
  • NamespaceRoutingConvention establece la plantilla de controladores en Admin/Controllers/Users/[action]/{id?.

NamespaceRoutingConvention También se puede aplicar como un atributo en un controlador:

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

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

Enrutamiento mixto: enrutamiento mediante atributos frente a enrutamiento convencional

Las aplicaciones ASP.NET Core pueden mezclar el uso del enrutamiento convencional y el enrutamiento por atributos. Es habitual usar las rutas convencionales para controladores que sirven páginas HTML para los exploradores, y el enrutamiento mediante atributos para los controladores que sirven las API de REST.

Las acciones se enrutan bien mediante convención o bien mediante atributos. Colocar una ruta en el controlador o la acción hace que se enrute mediante atributos. Las acciones que definen rutas de atributo no se pueden alcanzar a través de las rutas convencionales y viceversa. Cualquier atributo de ruta en el controlador hace que todas las acciones del controlador se enruten mediante atributos.

El enrutamiento de atributos y el enrutamiento convencional usan el mismo motor de enrutamiento.

Generación de direcciones URL y valores ambientales

Las aplicaciones pueden utilizar las funciones de generación de URL de enrutamiento para generar enlaces URL a las acciones. La generación de URL elimina la codificación de las mismas, lo que hace que el código sea más robusto y fácil de mantener.Generating URLs eliminates hardcoding URLs, making code more robust and maintainable. Esta sección se centra en las características de generación de direcciones URL proporcionadas por MVC y solo aborda los conceptos básicos de su funcionamiento. Consulte Enrutamiento para obtener una descripción detallada de la generación de direcciones URL.

La interfaz IUrlHelper es la pieza subyacente de la infraestructura entre MVC y el enrutamiento para la generación de direcciones URL. Encontrará una instancia de IUrlHelper disponible a través de la propiedad Url en los controladores, las vistas y los componentes de vista.

En el siguiente ejemplo, la interfaz IUrlHelper se utiliza a través de la propiedad Controller.Url para generar una URL a otra acción.

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

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

Si la aplicación está usando la ruta convencional predeterminada, el valor de la variable url será la cadena de ruta de dirección URL /UrlGeneration/Destination. Esta ruta de acceso URL se crea mediante el enrutamiento mediante la combinación de:

  • Los valores de ruta de la solicitud actual, que se denominan valores de ambiente.
  • Los valores pasados a Url.Action y sustituyendo esos valores a la plantilla de ruta:
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

El valor de cada uno de los parámetros de ruta incluidos en la plantilla de ruta se sustituye por nombres que coincidan con los valores y los valores de ambiente. Un parámetro de ruta que no tiene un valor puede:

  • Use un valor predeterminado si tiene uno.
  • Se omitirá si es opcional. Por ejemplo, el id de la plantilla de ruta {controller}/{action}/{id?}.

La generación de URL falla si algún parámetro de ruta requerido no tiene un valor correspondiente. Si se produce un error en la generación de direcciones URL para una ruta, se prueba con la ruta siguiente hasta que se hayan probado todas las rutas o se encuentra una coincidencia.

En el ejemplo anterior de Url.Action supone el enrutamiento convencional. La generación de URL funciona de forma similar al enrutamiento de atributos, aunque los conceptos son diferentes. Con enrutamiento convencional:

  • Los valores de ruta se usan para expandir una plantilla.
  • Los valores de ruta de controller y action normalmente aparecen en esa plantilla. Esto funciona porque las direcciones URL coincidentes con el enrutamiento cumplen una convención.

En el ejemplo siguiente se usa el enrutamiento de atributos:

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

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

La Source acción del código anterior genera custom/url/to/destination.

LinkGeneratorse agregó en ASP.NET Core 3.0 como alternativa a IUrlHelper. LinkGenerator ofrece una funcionalidad similar pero más flexible. Cada método de IUrlHelper tiene también una familia de métodos correspondiente.LinkGenerator

Generación de direcciones URL por nombre de acción

Url.Action, LinkGenerator.GetPathByAction y todas las sobrecargas relacionadas están diseñadas para generar el punto de conexión de destino especificando un nombre de controlador y un nombre de acción.

Cuando se usa Url.Action, el tiempo de ejecución proporciona los valores de ruta actuales para controller y action:

  • El valor de controller y action forman parte de ambos valores de ambiente. El método Url.Action siempre utiliza los valores actuales de action y controller y genera una ruta de dirección URL que dirige a la acción actual.

El enrutamiento intenta utilizar los valores del entorno para rellenar la información que no se proporcionó al generar una URL. Considere una ruta como {a}/{b}/{c}/{d} con los valores { a = Alice, b = Bob, c = Carol, d = David }de ambiente:

  • El enrutamiento tiene suficiente información para generar una dirección URL sin valores adicionales.
  • El enrutamiento tiene suficiente información porque todos los parámetros de ruta tienen un valor.

Si se añade el valor { d = Donovan }:

  • El valor { d = David } se omite.
  • La ruta de acceso URL generada es Alice/Bob/Carol/Donovan.

Advertencia: Las rutas URL son jerárquicas. En el ejemplo anterior, si se agrega el valor { c = Cheryl }:

  • Ambos valores { c = Carol, d = David } se omiten.
  • Ya no hay un valor para d y se produce un error en la generación de direcciones URL.
  • Los valores deseados de c y d deben especificarse para generar una dirección URL.

Es posible que se produzca este problema con la ruta {controller}/{action}/{id?} predeterminada. Este problema es poco frecuente en la práctica porque Url.Action siempre especifica explícitamente un controller valor y action .

Varias sobrecargas de Url.Action toman un objeto de valores de ruta para proporcionar valores para parámetros de ruta distintos de controller y action. El objeto de valores de ruta se usa con frecuencia con id. Por ejemplo: Url.Action("Buy", "Products", new { id = 17 }). Objeto de valores de ruta:

  • Por convención, normalmente es un objeto de tipo anónimo.
  • Puede ser IDictionary<> o POCO).

Los valores de ruta adicionales que no coinciden con los parámetros de ruta se colocan en la cadena de consulta.

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

El código anterior genera /Products/Buy/17?color=red.

El código siguiente genera una dirección URL absoluta:

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

Para crear una dirección URL absoluta, use una de las siguientes opciones:

  • Una sobrecarga que acepta un objeto protocol. Por ejemplo, el código anterior.
  • LinkGenerator.GetUriByAction, que genera URI absolutos de forma predeterminada.

Generación de direcciones URL por ruta

El código anterior muestra la generación de una URL pasando el controlador y el nombre de la acción. IUrlHelper también proporciona la familia de métodos Url.RouteUrl. Estos métodos son similares a Url.Action, pero no copian los valores actuales de action y a los valores de la ruta controller. El uso más común de Url.RouteUrl:

  • Especifica un nombre de ruta para generar la dirección URL.
  • Por lo general, no especifica un nombre de controlador o acción.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

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

El siguiente Razor archivo genera un vínculo HTML a Destination_Route:

<h1>Test Links</h1>

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

Generar direcciones URL en HTML y Razor

IHtmlHelper proporciona los métodos HtmlHelperHtml.BeginForm y Html.ActionLink para generar lo elementos <form> y <a> respectivamente. Estos métodos utilizan el método Url.Action para generar una URL y aceptan argumentos similares. Los métodos Url.RouteUrl complementarios de HtmlHelper son Html.BeginRouteForm y Html.RouteLink, cuya funcionalidad es similar.

Las TagHelper generan direcciones URL a través de la TagHelper form y la TagHelper <a>. Ambos usan IUrlHelper para su implementación. Consulte Asistentes de etiquetas en formularios para obtener más información.

Dentro de las vistas, IUrlHelper está disponible a través de la propiedad Url para una generación de direcciones URL ad hoc no cubierta por los pasos anteriores.

Generación de direcciones URL en los resultados de la acción

En los ejemplos anteriores se muestra el uso IUrlHelper de en un controlador. El uso más común en un controlador es generar una URL como parte del resultado de una acción.

Las clases base ControllerBase y Controller proporcionan métodos de conveniencia para los resultados de acción que hacen referencia a otra acción. Un uso típico es redirigir después de aceptar la entrada del usuario:

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

Los métodos de fábrica de resultados de acciones como RedirectToAction y CreatedAtAction siguen un patrón similar al de los métodos en IUrlHelper.

Caso especial para rutas convencionales dedicadas

El enrutamiento convencional puede utilizar un tipo especial de definición de ruta denominada ruta convencional dedicada. En el ejemplo siguiente, la ruta llamada blog es una ruta convencional dedicada:

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

Con las definiciones de ruta anteriores, Url.Action("Index", "Home") genera la ruta / de acceso url mediante la default ruta, pero ¿por qué? Se puede suponer que los valores de ruta { controller = Home, action = Index } son suficientes para generar una dirección URL utilizando blog, con el resultado /blog?action=Index&controller=Home.

Las rutas convencionales dedicadas se basan en un comportamiento especial de los valores predeterminados que no tienen un parámetro de ruta correspondiente que impida que la ruta sea demasiado expansiva con la generación de direcciones URL. En este caso, los valores predeterminados son { controller = Blog, action = Article }, y ni controller ni action aparecen como un parámetro de ruta. Cuando el enrutamiento realiza la generación de direcciones URL, los valores proporcionados deben coincidir con los valores predeterminados. La generación de direcciones URL con blog producirá un error porque los valores { controller = Home, action = Index } no coinciden con { controller = Blog, action = Article }. Después, el enrutamiento vuelve para probar default, operación que se realiza correctamente.

Áreas

Las áreas son una característica MVC utilizada para organizar funcionalidades relacionadas en un grupo como separadas:

  • Espacio de nombres de enrutamiento para acciones del controlador.
  • Estructura de carpetas para vistas.

El uso de áreas permite que una aplicación tenga varios controladores con el mismo nombre, siempre que tengan áreas diferentes. El uso de áreas crea una jerarquía para el enrutamiento mediante la adición de otro parámetro de ruta, area, a controller y action. En esta sección se describe cómo interactúa el enrutamiento con las áreas. Consulte Áreas para obtener más información sobre cómo se utilizan las áreas con las vistas.

En el ejemplo siguiente se configura MVC para usar la ruta predeterminada convencional y una arearuta de áreaarea para un área denominada Blog:

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

En el código anterior, MapAreaControllerRoute se llama a para crear ."blog_route" El segundo parámetro, "Blog", es el nombre del área.

Cuando coincide con una ruta de acceso de dirección URL como /Manage/Users/AddUser, la "blog_route" ruta genera los valores { area = Blog, controller = Users, action = AddUser } de ruta . El area valor de ruta se genera con un valor predeterminado para area. La ruta creada por MapAreaControllerRoute es equivalente a la siguiente:

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

MapAreaControllerRoute utiliza el nombre de área proporcionado, que en este caso es Blog, para crear una ruta con un valor predeterminado y una restricción para area. El valor predeterminado garantiza que la ruta siempre produce { area = Blog, ... }; la restricción requiere el valor { area = Blog, ... } para la generación de la dirección URL.

El enrutamiento convencional depende del orden. En general, las rutas con áreas deben colocarse antes, ya que son más específicas que las rutas sin área.

Utilizando el ejemplo anterior, los valores de ruta { area = Blog, controller = Users, action = AddUser } coinciden con la siguiente acción:

using Microsoft.AspNetCore.Mvc;

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

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

El atributo [Área] es el que indica que un controlador forma parte de un área. Este controlador está en el Blog área. Los controladores sin un atributo [Area] no son miembros de ningún área y no coincidirán cuando el enrutamiento proporcione el valor de ruta area. En el ejemplo siguiente, solo el primer controlador enumerado puede coincidir con los valores de ruta { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

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

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

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

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

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

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

El espacio de nombres de cada controlador se muestra aquí por integridad. Si los controladores anteriores usaban el mismo espacio de nombres, se generaría un error del compilador. Los espacios de nombres de clase no tienen ningún efecto en el enrutamiento de MVC.

Los dos primeros controladores son miembros de las áreas y solo coinciden cuando el valor de ruta area proporciona su respectivo nombre de área. El tercer controlador no es miembro de ningún área y solo puede coincidir cuando el enrutamiento no proporciona ningún valor para area.

En términos de búsqueda de coincidencias de ningún valor, la ausencia del valor area es igual que si el valor de area fuese null o una cadena vacía.

Al ejecutar una acción en un área, el valor de ruta para area estará disponible como un valor de ambiente para que el enrutamiento pueda usarlo en la generación de direcciones URL. Esto significa que, de forma predeterminada, las áreas actúan de forma adhesiva para la generación de direcciones URL, tal como se muestra en el ejemplo siguiente.

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

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

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

El siguiente código genera una URL para /Zebra/Users/AddUser:

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

Definición de acción

Los métodos públicos de un controlador, excepto aquellos con el atributo NonAction, son acciones.

Código de ejemplo

Diagnóstico de depuración

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