Cómo crear controladores REST en .NET

Resumen

Crear controladores en una API .NET es el paso donde toda la arquitectura previa cobra sentido: el AppDbContext, los modelos y los servicios se conectan para exponer endpoints que otras aplicaciones puedan consumir. Aquí aprenderás a estructurar controladores REST para usuarios y tareas, aplicar inyección de dependencias y devolver los códigos HTTP correctos según el patrón REST.

Qué hace un controlador en una API .NET

Un controlador es la capa que expone funciones accesibles vía HTTP y delega la lógica al servicio, que a su vez habla con la base de datos a través del AppDbContext. Esa separación mantiene el código ordenado y testeable.

En la carpeta Controllers creas un nuevo elemento del tipo API Controller vacío. Por convención, la ruta por defecto queda como api/[nombre del controlador], así que un UserController se accede en api/user.

¿Qué diferencia hay entre GET y POST en un controlador? GET sirve para obtener datos sin modificarlos, POST para crear nuevos registros. Ambos pueden compartir la misma ruta, lo que cambia es el verbo HTTP.

Cómo inyectar el servicio en el controlador

Lo primero dentro del controlador es recibir la interfaz del servicio por constructor y guardarla en una variable privada con guion bajo, por convención _userService. Así aplicas inyección de dependencias, un patrón clave en .NET que permite que el framework resuelva automáticamente la implementación correcta en tiempo de ejecución [02:00].

Luego defines los métodos asíncronos que devuelven Task<ActionResult>. Usar ActionResult da flexibilidad para responder con distintos códigos HTTP según el caso.

Cómo implementar GetAll y GetById

El método para obtener todos los registros lleva el atributo [HttpGet] y llama a _userService.GetAllAsync() con await. Como devuelves una lista, lo correcto es nombrar la variable en plural, users, y responder con return Ok(users).

Para obtener uno solo necesitas:

  • Atributo [HttpGet("{id}")] que define la ruta api/user/{id}.
  • Parámetro int id requerido en la firma del método.
  • Llamada a GetByIdAsync(id) para buscar el registro.
  • Validación con if que devuelva NotFound() si el resultado es nulo.

Esa validación de NotFound es una buena práctica REST: si el cliente busca un id válido pero el elemento no existe, devolver el código 404 es más correcto que regresar un objeto vacío o un null [09:30].

Cómo crear el endpoint POST con CreatedAtAction

El método de creación marca con [HttpPost] y recibe el objeto desde el cuerpo de la petición usando [FromBody]. Internamente llama a _userService.CreateAsync(user) y guarda el resultado.

Aquí viene lo interesante: aunque podrías responder con un simple Ok(), REST tiene un código específico para creaciones exitosas, el 201 Created. Para devolverlo se usa CreatedAtAction, que recibe tres elementos:

  1. El nombre de la acción a invocar, idealmente con nameof(GetById) para que sea dinámico.
  2. Un objeto anónimo con el parámetro de ruta, en este caso new { id = created.Id }.
  3. El objeto completo recién creado.

Con esto, la respuesta no solo confirma la creación: incluye también la URL donde el cliente puede consultar el nuevo registro. Es una práctica que mejora la experiencia de cualquiera que consuma tu API.

¿Qué hace nameof en C#? Devuelve el nombre de un método o variable como string en tiempo de compilación. Si renombras el método, nameof se actualiza solo y evitas errores por strings desactualizados.

Cómo replicar la estructura para el controlador de tareas

El TaskController sigue el mismo patrón pero inyecta ITaskService en lugar de IUserService. Implementa GetAll, GetById con validación de NotFound, y Create con CreatedAtAction. La lógica es idéntica porque la arquitectura está bien desacoplada: cambiar de entidad solo requiere cambiar el servicio inyectado.

Qué configuración necesita Program.cs para que funcione

Para que los controladores respondan correctamente, el archivo Program.cs debe tener tres piezas activas:

  • AddControllers() que registra el servicio y resuelve las dependencias.
  • MapControllers() que actúa como middleware para hacer match entre la ruta solicitada y el controlador correspondiente.
  • La configuración del AppDbContext, en este caso usando base de datos en memoria para pruebas.

Si falta MapControllers, ninguna ruta encontrará su controlador y la API devolverá un Not Found genérico [12:30].

Cómo probar la API con Swagger

Al ejecutar el proyecto, Swagger aparece como interfaz para probar cada endpoint. Como hay autorización activa, primero te autenticas con las credenciales de ejemplo Platzi y 12345.

El flujo de prueba es directo:

  1. POST a api/user con un cuerpo JSON que incluya el id manualmente, ya que la base de datos en memoria no genera identificadores automáticos.
  2. Verificar la respuesta 201 Created con el objeto devuelto.
  3. GET a api/user para confirmar que el registro aparece en la lista.
  4. POST a api/task referenciando el id del usuario creado, con campos como título y isComplete: false.

Swagger a veces muestra una estructura confusa para entidades relacionadas, pero basta con enviar el id del usuario existente para que la relación funcione.

Tu siguiente reto es completar el CRUD: faltan los métodos Update con [HttpPut] y Delete con [HttpDelete]. Cuando los tengas, podrás conectar el proyecto a una base de datos real. ¿Cuál fue la parte más retadora al armar tu primer controlador? Cuéntalo en los comentarios.