Enrutar a acciones de controlador de ASP.NET Core

Por Ryan Nowak, Kirk Larkin y Rick Anderson

ASP.NET Core controladores usan el middleware de enrutamiento para hacer coincidir las direcciones 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 comparan las rutas de dirección URL con las acciones.
  • Se usan para generar direcciones URL para vínculos. Los vínculos generados normalmente se devuelven en respuestas.

Las acciones se enrutadas convencionalmente o enrutadas por atributos. La colocación de una ruta en el controlador o la acción hace que se enruta por atributos. Consulte Enrutamiento mixto para obtener más información.

Este documento:

  • Explica las interacciones entre MVC y el enrutamiento:
    • Cómo usan las aplicaciones MVC típicas las características de enrutamiento.
    • Abarca ambos:
      • Enrutamiento convencional que normalmente se usa con controladores y vistas.
      • Enrutamiento de atributos usado con las API REST. Si está interesado principalmente en el enrutamiento de las API REST, vaya a la sección Enrutamiento de atributos para las API REST.
    • Consulte Enrutamiento para obtener detalles de enrutamiento avanzados.
  • Hace referencia al sistema de enrutamiento predeterminado denominado enrutamiento de puntos de conexión. Es posible usar controladores con la versión anterior del 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 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 sola ruta. La ruta única se denomina default ruta. La mayoría de las aplicaciones con controladores y vistas usan una plantilla de ruta similar a la default ruta. Las API REST 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 dirección URL como /Products/Details/5

  • Extrae los valores de ruta { controller = Products, action = Details, id = 5 } 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 parámetro en id 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 ? carácter de define como {id?} id 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.

  • Coincide con la ruta de acceso url / .

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

Los valores de controller y hacen uso de los valores action predeterminados. id no genera un valor porque no hay ningún segmento correspondiente en la ruta de acceso url. / solo coincide si existe una HomeController acción Index y :

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

Con la definición del controlador anterior y la plantilla de ruta, la HomeController.Index acción se ejecuta para las siguientes rutas de dirección URL:

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

La ruta de dirección URL / usa la acción y los controladores predeterminados de la plantilla de Home Index ruta. La ruta de acceso URL /Home usa la acción predeterminada de la plantilla de Index ruta.

El método de conveniencia MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Reemplaza:

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

Importante

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

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 anterior es un ejemplo de una ruta convencional. Se denomina enrutamiento convencional porque establece una convención para las rutas de dirección URL:

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

Con esta default ruta, la ruta de dirección URL:

  • /Products/List se asigna a la ProductsController.List acción.
  • /Blog/Article/17 se asigna BlogController.Article a y normalmente el modelo enlaza el parámetro a id 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.

El uso del enrutamiento convencional con la ruta predeterminada permite crear la aplicación sin tener que crear un nuevo patrón de dirección URL para cada acción. Para una aplicación con acciones de estilo CRUD, tener coherencia para las direcciones URL entre controladores:

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

Advertencia

En el código anterior, la plantilla de id ruta define como opcional. 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 por 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 que el identificador sea necesario para algunas acciones y no para otras. Por convención, la documentación incluye parámetros opcionales como cuando es probable que id aparezcan en el uso 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 más grandes, otra ruta que usa Áreas suele ser todo lo que se necesita.

MapControllerRoute y MapAreaRoute :

  • Asigne automáticamente un valor de pedido a sus puntos de conexión en función del orden en que se invoque.

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 agregar varias rutas convencionales dentro UseEndpoints agregando más llamadas a y MapControllerRoute MapAreaControllerRoute . Esto permite definir varias convenciones o agregar rutas convencionales dedicadas 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:

Dado controller que y no aparecen en la plantilla de ruta como action "blog/{*article}" 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 son las /Blog/{any-string} únicas rutas de dirección URL que coinciden con la ruta de 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 Lento, donde 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 examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia de punto de conexión UseRouting 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 definida por la aplicación. Esto está pensado para simplificar los casos en los que las rutas convencionales se superponen. Agregar rutas mediante , y asignar automáticamente un valor de pedido a sus puntos de conexión MapControllerRoute en función del orden en que se MapDefaultControllerRoute MapAreaControllerRoute invocan. Las coincidencias de una ruta que aparece anteriormente tienen una prioridad mayor. 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 un área. Las rutas convencionales dedicadas con parámetros de ruta catch-all como pueden hacer que una ruta sea demasiado expansión, lo que significa que coincide con las direcciones URL que se pretenden que coincidan con {*article} otras rutas. Coloque las rutas expansiones más adelante en la tabla de rutas para evitar coincidencias expansiones.

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:

  • Ruta de acceso url /Products33/Edit/17
  • Enrutar datos { 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) se selecciona cuando la solicitud es POST HTTP.
  • Edit(int) se selecciona cuando el verbo HTTP es cualquier otra cosa. Edit(int) normalmente se llama a través de GET .

