ASP .NET Core - Implementando HATEOAS


Neste artigo veremos uma implementação da funcionalidade HATEOAS em uma API ASP .NET Core.

Vamos continuar a primeira parte do artigo onde eu apresentei o HATEOAS e agora mostrar uma forma de implementar esta funcionalidade em uma API ASP .NET Core.

Vou usar um projeto Web API já pronto para focar apenas na implementação HATEOAS, e, vou fazer uma implementação bem simples.

Nossa Web API ClientesAPI expõe serviços para consultar, incluir, alterar e excluir informações de clientes e define um controlador chamado ClientesController que usa o EF Core para realizar o acesso e a persistência dos dados.

A seguir vamos fazer a nossa implementação realizando as seguintes tarefas:

  1. Criar uma classe chamada LinkDTO onde vamos definir a referência, o relacionamento e o método que iremos exibir no JSON como parte das propriedades do Cliente;
  2. Criar uma classe Recurso que define uma lista de Links;
  3. Alterar o nome dos métodos Action GET, PUT, POST e DELETE do controlador ClientesController;
  4. Fazer com que nosso modelo de domínio Cliente herda da classe Recurso;
  5. Implementar no controlador o método GeraLinks() para criar os links que serão exibidos no JSON;

Implementando HATEOAS

Abra o projeto ClientesAPI no VS 2019 Community e na pasta Models do projeto crie a classe LinkDTO:

namespace ClientesApi.Model
{
    public class LinkDTO
    {
        public int Id { get; private set; }    
        public string Href { get; private set; }
        public string Rel { get; private set; }
        public string Metodo { get; private set; }
        public LinkDTO(string href, string rel, string metodo)
        {
            Href = href;
            Rel = rel;
            Metodo = metodo;
        }
    }
}

A seguir ainda na pasta Models crie a classe Recurso :

    public class Recurso
    {
        public List<LinkDTO> Links { get; set; } = new List<LinkDTO>();
    }

Altere a classe Cliente de forma a que passe herdar da classe Recurso:

    public class Cliente : Recurso
    {
        public int ClienteId { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
        public string Telefone { get; set; }
        public string Endereco { get; set; }
    }

Nossa implementação vai usar os recursos da classe UrlHelper e para isso vamos definir um serviço IUrlHelper para poder injetar uma instância do serviço no construtor do controlador.

Para isso vamos registrar o serviço no método ConfigureServices da classe Startup:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ClienteDbContext>(opt => opt.UseMySql(Configuration.GetConnectionString("ClientesConnectionString")));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //Implementação de IUrlHelper para injetar a instância de UrlHelper
            services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
                .AddScoped<IUrlHelper>(x => x.GetRequiredService<IUrlHelperFactory>()
                .GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));

        }

Agora podemos nos concentrar no controlador ClientesController, onde vamos injetar uma instância de UrlHelper e definir o método GerarLinks().

1- Injetando o serviço UrlHelper no construtor

    ...
    [Route("api/[controller]")]
    [ApiController]
    public class ClientesController : ControllerBase
    {
        private readonly ClienteDbContext _context;
        private readonly IUrlHelper _urlHelper;
        public ClientesController(ClienteDbContext context, IUrlHelper urlHelper)
        {
            _context = context;
            _urlHelper = urlHelper;
        }
   ...

Agora vamos definir o método GerarLinks():

private void GerarLinks(Cliente cliente)
{
  cliente.Links.Add(new LinkDTO(_urlHelper.Link(nameof(GetCliente), new { id = cliente.ClienteId }), rel: "self", metodo: "GET"));

  cliente.Links.Add(new LinkDTO(_urlHelper.Link(nameof(PutCliente), new { id = cliente.ClienteId }), rel: "update-cliente", metodo: "PUT"));

  cliente.Links.Add(new LinkDTO(_urlHelper.Link(nameof(DeleteCliente), new { id = cliente.ClienteId }), rel: "delete-cliente", metodo: "DELETE"));

 }

Este método recebe uma instância de Cliente e usa os recursos da classe UrlHelper para gerar os links para os métodos GET, PUT e DELETE.

Para concluir vamos ter que incluir um nome qualificado para cada método Action conforme definimos no método acima.

Para testar vamos incluir uma chamada a GerarLinks no método Action  Get(int id)GerarLinks(cliente);  

Executando o projeto e acionando a url : https://localhost:44345/api/clientes/1 vemos o resultado conforme mostrado a seguir:

Falta implementar a geração dos links para a lista de clientes onde vamos ter que gerar o links para os métodos Action e o link que indica a possibilidade para criar clientes em cada cliente.

Para isso vamos criar na pasta Models a classe ColecaoRecursos<T> que herda da classe Recurso :

using System.Collections.Generic;

namespace ClientesApi.Model
{
    public class ColecaoRecursos<T> : Recurso where T : Recurso
    {
        public List<T> Valores { get; set; }
        public ColecaoRecursos(List<T> valores)
        {
            Valores = valores;
        }
    }
}

Agora no controlador ClientesController vamos alterar o método Action Get() conforme abaixo:

 // GET: api/Clientes
 [HttpGet(Name = nameof(GetClientes))]

 public async Task<ActionResult<ColecaoRecursos<Cliente>>> GetClientes()
 {
    var clientes = await _context.Clientes.ToListAsync();

    clientes.ForEach(c => GerarLinks(c));

   var resultado = new ColecaoRecursos<Cliente>(clientes);
   resultado.Links.Add(new LinkDTO(_urlHelper.Link(nameof(GetClientes), new { }), rel: "self", metodo: "GET"));
   resultado.Links.Add(new LinkDTO(_urlHelper.Link(nameof(PostCliente), new { }), rel: "create-cliente", metodo: "POST"));

    return resultado;
 }

Executando o projeto novamente agora veremos o resultado a seguir para a url: https://localhost:44345/api/clientes

Pegue o código do projeto neste link: ClientesApi_hateoas.zip

"Porque onde há inveja e espírito faccioso aí há perturbação e toda a obra perversa.
Mas a sabedoria que do alto vem é, primeiramente pura, depois pacífica, moderada, tratável, cheia de misericórdia e de bons frutos, sem parcialidade, e sem hipocrisia."

Tiago 3:16,17

Referências:


José Carlos Macoratti