El routing es el mecanismo por el cual un servidor web mapea las solicitudes HTTP a un fragmento específico en el código de nuestra aplicación.
Por ejemplo, si un usuario solicita la URL (ej: GET /products/1
), ASP.NET necesita saber qué código debe ejecutar para manejar esta solicitud.
El enrutamiento y es el sistema mediante el cuál definimos que fragmento de código debe ejecutarse, para cada posible URL.
Para ello empleamos el concepto de ruta, que no es más que una cadena de texto que define un patrón que puede coincidir con la URL de una solicitud HTTP.
Las rutas, además, pueden contener segmentos dinámicos que se reemplazarán en función de la solicitud (ahí está la mayor parte de su gracia).
/products/{id}
En este caso, {id}
es un parámetro dinámico, y puede coincidir con cualquier valor en esa posición, como por ejemplo /products/123
.
Cuando un cliente realiza una solicitud HTTP a una aplicación ASP.NET Core, el sistema de enrutamiento compara la URL de la solicitud con las rutas definidas en la aplicación.
Tipos de enrutamiento
ASP.NET soporta múltiples enfoques de enrutamiento:
Tipo de Enrutamiento | Dónde se define | Ventajas principales |
---|---|---|
Minimal APIs | Program.cs (desde .NET 6+) | ✔️ Código conciso ✔️ Rápido para prototipos |
Routing por atributos | En controladores) | ✔️ Flexibilidad ✔️Control explícito |
Routing convencional | Program.cs o Startup.cs | ✔️ Configuración centralizada ✔️ Útil en apps grandes |
Minimal APIs
La opción más sencilla es emplear mappeo de rutas minimalista. definir rutas directamente en Program.cs
Este es un enfoque muy directo, que fue introducido en .NET 6, para tener un comportamiento sencillo y similar al de otros frameworks.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "¡Hola, mundo!");
app.MapGet("/saludo/{nombre}", (string nombre) => $"Hola, {nombre}!");
app.MapPost("/crear", async (HttpContext context) =>
{
string body = await new StreamReader(context.Request.Body).ReadToEndAsync();
return Results.Ok($"Datos recibidos: {body}");
});
app.Run();
En este caso:
MapGet
responde a solicitudesGET
en/saludo
.MapPost
maneja solicitudesPOST
en/crear
.
Ventajas
✔️ Menos código boilerplate ➡️ Ideal para proyectos pequeños.
Enrutado por controladores
El enrutado “convencional” y más usado en ASP.NET es el enrutado a controladores. (un controlador es simplemente un fragmento de código diseñado para responder a una ruta).
Dentro del enrutado por controladores, a su vez podemos tener
- Rutas de atributo: Se definen directamente en los controladores y acciones utilizando atributos.
- Rutas convencionales: Se definen en el archivo de configuración de la aplicación o mediante atributos en los controladores y acciones.
Es el enrutado más empleado. Las rutas de atributo se definen directamente en los controladores y acciones utilizando el atributo [Route]
.
Este enfoque es muy flexible y permite definir rutas específicas para cada acción.
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
// Lógica para obtener el producto
}
}
En este ejemplo:
- El atributo
[Route("api/[controller]")]
define una ruta base para el controladorProductController
. La palabra clave[controller]
se reemplaza automáticamente por el nombre del controlador (en este caso,Product
). - El atributo
[HttpGet("{id}")]
define una ruta específica para la acciónGetProduct
. La URL/api/product/123
se mapeará a esta acción, y el valor123
se pasará como parámetroid
.
Es una alternativa más antigua de enrutado a controladores, que no emplea atributos.
Se configura en Program.cs
o Startup.cs
.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
En este ejemplo:
controller
: Especifica el controlador que manejará la solicitud.action
: Indica la acción o método dentro del controlador.id?
: Un parámetro opcional.- Una solicitud a
https://localhost:5001/Products/Details/5
se mapeará al controladorProducts
, acciónDetails
, y pasará el parámetroid=5
.
Routing convencional se usa menos en nuevos proyectos, pero puedes encontrartelo en aplicaciones existentes.
Combinación de rutas convencionales y de atributo
Es posible combinar rutas convencionales y de atributo en la misma aplicación (aunque probablemente no sea buena idea 🙄) .
En este caso, las rutas de atributo tienen prioridad sobre las rutas convencionales. Esto significa que si una acción tiene una ruta de atributo definida, se utilizará en lugar de la ruta convencional.
En serio, no lo hagáis si podéis evitarlo 🙅
Rutas con parámetros
En cualquiera de los tipos de routing que hemos visto, existe la posibilidad de incluir parámetros que se pasan a las acciones como argumentos. Por ejemplo:
app.MapControllerRoute(
name: "product",
pattern: "product/{id}",
defaults: new { controller = "Product", action = "Details" });
En este caso, una URL como /product/123
se mapeará a la acción Details
del controlador Product
, y el valor 123
se pasará como parámetro id
.
Los veremos con mayor detalles, en cada uno de las artículos correspondientes
Buenas prácticas de diseño
Vamos con algunos consejitos a seguir antes de lanzaros a diseñar vuestras APIs y aplicaciones web.
Nomenclatura consistente
La denominación de los end-points es muy importante al diseñar una API web. Un par de pautas a seguir 👇.
- Usa sustantivos plurales:
/products
en lugar de/getProducts
- Minúsculas y guiones:
/user-roles
, nunca algo como/userRoles
- Evita verbos en rutas: Usa los métodos HTTP para indicar acciones
Versionado de APIs
También es muy común que un API cambie en algún momento. Es una buena precaución incluir una referencia a la versión en las APIs.
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductosController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetById(int id) { /* ... */ }
}