, se proporciona al enrutamiento para que pueda elegir según HttpPostAttribute el método HTTP de la [HttpPost] solicitud. hace HttpPostAttribute una mejor coincidencia que Edit(int, Product) Edit(int) .

Es importante comprender el rol de atributos como HttpPostAttribute . Se definen atributos similares para otros verbos HTTP. En el enrutamiento convencional,es habitual que las acciones usen el mismo nombre de acción cuando forman parte de un flujo de trabajo mostrar formulario, enviar formulario. Por ejemplo, vea Examinar los dos métodos de acción Edit.

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

Nombres de ruta convencionales

Las cadenas "blog" y en los ejemplos siguientes son nombres de ruta "default" 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 le dan un nombre lógico a la ruta. La ruta con nombre se puede usar para la generación de direcciones URL. El uso de una ruta con nombre simplifica la creación de direcciones URL cuando el orden de las rutas podría complicar la generación de direcciones 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 las API REST

Las API REST deben usar el enrutamiento de 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 código siguiente es típico de una API REST y se usa en el ejemplo siguiente:

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, se MapControllers llama a dentro de para asignar controladores UseEndpoints enrutados de atributos.

En el ejemplo siguiente:

  • HomeController coincide con un conjunto de direcciones URL similares a las que coincide la ruta convencional {controller=Home}/{action=Index}/{id?} predeterminada.
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 HomeController.Index acción se ejecuta para cualquiera de las rutas de acceso de dirección URL , / , o /Home /Home/Index /Home/Index/3 .

En este ejemplo se resalta una diferencia de programación clave entre el enrutamiento de atributos y el enrutamiento convencional. El enrutamiento de atributos requiere más entrada para especificar una ruta. La ruta predeterminada convencional controla las rutas de forma más concisa. Sin embargo, el enrutamiento de atributos permite y requiere un control preciso de qué plantillas de ruta se aplican a cada acción.

Con el enrutamiento de atributos, los nombres de controlador y acción no desempeñan ninguna parte en la que la acción coincide, a menos que se utilice el reemplazo de tokens. En el ejemplo siguiente se coinciden las mismas direcciones URL que en 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 action para 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 código siguiente se 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 plantillas Index 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 Prioridad de la plantilla de ruta para obtener información sobre la selección de la plantilla de ruta.

Nombres de enrutamientos reservados

Las palabras clave siguientes son nombres de parámetros de ruta reservados cuando se usan controladores o Razor páginas:

  • action
  • area
  • controller
  • handler
  • page

Usar page como parámetro de ruta con enrutamiento de atributos es un error común. Si lo hace, se produce 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ámetro 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 usarse para las generaciones de vínculos, los parámetros enlazados al modelo ni las 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 de atributos con atributos de verbo Http

Tenga en cuenta el controlador siguiente:

[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 atributo , que restringe la coincidencia solo con [HttpGet] las solicitudes HTTP GET.
  • La GetProduct acción incluye la "{id}" plantilla, por lo que se anexa a id la plantilla en el "api/[controller]" 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 parte de la plantilla restringe los valores de ruta :int id a cadenas que se pueden convertir en un entero. Una solicitud GET a /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 acción contiene en la plantilla, pero no restringe a GetInt2Product los valores que se pueden convertir en un {id} id entero. Una solicitud GET a /api/test2/int2/abc :
    • Coincide con esta ruta.
    • El enlace de modelos no se abc puede convertir en un entero. El id parámetro del método es integer.
    • Devuelve una solicitud 400 bad porque el enlace del modelo no se pudo convertir en un abc entero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

El enrutamiento de atributos HttpMethodAttribute puede usar atributos como , y HttpPostAttribute HttpPutAttribute HttpDeleteAttribute . Todos los atributos del verbo HTTP aceptan una plantilla de ruta. En el ejemplo siguiente se muestran 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);
    }
}

Mediante 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 .

Al compilar una API REST, es poco frecuente que necesite usar en un método de acción porque la acción [Route(...)] acepta todos los métodos HTTP. Es mejor usar el atributo de verbo HTTP más específico para ser preciso sobre lo que admite la API. Se espera que los clientes de API de REST sepan qué rutas y verbos HTTP se asignan a determinadas operaciones lógicas.

