Crear controladores en una API .NET es el paso donde tu arquitectura cobra sentido: aquí expones los endpoints que otras aplicaciones consumirán y validas que el AppDbContext, los servicios y los modelos trabajen juntos. Esta guía te muestra cómo construir un UserController y un TaskController aplicando buenas prácticas de REST.
¿Qué hace un controlador en una API .NET?
Un controlador actúa como la puerta de entrada de tu API. Recibe peticiones HTTP, delega la lógica al servicio y devuelve una respuesta con el código correcto.
En tu proyecto ya tienes tres piezas listas: el AppDbContext con los modelos User y TaskItem, los servicios que median entre el controlador y la base de datos, y ahora los controladores que exponen todo eso al exterior. Esa separación es la que mantiene el código limpio y testeable.
¿Qué es un endpoint en una API? Es una ruta específica que responde a un verbo HTTP. Por ejemplo, api/user con GET devuelve usuarios y con POST crea uno nuevo.
¿Cómo creo el UserController paso a paso?
Desde la carpeta Controllers, agregas un nuevo Controller tipo API vacío y lo nombras UserController. La ruta por defecto queda como api/[controller], lo que se traduce en api/user [00:48].
Lo primero dentro de la clase es inyectar el servicio mediante su interfaz. La recibes en el constructor y la asignas a una variable privada con guion bajo (_userService) para indicar que es local. Esta es la inyección de dependencias trabajando a tu favor.
¿Cómo implemento GetAll y GetById?
El método GetAll es asíncrono, devuelve un ActionResult y llama a _userService.GetAllAsync() con await. El retorno usa Ok(users) en plural, porque buenas prácticas REST piden nombrar la variable según lo que representa: una lista [02:00].
No olvides el atributo [HttpGet] encima del método. Sin él, el middleware no reconocerá la ruta como endpoint.
Para GetById, decoras con [HttpGet("{id}")] y recibes el id como parámetro tipado int. Esto crea una nueva ruta: api/user/{id}. Aquí entra una validación crítica: si el servicio no encuentra el registro, debes devolver NotFound() en lugar de un null vacío. Es el código correcto del patrón REST para decirle al cliente: "ese recurso no existe".
¿Cómo devuelvo el código 201 Created al crear un recurso?
El método Create lleva [HttpPost] y recibe el objeto desde el cuerpo de la petición con [FromBody]. Llamas a _userService.CreateAsync(user) y guardas el resultado.
Podrías devolver un simple Ok(), pero REST tiene un código específico para creaciones: el 201 Created. Para devolverlo correctamente, usas CreatedAtAction:
csharp
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
Esto hace tres cosas: confirma la creación, llama al método GetById para verificar que el recurso existe, y devuelve la URL donde ubicarlo. El truco está en nameof(GetById), que evita escribir el nombre como string y mantiene el código refactorizable.
¿Para qué sirve CreatedAtAction? Devuelve el código 201, ejecuta el método indicado para confirmar el recurso creado y entrega al cliente la ruta exacta donde consultarlo.
¿Cómo replico la estructura en el TaskController?
El TaskController sigue exactamente el mismo patrón, pero inyectas ITaskService en lugar del servicio de usuarios. Implementas GetAll, GetById y Create con la misma lógica [07:30].
La validación de NotFound aquí es clave. Si alguien busca una tarea por id y no existe, ya sea porque fue eliminada o nunca se creó, el controlador debe responder con el código correcto y no con un objeto vacío.
¿Qué configuración necesita Program.cs para que los controladores funcionen?
Dos líneas son obligatorias en Program.cs:
builder.Services.AddControllers() registra todas las dependencias e inyecciones que necesitan los controladores.
app.MapControllers() activa el middleware que intercepta cada petición, busca un controlador que coincida con la ruta y lo ejecuta.
Si falta MapControllers, la API devolverá un NotFound genérico aunque tus controladores estén perfectamente escritos. Y como estás usando una base de datos en memoria, también necesitas la configuración del AppDbContext registrada ahí mismo.
¿Cómo pruebo los endpoints con Swagger?
Al ejecutar el proyecto, Swagger se abre con la autorización activa. Te autenticas con el usuario Platzi y la clave 12345 [11:30].
Luego pruebas el flujo completo:
- POST a
api/user: creas un usuario enviando un id manual (la base en memoria no autogenera ids) y los demás campos. Swagger te devuelve 201 Created.
- GET a
api/user: obtienes la lista con el usuario recién creado.
- POST a
api/task: creas una tarea como "Terminar el curso de APIs" asociada al id del usuario, con isComplete en false.
- GET a
api/task: confirmas que la tarea aparece con toda su información.
Esa misma ruta api/user sirve para GET y POST. Lo que cambia es el verbo HTTP, no la URL. Esa es la elegancia de REST.
¿Qué falta para completar el CRUD?
Con GetAll, GetById y Create tienes la mitad del camino. Faltan dos métodos para cerrar el CRUD completo:
- Update con
[HttpPut("{id}")] para modificar un recurso existente.
- Delete con
[HttpDelete("{id}")] para eliminarlo.
Implementarlos siguiendo el mismo patrón te dará el control total sobre los recursos de tu API y te preparará para conectar una base de datos real. ¿Ya intentaste implementar el Update y Delete en tu proyecto? Cuéntame en los comentarios cómo te fue.