DEV Community

Cover image for Creando un API en Net Core - Mi primer API
Andres Lozada Mosto
Andres Lozada Mosto

Posted on • Edited on

Creando un API en Net Core - Mi primer API

Llegó el tan ansiado momento de meter mano en el código. Durante este artículo vamos a crear nuestro primer API e implementar algunas de las cosas que vimos en los artículos anteriores.

Pueden clonarse el proyecto desde github.


Creando nuestro primer proyecto

Si estamos utilizando un IDE como Visual Studio o Rider simplemente vamos a la opción de crear un nuevo proyecto, seleccionamos el tipo de proyecto Web API y seguimos las instrucciones. Con esta acción se generará un nuevo proyecto utilizando como base el template que viene integrado en Net Core y seleccionará la versión de Net Core más nueva que se encuentre instalada en el sistema (recomiendo utilizar la última, Net Core 5).

En cambio si estamos usando la línea de comandos y un editor como VS Code para programar, debemos seguir estos pasos para crear la estructura del proyecto

// creamos la carpeta de la solución
$> mkdir MyFirstNetCoreWebAPI

// creamos el archivo de solución
$> dotnet new sln --name MyFirstNetCoreWebAPI

// creamos la carpeta de código
$> mkdir src
$> cd src

// creamos el proyecto de web api
$> dotnet new webapi --name MyFirstNetCoreWebAPI.WebAPI

// agregamos el proyecto a la solución
$> cd ..
$> dotnet sln .\MyFirstNetCoreWebAPI.sln add .\src\MyFirstNetCoreWebAPI.WebAPI\MyFirstNetCoreWebAPI.WebAPI.csproj
Enter fullscreen mode Exit fullscreen mode

Si nos gusta trabajar con la consola y/o queremos utilizar un IDE liviano como VS Code talvez convenga instalar el generador de dotnet que nos ayudará a crear los elementos necesarios (mas allá de los plugins de VS Code).

$> dotnet tool install -g dotnet-aspnet-codegenerator

## Agregamos el paquete al proyecto
$> dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
Enter fullscreen mode Exit fullscreen mode

Ya sea que creemos desde donde creemos el proyecto, luego éste puede utilizarse desde cualquier IDE.

Con el proyecto creado, borramos las 2 clases que se crean de forma automática: WeatherForecast.cs y WeatherForecastController.cs. Es código del template que se agrega únicamente como referencia pero que nosotros no vamos a utilizar.

La estructura base que les debe quedar es la siguiente:

Alt Text

Para los que son nuevos en Net Core vamos a repasar qué es cada cosa:

  • Program.cs: es el archivo que crea y levanta el Host donde se va a ejecutar el web api
  • Startup.cs: es la clase de inicialización de la aplicación. En ella se realiza la configuración del servicio/app/web como por ejemplo la configuración de seguridad, de logging, de OpenAPI/Swagger, motor de inyección de dependencias (IOC), etc
  • appsettings.json: es un archivo de configuración externo y variable por ambiente
  • Controllers: esta carpeta estarán los controllers que son los puntos de entrada de las URIs/endpoints del API.

Nuestro primer recurso

Para nuestro ejemplo vamos a utilizar el recurso usuarios en donde vamos a poder listar los usuarios existentes, consultar por un usuario en particular y agregar un nuevo usuario.

Lo primero que debemos realizar es crear una clase controller que se llame UsersController ya sea con el IDE o con el generador de código.

$> dotnet aspnet-codegenerator controller -api -async -name UsersController -outDir Controllers
Enter fullscreen mode Exit fullscreen mode

Si ahora vemos el file system o el explorador del IDE vemos que se creó un archivo con la estructura básica de un Controller de Web API.

Dentro del controller comenzamos a agregar la lógica necesaria.

Listado de todos los usuarios

[HttpGet()]
public ActionResult<List<UserDto>> GetAllUsers() 
    => Ok(_userRepository
        .GetAll()
        .Select(x => UserDto.FromModel(x)).ToList());
Enter fullscreen mode Exit fullscreen mode

Obtener un usuario en particular

[HttpGet("{name}")]
public ActionResult<UserDto> GetUser(string name)
{
    var user = _userRepository.Get(name);
    if (user == null)
        return Problem(
            "User not found",
            HttpContext.Request.Path,
            StatusCodes.Status404NotFound,
            "Bad parameters");

    return Ok(UserDto.FromModel(user));
}
Enter fullscreen mode Exit fullscreen mode

Crear un nuevo usuario

[HttpPost()]
public ActionResult<UserDto> AddUser([FromBody] UserDto newUserToCreate)
{
    if (string.IsNullOrEmpty(newUserToCreate.Name))
        return BadRequest(new ProblemDetails()
        {
            Title = "Bad parameters",
            Detail = "The name of the new user cannot be empty or null",
            Instance = HttpContext.Request.Path
        });

    var userAlreadyExists = _userRepository.Exists(newUserToCreate.Name);
    if (userAlreadyExists)
        return Conflict(new ProblemDetails()
        {
            Detail = "User already exists",
            Title = "Bad parameters",
            Instance = HttpContext.Request.Path
        });

    var newUser = new User(newUserToCreate.Name);

    _userRepository.Add(newUser);

    return Created($"/users/{newUserToCreate.Name}", UserDto.FromModel(newUser));
}
Enter fullscreen mode Exit fullscreen mode