Las API REST deben usar el enrutamiento de 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 en el 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 el ejemplo siguiente, id se requiere como parte de la ruta de acceso url:

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

La Products2ApiController.GetProduct(int) acción:

  • Se ejecuta con una ruta de dirección 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, vea Definir tipos de contenido de solicitud admitidos 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 ApiController attribute.

Nombre de ruta

El código siguiente define un nombre de ruta de 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 tiene ningún impacto en el comportamiento de coincidencia de direcciones 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.

Compare el código anterior con la ruta predeterminada convencional, que define id el parámetro como opcional ( {id?} ). La capacidad de especificar con precisión las API tiene ventajas, como permitir y /products /products/5 enviar a diferentes acciones.

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 de acceso url /products puede coincidir ProductsApi.ListProducts
  • La ruta de acceso url /products/5 puede coincidir con ProductsApi.GetProduct(int) .

Ambas acciones solo coinciden con HTTP GET porque están marcadas con el [HttpGet] atributo .

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. En el ejemplo siguiente se coincide con un conjunto de rutas de dirección URL similares a la ruta predeterminada.

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

Atributo Se 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 colocaran en un orden ideal.
  • Las rutas más específicas tienen la oportunidad de ejecutarse antes que las rutas más generales.

Por ejemplo, una ruta de atributo como blog/search/{topic} es más específica que una ruta de atributo como blog/{*article} . La blog/search/{topic} ruta tiene mayor prioridad, de forma predeterminada, porque es más específica. Mediante 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 mediante se ejecuta antes de las rutas que no establecen un orden. El establecimiento de una ruta mediante Order = 1 se ejecuta después de la ordenación de rutas predeterminada.

Evite depender de Order . Si el espacio de direcciones URL de una aplicación requiere valores de orden explícitos para enrutar correctamente, es probable que también sea confuso para los clientes. En general, el enrutamiento de atributos selecciona la ruta correcta con coincidencia de direcciones URL. Si el orden predeterminado usado para la generación de direcciones URL no funciona, el uso de un nombre de ruta como invalidación suele ser más sencillo que aplicar la Order propiedad .

Tenga en cuenta los dos controladores siguientes que definen la coincidencia de /home rutas :

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 /home con el código anterior produce 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, no se puede hacer coincidir esa plantilla. Sería mejor quitar la [Route("Home")] plantilla.

Consulte Razor Pages route and app conventions: Route order (Convenciones de aplicación y ruta de Pages: orden de ruta) para obtener información sobre el orden de las rutas 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 .

Sustitución de tokens en plantillas de ruta [controlador], [acción], [área]

Para mayor comodidad, las rutas de atributo admiten el reemplazo de tokens al encier un token entre corchetes ( [ , ] ). Los tokens , y se reemplazan por los valores del nombre de acción, el nombre del área y el nombre del controlador de la acción donde [action] [area] se define la [controller] 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();
}
  • Partidos /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Partidos /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 esto en un idioma distinto del inglés, háganoslo saber en este problema de discusión de GitHub si desea ver los comentarios de código en su idioma nativo.

Las rutas de atributo también se pueden combinar con la herencia. Esto es eficaz combinado con el reemplazo de tokens. 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 SlugifyParameterTransformer de parámetros personalizado cambia el valor de ruta a SubscriptionManagement 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 anterior ListAll coincide con /subscription-management/list-all .

se RouteTokenTransformerConvention registra como una 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 atributo

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();
    }

Colocar varios atributos de ruta en el controlador significa que cada uno de ellos 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 del verbo HTTP implementan IActionConstraint .

Cuando se colocan varios atributos de IActionConstraint ruta que implementan 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. Use varias rutas en acciones solo cuando sea necesario, por ejemplo, para admitir 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 rutas de dirección URL como /product14/3 . La parte de la plantilla {id:int} de ruta restringe ese segmento solo a 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 ASP.NET Core de ejecución:

  • Busca atributos en las clases de controlador y los métodos de acción cuando se inicia la aplicación.
  • Usa los atributos que implementan IRouteTemplateProvider para compilar 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 método anterior Get devuelve Order = 2, Template = api/MyTestApi .

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

El modelo de aplicación:

  • Es un modelo de objetos creado al iniciarse 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 recopilados 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.

En esta sección se muestra un ejemplo básico de personalización del enrutamiento mediante el modelo de aplicación. El código siguiente hace que las rutas se alinee 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 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 controladores basada en namespace , con la base namespace quitada.

