← Back to list

Automatizando HATEOAS no .NET 8 com Reflection — APIs REST realmente inteligentes

“Uma API REST sem HATEOAS é como um mapa sem legendas.”  — Roy Fielding (autor da tese REST, provavelmente teria dito isso 😅)

Leandro Vilas Boas (leandrovboas) · 2025-11-04 15:11 · 5 claps · 3.0 min read
#dotnet #hateoas #rest-api #api #software-development
Open on Medium ↗
Wiki topics: SEO · SEO & SEM

Automatizando HATEOAS no .NET 8 com Reflection — APIs REST realmente inteligentes

“Uma API REST sem HATEOAS é como um mapa sem legendas.” — Roy Fielding (autor da tese REST, provavelmente teria dito isso 😅)

🧭 Introdução

Quando falamos em REST de verdade, o termo HATEOAS (Hypermedia As The Engine Of Application State) é inevitável.

Em resumo: cada resposta da sua API deve conter links que guiam o cliente sobre o que ele pode fazer em seguida — sem precisar conhecer as rotas de antemão.

Exemplo simples:

{
  "id": 10,
  "nome": "Projeto X",
  "status": "Ativo",
  "_links": {
    "self": { "href": "/api/projetos/10", "method": "GET" },
    "update": { "href": "/api/projetos/10", "method": "PUT" },
    "delete": { "href": "/api/projetos/10", "method": "DELETE" }
  }
}

Até aqui tudo bem. O problema é que, em APIs grandes, gerar esses links manualmente em cada controller é repetitivo e propenso a erro.

Então… e se a gente automatizasse tudo? Sim: gerar os links de forma dinâmica, via reflection, sem escrever um único builder manual.

🧩 Estrutura básica de um recurso HATEOAS

Começamos definindo um contrato simples para os recursos que vão suportar hypermedia.

public interface IHateoasResource
{
    List<Link> Links { get; set; }
}

public class Link
{
    public string Href { get; set; } = string.Empty;
    public string Rel { get; set; } = string.Empty;
    public string Method { get; set; } = string.Empty;
}

E um exemplo de modelo:

public class ProjetoResource : IHateoasResource
{
    public int Id { get; set; }
    public string Nome { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
    public List<Link> Links { get; set; } = new();
}

⚙️ Gerando os links automaticamente (a mágica via Reflection)

Aqui entra a parte interessante. Vamos usar o EndpointDataSource do ASP.NET Core para inspecionar todos os endpoints da aplicação e gerar automaticamente os links com base nas rotas registradas.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using System.Reflection;

public class AutoHateoasFilter : IActionFilter
{
    private readonly LinkGenerator _linkGenerator;
    private readonly EndpointDataSource _endpointDataSource;

    public AutoHateoasFilter(LinkGenerator linkGenerator, EndpointDataSource endpointDataSource)
    {
        _linkGenerator = linkGenerator;
        _endpointDataSource = endpointDataSource;
    }

    public void OnActionExecuting(ActionExecutingContext context) { }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Result is ObjectResult objectResult && objectResult.Value is IHateoasResource resource)
        {
            var httpContext = context.HttpContext;
            var controllerName = context.Controller.GetType().Name.Replace("Controller", "");
            var idProp = resource.GetType().GetProperty("Id");

            object? idValue = idProp?.GetValue(resource);

            var endpoints = _endpointDataSource.Endpoints
                .OfType<RouteEndpoint>()
                .Where(e => e.DisplayName != null && e.DisplayName.Contains(controllerName))
                .ToList();

            foreach (var endpoint in endpoints)
            {
                var httpMethod = endpoint.Metadata
                    .OfType<HttpMethodMetadata>()
                    .FirstOrDefault()?.HttpMethods.FirstOrDefault();

                if (httpMethod is null) continue;

                var pattern = endpoint.RoutePattern.RawText ?? string.Empty;

                string href = pattern.Contains("{id}") && idValue is not null
                    ? pattern.Replace("{id}", idValue.ToString())
                    : pattern;

                resource.Links.Add(new Link
                {
                    Href = href,
                    Rel = GerarRel(endpoint),
                    Method = httpMethod
                });
            }

            objectResult.Value = resource;
        }
    }

    private static string GerarRel(RouteEndpoint endpoint)
    {
        var nome = endpoint.DisplayName?
            .Split('.')
            .Last()
            .Split(' ')
            .FirstOrDefault() ?? string.Empty;

        return nome.Replace("Controller", "").ToLowerInvariant();
    }
}

Esse filtro é executado automaticamente após cada ação do controller. Ele busca as rotas associadas ao controller atual, lê seus métodos (GET, PUT, etc.) e monta dinamicamente a lista de links.

🧱 Registrando o filtro globalmente

No Program.cs, registramos o filtro para que ele seja aplicado em toda a API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.Filters.Add<AutoHateoasFilter>();
});

var app = builder.Build();
app.MapControllers();
app.Run();

🧩 Exemplo de Controller (sem código HATEOAS)

Olha como o controller fica limpo:

[ApiController]
[Route("api/[controller]")]
public class ProjetosController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var projeto = new ProjetoResource { Id = id, Nome = "Projeto X", Status = "Ativo" };
        return Ok(projeto);
    }

    [HttpPut("{id}")]
    public IActionResult Update(int id, ProjetoResource projeto)
    {
        return NoContent();
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        return NoContent();
    }

    [HttpGet("{id}/tarefas")]
    public IActionResult GetTarefas(int id)
    {
        return Ok(new[] { "Tarefa 1", "Tarefa 2" });
    }
}

✅ Resultado final

Ao chamar:

GET /api/projetos/1

A resposta vem automaticamente assim:

{
  "id": 1,
  "nome": "Projeto X",
  "status": "Ativo",
  "links": [
    { "href": "api/Projetos/1", "rel": "getbyid", "method": "GET" },
    { "href": "api/Projetos/1", "rel": "update", "method": "PUT" },
    { "href": "api/Projetos/1", "rel": "delete", "method": "DELETE" },
    { "href": "api/Projetos/1/tarefas", "rel": "gettarefas", "method": "GET" }
  ]
}

Tudo automático, sem precisar criar classes auxiliares, builders ou duplicar lógica de rotas.

⚡ Por que isso é poderoso

✅ Nenhum código HATEOAS manual por controller ✅ Descobre automaticamente todos os endpoints da aplicação ✅ Escala bem com novas rotas e controladores ✅ Ideal para APIs REST realmente autodocumentadas ✅ Pode ser combinado com Swagger ou OpenAPI para geração completa de documentação dinâmica

🎯 Conclusão

Com algumas linhas de reflection e o poder do EndpointDataSource, conseguimos um HATEOAS totalmente automático e sem acoplamento. Essa abordagem deixa tua API muito mais expressiva, autodocumentada e pronta para crescer com segurança.

💡 Stack: .NET 8, ASP.NET Core, REST, Reflection, Clean Architecture


메타데이터
post_id
3ef79d7cfc98
slug
automatizando-hateoas-no-net-8-com-reflection-apis-rest-realmente-inteligentes-3ef79d7cfc98
url
https://medium.com/@leandrovboas/automatizando-hateoas-no-net-8-com-reflection-apis-rest-realmente-inteligentes-3ef79d7cfc98
canonical_url
https://medium.com/@leandrovboas/automatizando-hateoas-no-net-8-com-reflection-apis-rest-realmente-inteligentes-3ef79d7cfc98
author_url
https://medium.com/@leandrovboas
status
ok
fetched_at
2026-06-09 15:37:30