Domina el manejo de rutas en APIs .NET con un enfoque práctico: desde configurar la ruta base con el atributo Route hasta crear endpoints con parámetros y devolver respuestas HTTP correctas usando ActionResult. Verás cómo evitar errores comunes, aprovechar Hot Reload y explorar endpoints con Endpoints Explorer, manteniendo un estilo REST claro y predecible.
¿Cómo configurar la ruta base en .NET?
Configurar la ruta base define cómo se accede a tu controller. Usar [Route("api/[controller]")] iguala el nombre de la ruta al nombre del controller, un patrón común para agrupar recursos de la API.
Usa [ApiController] y ControllerBase para respuestas HTTP listas para usar.
Prefija con api/ para separar endpoints de salud o documentación de la API pública.
Valida con Hot Reload para aplicar cambios sin reiniciar.
Si la URL no existe, el framework responde 404 Not Found acorde a REST.
Ejemplo base con lista precargada y GET general:
[ApiController][Route("api/[controller]")]publicclassWeatherForecastController:ControllerBase{privatestaticList<WeatherForecast> _items;publicWeatherForecastController(){if(_items ==null){// Inicializa 5 elementos aleatorios como en la plantilla por defecto. _items = Enumerable.Range(1,5).Select(i =>newWeatherForecast{/* ... */}).ToList();}}[HttpGet]publicIEnumerable<WeatherForecast>Get()=> _items;}
¿Cómo crear endpoints con parámetros y rutas relativas o absolutas?
Definir la ruta de un endpoint con parámetro es directo. La clave está en si la ruta es relativa (se suma a la base) o absoluta (reemplaza la base).
Relativa: sin slash inicial. Se concatena a la base del controller.
Absoluta: con slash inicial. Ignora la ruta base y crea una nueva.
El nombre del parámetro en la ruta debe coincidir con el del método.
Relativa (se combina con api/weatherforecast):
[HttpGet("{id}")]publicActionResult<WeatherForecast>GetById(int id){// Validación se muestra abajo.returnOk(_items[id]);}
Absoluta (no usa la base del controller):
[HttpGet("/id")]// o [Route("/id")]publicWeatherForecastGetByAbsolute(int id)=> _items[id];
Apóyate en Endpoints Explorer para ver la lista de endpoints y generar un request de prueba rápidamente.
¿Cómo devolver códigos HTTP correctos con ActionResult?
Devolver el código correcto mejora la claridad del contrato REST. Un acceso fuera de rango produce una excepción y termina en 500 Internal Server Error si no lo controlas. En su lugar, valida y devuelve 400 Bad Request; en casos válidos, 200 OK.
Usa ActionResult<T> para retornar tanto datos como estados HTTP.
Métodos útiles: BadRequest() y Ok() ya disponibles por ControllerBase.
Valida límites: el índice no debe ser negativo ni superar el tamaño de la lista.
Ejemplo con validación y respuestas adecuadas:
[HttpGet("{id}")]publicActionResult<WeatherForecast>GetById(int id){if(id <0|| id >= _items.Count){returnBadRequest();// Request inválido: índice fuera de rango.}returnOk(_items[id]);// 200 OK con el elemento solicitado.}
Prácticas útiles para probar:
Usa el archivo HTTP del proyecto para lanzar requests rápidos.
Verifica cambios con Hot Reload antes de probar.
Prueba también con Postman para ganar experiencia en ambas herramientas.
Completa el CRUD: crea con POST, actualiza con PUT y elimina con DELETE.
¿Quieres que revisemos tu ruta actual o un caso particular de validación? Cuéntalo en los comentarios y te ayudo a pulirlo.
Considero que en un escenario real, NotFound() sería más apropiado que BadRequest(), ya que el cliente no tiene conocimiento del tamaño de la colección. Una petición como /weatherforecast/50 es válida desde el punto de vista del usuario y el contrato HTTP, y si el recurso no existe, el código de respuesta correcto sería 404. BadRequest() podría reservarse para índices negativos u otros casos donde la petición sea inválida. En cuanto a valores no numéricos, ASP.NET Core ya se encarga automáticamente de esa validación.
Adicionalmente, se podría aprovechar la propiedad .Count de la colección en lugar de un valor fijo para hacer la solución más dinámica, en caso de que se quisieran agregar más elementos.
[HttpGet("{index}", Name ="GetWeatherForecastByIndex")]public ActionResult<WeatherForecast>GetByIndex(int index){if(index <0)returnBadRequest();if(index >= _weatherForecasts.Count)returnNotFound();returnOk(_weatherForecasts[index]);}
👍
Me parece acertado tu punto, un número entero positivo siempre lo van a tomar como algo válido entonces por el escenario especifico que tenemos con pocos elementos deberia retornar not found cuando supere los elementos del array.
En APIs públicas sí podría usarse:
.AllowAnyOrigin()
pero con cuidados extra de Rate Limit, API keys e incluso Autenticación.
builder.Services.AddRateLimiter(...)
builder.Services.AddAuthentication()
Hola, paso mi código del reto por si ayuda:
using Microsoft.AspNetCore.Mvc;namespace CursoApis.Controllers
{[ApiController][Route("api/[controller]")] public class WeatherForecastController : ControllerBase
{ private static readonly string[] Summaries =["Freezing","Bracing","Chilly","Cool","Mild","Warm","Balmy","Hot","Sweltering","Scorching"];// Cambiado: Inicializamos la lista una sola vez para que no se borre private static List<WeatherForecast> ListWeatherForecast = new List<WeatherForecast>();// Constructor Estático: Se ejecuta UNA sola vez al iniciar la appstaticWeatherForecastController(){ ListWeatherForecast = Enumerable.Range(1,5).Select(index => new WeatherForecast
{ Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20,55), Summary = Summaries[Random.Shared.Next(Summaries.Length)]}).ToList();}// 1. GET (Listar todo)[HttpGet(Name ="GetWeatherForecast")] public IEnumerable<WeatherForecast>Get(){return ListWeatherForecast;}// 2. GET BY ID (Obtener uno por posición)[HttpGet("{id}")] public ActionResult<WeatherForecast>GetByPosition(int id){if(id <0|| id >= ListWeatherForecast.Count){returnBadRequest($"No existe el índice {id}. Elementos actuales: {ListWeatherForecast.Count}");}returnOk(ListWeatherForecast[id]);}// 3. POST (Crear nuevo)[HttpPost] public ActionResult<WeatherForecast>Post([FromBody] WeatherForecast weatherForecast){// Nota: En .NET moderno la validación de null suele ser automática, // pero dejar esto no hace daño.if(weatherForecast == null){returnBadRequest("El objeto es null");} ListWeatherForecast.Add(weatherForecast);// Retorna 201 Created y la URL donde consultar lo creadoreturnCreatedAtAction(nameof(GetByPosition), new { id = ListWeatherForecast.Count -1}, weatherForecast
);}// 4. PUT (Actualizar existente)[HttpPut("{id}")] public ActionResult Put(int id,[FromBody] WeatherForecast weatherForecast){if(id <0|| id >= ListWeatherForecast.Count){returnBadRequest("Índice fuera de rango");}// Reemplazamos el objeto en esa posición ListWeatherForecast[id]= weatherForecast;returnNoContent();// 204: Éxito pero no devuelvo nada (estándar REST)}// 5. DELETE (Borrar)[HttpDelete("{id}")] public ActionResult Delete(int id){if(id <0|| id >= ListWeatherForecast.Count){returnBadRequest("Índice fuera de rango");} ListWeatherForecast.RemoveAt(id);returnNoContent();// 204: Éxito, ya se borró}}}
HTTP:
@CursoApis_HostAddress = https://localhost:7259
### 1. Obtener toda la lista (Verás 5 iniciales)GET {{CursoApis_HostAddress}}/api/weatherforecast/
Accept: application/json
### 2. Obtener un elemento específico (Índice 1)@id =1GET {{CursoApis_HostAddress}}/api/weatherforecast/{{id}}### 3. Crear un nuevo clima (POST)# Debería devolver 201 CreatedPOST {{CursoApis_HostAddress}}/api/weatherforecast
Content-Type: application/json
{"date":"2026-02-20",
"temperatureC":35,
"summary":"Super Hot Platzi"}### 4. Verificar que se creó (Debería estar en el índice 5)GET {{CursoApis_HostAddress}}/api/weatherforecast/5
### 5. Actualizar el primer elemento (PUT)# Cambiamos el índice 0 a "Congelado"PUT {{CursoApis_HostAddress}}/api/weatherforecast/0
Content-Type: application/json
{"date":"2026-01-01",
"temperatureC": -10,
"summary":"Congelado Update"}### 6. Borrar un elemento (DELETE)# Borramos el índice 0 que acabamos de editarDELETE {{CursoApis_HostAddress}}/api/weatherforecast/0
### 7. Comprobación final (Deberían quedar 5 elementos de nuevo)GET {{CursoApis_HostAddress}}/api/weatherforecast/
Saludos.
Quiero compartirles mi proceso desarrollando el reto de esta clase: Completar el ciclo de vida completo de un recurso (CRUD).
WeatherForecastController.cs
using Microsoft.AspNetCore.Http.HttpResults;using Microsoft.AspNetCore.Mvc;namespace CursoApis.Controllers
{[ApiController][Route("api/[controller]")] public class WeatherForecastController : ControllerBase
{ private static readonly string[] Summaries =["Freezing","Bracing","Chilly","Cool","Mild","Warm","Balmy","Hot","Sweltering","Scorching"]; private static List<WeatherForecast> ListweatherForecasts = new List<WeatherForecast>(); public WeatherForecastController(){if(ListweatherForecasts.Count ==0){ ListweatherForecasts = Enumerable.Range(1,5).Select(index => new WeatherForecast
{ Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20,55), Summary = Summaries[Random.Shared.Next(Summaries.Length)]}).ToList();}}[HttpGet] public IEnumerable<WeatherForecast>Get(){return ListweatherForecasts;}[HttpGet()][Route("{id}")] public ActionResult<WeatherForecast>GetByPosition(int id){if(id <0|| id >= ListweatherForecasts.Count){returnBadRequest($"Índice {id} no válido. La lista tiene {ListweatherForecasts.Count} elementos.");}returnOk(ListweatherForecasts[id]);}[HttpPost] public ActionResult Post([FromBody] WeatherForecast nuevoClima){ ListweatherForecasts.Add(nuevoClima);returnOk($"Elemento creado con exito en el índice {ListweatherForecasts.Count - 1}. La lista ahora tiene {ListweatherForecasts.Count} elementos.");// Retorna 200 OK}[HttpPut][Route("{id}")] public ActionResult Put(int id,[FromBody] WeatherForecast climaActualizado){if(id <0|| id >= ListweatherForecasts.Count){returnBadRequest("No se puede actualizar: Índice inexistente.");} ListweatherForecasts[id]= climaActualizado;returnOk($"Índice {id} Actualizado con exito.");}[HttpDelete][Route("{id}")] public ActionResult Delete(int id){if(id <0|| id >= ListweatherForecasts.Count){returnBadRequest("No se puede eliminar: Índice inexistente.");} ListweatherForecasts.RemoveAt(id);returnOk($"Índice {id} Eliminado con Exito. La lista tiene {ListweatherForecasts.Count} elementos.");}}}
Explorador de Puntos de Conexion
### CursoApis.http
@CursoApis_HostAddress = https://localhost:7027### Aqui podemos ingresar el id especifico en mi caso esta generando 5 id
### y esoty agregando el id 5 con el POST
@id =6### 1. Obtener todos los pronosticos
GET {{CursoApis_HostAddress}}/api/weatherforecast
Accept: application/json
###
### 2. Crear un nuevo pronostico(POST)POST {{CursoApis_HostAddress}}/api/weatherforecast
Content-Type: application/json
{"date":"2026-01-10","temperatureC":30,"summary":"Hot"}
###
### 3. Obtener un pronostico específico por ID(GET)GET {{CursoApis_HostAddress}}/api/weatherforecast/{{id}}
###
### 4. Actualizar un pronostico existente(PUT)PUT {{CursoApis_HostAddress}}/api/weatherforecast/{{id}}Content-Type: application/json
{"date":"2026-01-11","temperatureC":-5,"summary":"Bracing"}
###
### 5. Eliminar un pronostico(DELETE)DELETE {{CursoApis_HostAddress}}/api/weatherforecast/{{id}}