Se NamespaceRoutingConvention puede aplicar 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 siguiente controlador:

using Microsoft.AspNetCore.Mvc;

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

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

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

En el código anterior:

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

También NamespaceRoutingConvention se puede aplicar como 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

ASP.NET Core aplicaciones pueden mezclar el uso del enrutamiento convencional y el enrutamiento de 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 _ route del controlador hace que _ todas las acciones * del atributo del controlador se enrute.

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

Generación de direcciones URL y valores de ambiente

Las aplicaciones pueden usar características de generación de direcciones URL de enrutamiento para generar vínculos url a acciones. La generación de direcciones URL elimina las direcciones URL de codificación rígida, lo que hace que el código sea más sólido 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 cubre los aspectos básicos de cómo funciona la generación de direcciones URL. Consulte Enrutamiento para obtener una descripción detallada de la generación de direcciones URL.

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

En el ejemplo siguiente, la IUrlHelper interfaz se usa a través de la propiedad para generar una dirección URL a otra Controller.Url 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 usa la ruta convencional predeterminada, el valor de la variable es la cadena de ruta url de dirección URL /UrlGeneration/Destination . Esta ruta de dirección 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 en 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 omite si es opcional. Por ejemplo, de id la plantilla de ruta {controller}/{action}/{id?} .

Se produce un error en la generación de direcciones URL si algún parámetro de ruta necesario 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 se Url.Action da por supuesto el enrutamiento convencional. La generación de direcciones URL funciona de forma similar con el 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 para controller y normalmente aparecen en esa action 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 también tiene una familia de métodos LinkGenerator correspondiente.

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

Url.Action, LinkGenerator.GetPathByActiony 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 , el tiempo de ejecución proporciona los valores de ruta Url.Action controller action actuales para y :

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

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

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

Si se agrega { d = Donovan } el valor:

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

Advertencia: las rutas de dirección URL son jerárquicas. En el ejemplo anterior, si se agrega { c = Cheryl } el valor:

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

Es posible que se haya alcanzado este problema con la ruta predeterminada {controller}/{action}/{id?} . 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 }). El objeto de valores de ruta:

  • Por convención es normalmente un objeto de tipo anónimo.
  • Puede ser o IDictionary<> 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:

  • Sobrecarga que acepta un 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 mostró la generación de una dirección URL pasando el nombre del controlador y la acción. IUrlHelpertambié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 controller ruta. 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 archivo Razor siguiente 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>

Generación de direcciones URL en HTML y Razor

IHtmlHelper proporciona los HtmlHelper métodos Html.BeginForm y Html.ActionLink para generar <form> los elementos y <a> respectivamente. Estos métodos usan el método Url.Action para generar una dirección 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. Vea 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 mostró el IUrlHelper uso de en un controlador. El uso más común en un controlador es generar una dirección URL como parte de un resultado de 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);
}

La acción genera métodos de generador como RedirectToAction y siguen un patrón similar a los CreatedAtAction métodos de IUrlHelper .

Caso especial para rutas convencionales dedicadas

El enrutamiento convencional puede usar un tipo especial de definición de ruta denominado ruta convencional dedicada. En el ejemplo siguiente, la ruta denominada 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 dirección URL mediante la ruta , pero / default ¿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 impide que la ruta sea demasiado expansión 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. Se produce un error en la generación de direcciones URL blog mediante porque los valores no coinciden con { controller = Home, action = Index } { 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 de MVC que se usa para organizar la funcionalidad relacionada en un grupo como independiente:

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

El uso de áreas permite que una aplicación tenga varios controladores con el mismo nombre, siempre y cuando 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 usan las áreas con vistas.

En el ejemplo siguiente se configura MVC para usar la ruta convencional predeterminada y una area ruta para un denominado area 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 el "blog_route" . El segundo parámetro, "Blog" , es el nombre del área.

Al coincidir con una ruta de dirección URL como /Manage/Users/AddUser , la ruta genera los valores de ruta "blog_route" { area = Blog, controller = Users, action = AddUser } . El area valor de ruta se genera mediante 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 un área.

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

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 [Area] es lo que denota un controlador como parte de un área. Este controlador está en el Blog área. Los controladores sin un atributo no son miembros de ningún área y no coinciden cuando el enrutamiento [Area] proporciona el valor de area ruta. 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í para mayor integridad. Si los controladores anteriores usaron 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 dentro de un área, el valor de ruta de está disponible como un valor ambiente para el enrutamiento que se usará para area 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 código siguiente genera una dirección URL a /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 los que tienen 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"
    }
  }
}