Es un ejemplo simple y bastante trivial de una *API, pero podemos ver varios de lo visto en los artículos anteriores:

  • URI de recurso: el recurso usuario tiene su URI de acceso http://domain.com/api/users. Esto lo provee el Atributte [Route("api/[controller]")] que se encuentra al inicio de la clase.
  • HTTP Verbs: cada acción sobre el recurso está asociado al verbo HTTP correcto. Los que son de consulta utilizamos GET y el que es de creación POST.
  • Safe: las acciones sobre el recurso utilizando el verbo GET no genera efectos secundarios sobre el recurso
  • Idempotencia: Los Get siempre devuelven la misma información ante múltiples peticiones iguales.
  • Códigos de respuesta: Utilizamos los correctos para cada caso, 200 para request satisfactorio devolviendo los datos solicitados, 201 para cuando se crea un nuevo recurso, 404 para cuando no es encontrado el recurso solicitado y 400 si los datos que vinieron en el request tienen algún problema.
  • Open API: el scaffold del template ya viene con la implementación básica de OpenAPI/Swagger por lo que la documentación está disponible al ejecutar la aplicación.

Aquí podemos ver la clase completa

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
    private readonly IUserRepository _userRepository;

    public UsersController(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    [HttpGet()]
    public ActionResult<List<UserDto>> GetAllUsers() 
        => Ok(_userRepository
            .GetAll()
            .Select(x => UserDto.FromModel(x)).ToList());

    [HttpGet("{name}")]
    public ActionResult<UserDto> GetUser(string name)
    {
        var user = _userRepository.Get(name);
        if (user == null)
            return Problem(
                "User not found",
                HttpContext.Request.Path,
                StatusCodes.Status404NotFound,
                "Bad parameters");

        return Ok(UserDto.FromModel(user));
    }

    [HttpPost()]
    public ActionResult<UserDto> AddUser([FromBody] UserDto newUserToCreate)
    {
        if (string.IsNullOrEmpty(newUserToCreate.Name))
            return BadRequest(new ProblemDetails()
            {
                Title = "Bad parameters",
                Detail = "The name of the new user cannot be empty or null",
                Instance = HttpContext.Request.Path
            });

        var userAlreadyExists = _userRepository.Exists(newUserToCreate.Name);
        if (userAlreadyExists)
            return Conflict(new ProblemDetails()
            {
                Detail = "User already exists",
                Title = "Bad parameters",
                Instance = HttpContext.Request.Path
            });

        var newUser = new User(newUserToCreate.Name);

        _userRepository.Add(newUser);

        return Created($"/users/{newUserToCreate.Name}", UserDto.FromModel(newUser));
    }
}
Enter fullscreen mode Exit fullscreen mode

Problem Details

En el API otra cosa que podemos ver es la utilización del estándar de ProblemDetails` para notificar el error producido en la ejecución del request.

Esta manera de comunicar que fue lo sucedido en el API estamos convalidando el principio de Interfaz uniforme que mencionamos anteriormente.

Lo que define este estándar es como un HTTP API debe informar lo sucedido en la aplicación cuando se devuelve un código de error 3xx, 4xx o 5xx.

Por suerte para nosotros Net Core resuelve este tema de manera automática para las excepciones no controladas, parámetros vacíos y demás controles que realiza internamente antes de ejecutar nuestro código del controller, quedando de nuestro lado implementarlo en nuestros métodos.

En el ejemplo del Controller, vemos que hay 2 maneras de implementarlo, una es llamando al método Problems y el 2do es llamado al método del HTTP Status que queremos retornar y pasándole como parámetro una instancia de la clase ProblemDetails. Ambos métodos son igualmente válidos.

Del código también se desprende que no devolvemos directamente el objeto User sino que lo mapeamos a una clase UserDto. Esto es así porque por lo general obtendremos el usuario de una base de datos por medio de Entidades de Negocio. Nunca es una buena práctica devolver una Entidad de Negocio al Frontend, sino devolver un objeto que defina un contrato entre el API y el cliente que realiza el request sin que esté acoplado al negocio.


Felicitaciones!!! 🎉🎉

Ya tenemos nuestro primer API desarrollado. En las próximas entregas iremos agregando poder a nuestro API.

No se olviden de bajar el código de Github y de dejar en los comentarios si quieren que veamos algo en particular en los próximos artículos de la serie.

Top comments (2)

Collapse
 
kiquenet profile image
Kiquenet

public void Post([FromBody] string value)

Falla en Postman con Net Core5
stackoverflow.com/questions/595665...

Collapse
 
andreslozadamosto profile image
Andres Lozada Mosto

Hola @kiquenet
Me perdí... los ejemplos de POST que puse reciben objetos complejos no string... me explicas algo mas asi hago el cambio que haya que hacer en los codigos de ejemplo?