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 😅)
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