ASP.NET Core controladores usan el middleware de enrutamiento para hacer coincidir las direcciones URL de las solicitudes entrantes y asignarlas a acciones. Plantillas de ruta:

  • Se definen en el código o atributos de inicio.
  • Describir cómo se comparan las rutas de dirección URL con las acciones.
  • Se usan para generar direcciones URL para vínculos. Los vínculos generados normalmente se devuelven en respuestas.

Las acciones se enrutadas convencionalmente o enrutadas por atributos. La colocación de una ruta en el controlador o la acción hace que se enrute por atributos. Consulte Enrutamiento mixto para obtener más información.

Este documento:

  • Explica las interacciones entre MVC y el enrutamiento:
    • Cómo usan las aplicaciones MVC típicas las características de enrutamiento.
    • Abarca ambos:
      • Enrutamiento convencional que se suele usar con controladores y vistas.
      • Enrutamiento de atributos usado con las API REST. Si está interesado principalmente en el enrutamiento de las API REST, vaya a la sección Enrutamiento de atributos para las API REST.
    • Consulte Enrutamiento para obtener detalles de enrutamiento avanzados.
  • 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 del enrutamiento por motivos 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 , se usa para crear una sola MapControllerRoute ruta. La ruta única se denomina default ruta. La mayoría de las aplicaciones con controladores y vistas usan una plantilla de ruta similar a la default ruta. Las API REST deben usar el enrutamiento de atributos.

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

  • Coincide con una ruta de dirección URL como /Products/Details/5

  • Extrae los valores de ruta { controller = Products, action = Details, id = 5 } 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 parámetro en id 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 ? carácter de define como {id?} id 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.

  • Coincide con la ruta de acceso / url.

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

Los valores de controller y hacen uso de los valores action predeterminados. id no genera un valor porque no hay ningún segmento correspondiente en la ruta de acceso url. / solo coincide si existe una HomeController acción Index y :

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

Con la definición del controlador anterior y la plantilla de ruta, la HomeController.Index acción se ejecuta para las siguientes rutas de dirección URL:

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

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

El método de conveniencia MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Reemplaza:

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

Importante

El enrutamiento se configura mediante UseRouting el middleware , y MapControllerRoute 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 anterior es un ejemplo de una ruta convencional. Se denomina enrutamiento convencional porque establece una convención para las rutas de acceso URL:

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

Con esta default ruta, la ruta de dirección URL:

  • /Products/List se asigna a la ProductsController.List acción.
  • /Blog/Article/17 se asigna BlogController.Article a y normalmente el modelo enlaza el parámetro a id 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.

El uso del enrutamiento convencional con la ruta predeterminada permite crear la aplicación sin tener que crear un nuevo patrón de dirección URL para cada acción. Para una aplicación con acciones de estilo CRUD, teniendo coherencia para las direcciones URL entre controladores:

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

Advertencia

En el código anterior, la plantilla de id ruta define como opcional. 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 por enlace de modelo.
  • No se encuentra ninguna entidad en la base de datos que coincida con id == 0 .

El enrutamiento de atributos proporciona un control preciso para que el identificador sea necesario para algunas acciones y no para otras. Por convención, la documentación incluye parámetros opcionales como cuando es probable que id aparezcan en el uso 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 más grandes, otra ruta que usa Áreas suele ser todo lo que se necesita.

MapControllerRoute y MapAreaRoute :

  • Asigne automáticamente un valor de pedido a sus puntos de conexión en función del orden en que se invoque.

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 la 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 agregar varias rutas convencionales dentro UseEndpoints agregando más llamadas a y MapControllerRoute MapAreaControllerRoute . Esto permite definir varias convenciones o agregar rutas convencionales dedicadas 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:

Dado controller que y no aparecen en la plantilla de ruta como action "blog/{*article}" 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 son las /Blog/{any-string} ú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 donde 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 examina el conjunto de puntos de conexión definidos en la aplicación y selecciona la mejor coincidencia de punto de conexión UseRouting 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 definidos por la aplicación. Esto está pensado para simplificar los casos en los que las rutas convencionales se superponen. Agregar rutas mediante , y asignar automáticamente un valor de pedido a sus puntos de conexión MapControllerRoute en función del orden en que se MapDefaultControllerRoute MapAreaControllerRoute invocan. Las coincidencias de una ruta que aparece anteriormente tienen una prioridad mayor. 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 un área. Las rutas convencionales dedicadas con parámetros de ruta como catch-all pueden hacer que una ruta sea demasiado expansión, lo que significa que coincide con las direcciones URL que se pretenden que coincidan con {*article} otras rutas. Coloque las rutas expansiones más adelante en la tabla de rutas para evitar coincidencias expansiones.

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:

  • La ruta de acceso url /Products33/Edit/17
  • Enrutar datos { 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) se selecciona cuando la solicitud es POST http.
  • Edit(int) se selecciona cuando el verbo HTTP es cualquier otra cosa. Edit(int) Por lo general, se llama a a través de GET .

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

Es importante comprender el rol de atributos como HttpPostAttribute . Se definen atributos similares para otros verbos HTTP. En el enrutamiento convencional,es habitual que las acciones usen el mismo nombre de acción cuando forman parte de un flujo de trabajo mostrar formulario y enviar formulario. Por ejemplo, vea Examinar los dos métodos de acción Edit.

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

Nombres de ruta convencionales

Las cadenas "blog" y en los ejemplos siguientes son nombres de ruta "default" 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 le dan un nombre lógico a la ruta. La ruta con nombre se puede usar para la generación de direcciones URL. El uso de una ruta con nombre simplifica la creación de direcciones URL cuando el orden de las rutas podría complicar la generación de direcciones URL. Los nombres de ruta deben ser únicos para toda la aplicación.

Nombres de ruta:

  • No tiene ningún impacto en la coincidencia de direcciones URL ni en el control de 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 las API REST

Las API REST deben usar el enrutamiento de 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 código StartUp.Configure siguiente es típico de una API REST y se usa en el ejemplo siguiente:

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, se llama MapControllers a dentro de para asignar controladores UseEndpoints enrutados de atributos.

En el ejemplo siguiente:

  • HomeController coincide con un conjunto de direcciones URL similares a las que coincide la ruta convencional {controller=Home}/{action=Index}/{id?} predeterminada.
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 se ejecuta para cualquiera de las rutas de HomeController.Index acceso de dirección URL , , o / /Home /Home/Index /Home/Index/3 .

En este ejemplo se resalta una diferencia de programación clave entre el enrutamiento de atributos y el enrutamiento convencional. El enrutamiento de atributos requiere más entrada para especificar una ruta. La ruta predeterminada convencional controla las rutas de forma más concisa. Sin embargo, el enrutamiento de atributos permite y requiere un control preciso de qué plantillas de ruta se aplican a cada acción.

Con el enrutamiento de atributos, los nombres de controlador y acción no desempeñan ninguna parte en la que la acción coincide, a menos que se utilice el reemplazo de tokens. En el ejemplo siguiente se coinciden las mismas direcciones URL que en 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 action para 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 código siguiente se 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 plantillas Index 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 Prioridad de la plantilla de ruta para obtener información sobre la selección de plantillas de ruta.

Nombres de enrutamientos reservados

Las palabras clave siguientes son nombres de parámetros de ruta reservados cuando se usan controladores o Razor páginas:

  • action
  • area
  • controller
  • handler
  • page

El page uso 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ámetro 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 usarse para las generaciones de vínculos, los parámetros enlazados al modelo ni las 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 de atributos con atributos de verbo HTTP

Considere el controlador siguiente:

[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 atributo , que restringe la coincidencia solo con [HttpGet] las solicitudes HTTP GET.
  • La GetProduct acción incluye la "{id}" plantilla, por lo que se anexa a id la plantilla en el "api/[controller]" 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 parte de la plantilla restringe los valores de ruta :int id a cadenas que se pueden convertir en un entero. Una solicitud GET a /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 acción contiene en la plantilla, pero no restringe a GetInt2Product los valores que se pueden convertir en un {id} id entero. Una solicitud GET a /api/test2/int2/abc :
    • Coincide con esta ruta.
    • El enlace de modelos no se abc puede convertir en un entero. El id parámetro del método es integer.
    • Devuelve una solicitud 400 bad porque el enlace de modelos no se pudo convertir en un abc entero.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

El enrutamiento de atributos HttpMethodAttribute puede usar atributos como , y HttpPostAttribute HttpPutAttribute HttpDeleteAttribute . Todos los atributos del verbo HTTP aceptan una plantilla de ruta. En el ejemplo siguiente se muestran 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);
    }
}

Mediante 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 .

Al compilar una API REST, es poco frecuente que necesite usar en un método de acción porque la acción [Route(...)] acepta todos los métodos HTTP. Es mejor usar el atributo de verbo HTTP más específico para ser preciso sobre lo que admite la API. Se espera que los clientes de API de REST sepan qué rutas y verbos HTTP se asignan a determinadas operaciones lógicas.

Las API REST deben usar el enrutamiento de 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 en el 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 el ejemplo siguiente, id se requiere como parte de la ruta de acceso url:

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

La Products2ApiController.GetProduct(int) acción:

  • Se ejecuta con una ruta de dirección 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, vea Definir tipos de contenido de solicitud admitidos 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 ApiController attribute.

Nombre de ruta

El código siguiente define un nombre de ruta de 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 tiene ningún impacto en el comportamiento de coincidencia de direcciones 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.

Compare el código anterior con la ruta predeterminada convencional, que define el id parámetro como opcional ( {id?} ). La capacidad de especificar con precisión las API tiene ventajas, como permitir y enviarse a /products /products/5 diferentes acciones.

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 de acceso url /products puede coincidir ProductsApi.ListProducts
  • La ruta de acceso url /products/5 puede coincidir con ProductsApi.GetProduct(int) .

Ambas acciones solo coinciden con HTTP GET porque están marcadas con el [HttpGet] atributo .

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. En el ejemplo siguiente se coincide con un conjunto de rutas de dirección URL similares a la ruta predeterminada.

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

Atributo Se 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 colocaran 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 atributo como blog/search/{topic} es más específica que una ruta de atributo como blog/{*article} . La blog/search/{topic} ruta tiene mayor prioridad, de forma predeterminada, porque es más específica. Mediante 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 mediante se ejecuta antes que las Order = -1 rutas que no establecen un orden. El establecimiento de una ruta mediante Order = 1 se ejecuta después de la ordenación de rutas predeterminada.

Evite depender de Order . Si el espacio de direcciones URL de una aplicación requiere valores de orden explícitos para enrutar correctamente, es probable que también sea confuso para los clientes. En general, el enrutamiento de atributos selecciona la ruta correcta con coincidencia de direcciones URL. Si el orden predeterminado usado para la generación de direcciones URL no funciona, el uso de un nombre de ruta como invalidación suele ser más sencillo que aplicar la Order propiedad .

Tenga en cuenta los dos controladores siguientes que definen la coincidencia de /home rutas :

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 /home con el código anterior produce 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, no se puede hacer coincidir esa plantilla. Sería mejor quitar la [Route("Home")] plantilla.

Consulte Razor Pages route and app conventions: Route order (Convenciones de rutas y aplicaciones de Pages: orden de ruta) para obtener información sobre el orden de las rutas 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 .

Sustitución de tokens en plantillas de ruta [controlador], [acción], [área]

Para mayor comodidad, las rutas de atributo admiten el reemplazo de tokens al encier un token entre corchetes ( [ , ] ). Los tokens , y se reemplazan por los valores del nombre de acción, el nombre del área y el nombre del controlador de la acción donde [action] [area] se define la [controller] 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();
}
  • Partidos /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • Partidos /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 esto en un idioma distinto del inglés, háganoslo saber en este problema de discusión de GitHub si desea ver los comentarios de código en su idioma nativo.

Las rutas de atributo también se pueden combinar con la herencia. Esto es eficaz combinado con el reemplazo de tokens. 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 SlugifyParameterTransformer de parámetros personalizado cambia el valor de ruta a SubscriptionManagement 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 anterior ListAll 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 atributo

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();
    }

Colocar varios atributos de ruta en el controlador significa que cada uno de ellos 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 del verbo HTTP implementan IActionConstraint .

Cuando se colocan varios atributos de IActionConstraint ruta que implementan 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. Use varias rutas en acciones solo cuando sea necesario, por ejemplo, para admitir 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 acción Products14Controller.ShowProduct solo coincide con rutas de dirección URL como /product14/3 . La parte de la plantilla {id:int} de ruta restringe ese segmento solo a 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 ASP.NET Core de ejecución:

  • Busca atributos en las clases de controlador y los métodos de acción cuando se inicia la aplicación.
  • Usa los atributos que implementan IRouteTemplateProvider para compilar 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 método anterior Get devuelve Order = 2, Template = api/MyTestApi .

Uso del modelo de aplicación para personalizar 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 recopilados 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.

En esta sección se muestra un ejemplo básico de personalización del enrutamiento mediante el modelo de aplicación. El código siguiente hace que las rutas se alinee 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 namespace la 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 controladores basada en namespace , con la base namespace quitada.

se NamespaceRoutingConvention puede aplicar 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 siguiente controlador:

using Microsoft.AspNetCore.Mvc;

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

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

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

En el código anterior:

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

También NamespaceRoutingConvention se puede aplicar como 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

ASP.NET Core aplicaciones pueden mezclar el uso del enrutamiento convencional y el enrutamiento de 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 del controlador realiza todas las acciones del atributo del controlador enrutadas.

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

Generación de direcciones URL y valores de ambiente

Las aplicaciones pueden usar características de generación de direcciones URL de enrutamiento para generar vínculos url a acciones. La generación de direcciones URL elimina la codificación de direcciones URL, lo que hace que el código sea más sólido 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 cubre los aspectos básicos de cómo funciona la generación de direcciones URL. Consulte Enrutamiento para obtener una descripción detallada de la generación de direcciones URL.

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

En el ejemplo siguiente, la IUrlHelper interfaz se usa a través de la propiedad para generar una dirección URL a otra Controller.Url 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 usa la ruta convencional predeterminada, el valor de la variable es la cadena de ruta url de dirección URL /UrlGeneration/Destination . Esta ruta de dirección 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 en 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, de id la plantilla de ruta {controller}/{action}/{id?} .

Se produce un error en la generación de direcciones URL si algún parámetro de ruta necesario 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 se Url.Action da por supuesto el enrutamiento convencional. La generación de direcciones URL funciona de forma similar con el 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 para controller y normalmente aparecen en esa action 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 también tiene una familia de métodos LinkGenerator correspondiente.

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

Url.Action, LinkGenerator.GetPathByActiony 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 , el tiempo de ejecución proporciona los valores de ruta Url.Action controller action actuales para y :

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

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

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

Si se agrega { d = Donovan } el valor:

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

Advertencia: las rutas de dirección URL son jerárquicas. En el ejemplo anterior, si se agrega { c = Cheryl } el valor:

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

Es posible que se haya alcanzado este problema con la ruta predeterminada {controller}/{action}/{id?} . 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 }). El objeto de valores de ruta:

  • Por convención, suele ser un objeto de tipo anónimo.
  • Puede ser o IDictionary<> 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:

  • Sobrecarga que acepta un 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 mostró la generación de una dirección URL pasando el nombre del controlador y la acción. IUrlHelpertambié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 controller ruta. 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 archivo Razor siguiente 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>

Generación de direcciones URL en HTML y Razor

IHtmlHelper proporciona los HtmlHelper métodos Html.BeginForm y Html.ActionLink para generar <form> los elementos y <a> respectivamente. Estos métodos usan el método Url.Action para generar una dirección 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 mostró IUrlHelper el uso de en un controlador. El uso más común en un controlador es generar una dirección URL como parte de un resultado de 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);
}

La acción genera métodos de generador como RedirectToAction y siguen un patrón similar a los CreatedAtAction métodos de IUrlHelper .

Caso especial para rutas convencionales dedicadas

El enrutamiento convencional puede usar un tipo especial de definición de ruta denominado ruta convencional dedicada. En el ejemplo siguiente, la ruta denominada 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 dirección URL mediante la ruta , pero / default ¿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 expansión 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. Se produce un error en la generación de direcciones URL blog mediante porque los valores no coinciden con { controller = Home, action = Index } { 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 de MVC que se usa para organizar la funcionalidad relacionada en un grupo como independiente:

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

El uso de áreas permite que una aplicación tenga varios controladores con el mismo nombre, siempre y cuando 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 usan las áreas con vistas.

En el ejemplo siguiente se configura MVC para usar la ruta convencional predeterminada y una area ruta para un denominado area 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 el "blog_route" . El segundo parámetro, "Blog" , es el nombre del área.

Al coincidir con una ruta de dirección URL como /Manage/Users/AddUser , la ruta genera los valores de ruta "blog_route" { area = Blog, controller = Users, action = AddUser } . El area valor de ruta se genera mediante 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 un área.

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

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 [Area] es lo que denota un controlador como parte de un área. Este controlador está en el Blog área. Los controladores sin un atributo no son miembros de ningún área y no coinciden cuando el enrutamiento [Area] proporciona el valor de area ruta. 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í para mayor integridad. Si los controladores anteriores usaron 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 dentro de un área, el valor de ruta de está disponible como un valor ambiente para el enrutamiento que se usará para area 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 código siguiente genera una dirección URL a /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 los que tienen 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"
    }
  }
}