Fale com a gente no WhatsApp Fale com a gente no WhatsApp
Fale com a gente no WhatsApp

C# ASP .NET .NET Core

Adicionando OData em uma API ASP.NET Core

OData é um padrão de boas práticas para a criação de API. Veja os recursos que este padrão fornece para uma API ASP.NET Core.

há 3 anos 6 meses

Formação Desenvolvedor C#
Conheça a formação em detalhes

Imagine a situação, é necessário criar uma API que será consumida pela versão web e versão mobile da aplicação. A versão web deve mostrar todas as informações dos dados, já a versão mobile irá exibir apenas as informações mais importantes. Neste cenário, o desenvolvedor pode optar por endpoints distintos ou mesmo necessitar implementar filtros complexos. Entretanto, todo este processo pode ser facilitado com o auxílio do OData.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso C# (C Sharp) - Introdução ao ASP.NET Core
Conhecer o curso

OData

Open Data Protocol (OData) é um padrão que define boas práticas para a criação e consumo de APIs RESTful. Ele ajuda o desenvolvedor a focar nas regras de negócio da API, sem ter que se preocupar com a implementação de aspectos técnicos como media types, formatos de payload, opções de query, etc.

O seu uso requer apenas alterações pontuais no código, como veremos à seguir.

Integrando o OData a uma API ASP.NET Core

Para exemplificar o uso do OData criaremos uma API ASP.NET Core simples:

dotnet new webapi -n PessoaAPI

Que retornará apenas um recurso:

public class Pessoa
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Idade { get; set; }

    public override string ToString() => Id + " - " + Nome + " - " + Idade;
}

Salvo na memória:

public class PessoaRepository
{
    private static Dictionary<int, Pessoa> pessoas = new Dictionary<int, Pessoa>();

    public List<Pessoa> GetAll(){
        return pessoas.Values.ToList();
    }

    public Pessoa Get(int id){
        return pessoas.GetValueOrDefault(id);
    }

    public void Add(Pessoa pessoa){
        pessoas.Add(pessoa.Id, pessoa);
    }

    public void Edit(Pessoa pessoa){
        pessoas.Remove(pessoa.Id);
        pessoas.Add(pessoa.Id, pessoa);
    }

    public void Delete(int id){
        pessoas.Remove(id);
    }
}

Com isso, terá apenas um controller:

[ApiController]
[Route("[controller]")]
public class PessoaController : ControllerBase
{
    private readonly ILogger<PessoaController> _logger;
    private readonly PessoaRepository _repository;

    public PessoaController(ILogger<PessoaController> logger, PessoaRepository repository)
    {
        _logger = logger;
        _repository = repository;
    }

    [HttpGet]
    public IEnumerable<Pessoa> Get()
    {
        var pessoas = _repository.GetAll();
        return pessoas;
    }

    [HttpGet("{id}")]
    [ProducesResponseType(201)]
    [ProducesResponseType(400)]
    public ActionResult<Pessoa> Get(int id)
    {
        var item = _repository.Get(id);
        if(item != null)
            return item;
        
        return NotFound();
    }

    [HttpPost]
    [ProducesResponseType(201)]
    [ProducesResponseType(400)]
    public ActionResult<Pessoa> Post([FromBody] Pessoa value)
    {
        try
        {
            _repository.Add(value);
            return value;
        }
        catch(Exception)
        {
            return BadRequest();
        }
    }

    [HttpPut("{id}")]
    [ProducesResponseType(201)]
    [ProducesResponseType(400)]
    public ActionResult<Pessoa> Put(int id, [FromBody] Pessoa value)
    {
        try
        {
            value.Id = id;
            _repository.Edit(value);
            
            return value;
        }
        catch(Exception)
        {
            return BadRequest();
        }
    }

    [HttpDelete("{id}")]
    [ProducesResponseType(200)]
    [ProducesResponseType(400)]
    public ActionResult Delete(int id)
    {
        try
        {   
            _repository.Delete(id);
            return Ok(new { Description = "Item removed" });
        }
        catch(Exception)
        {
            return BadRequest();
        }
    }
}

Para integrar o OData na aplicação, basta adicionar no projeto a dependência Microsoft.AspNetCore.OData:

dotnet add package Microsoft.AspNetCore.OData

Também adicione a dependência Microsoft.AspNetCore.Mvc.NewtonsoftJson:

dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

No método ConfigureServices, o serviço do OData deve ser adicionado:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<PessoaRepository>();
    services.AddControllers()
            .AddNewtonsoftJson();

    //Adiciona o OData
    services.AddOData();

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "PessoaAPI", Version = "v1" });
    });

}

Note que o AddNewtonsoftJson() não é adicionado. Isso é necessário porque o OData não funciona corretamente com o System.Text.Json.

E no método Configure, na configuração dos endpoints da aplicação, é necessário configurar o middleware do OData:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.EnableDependencyInjection();
    endpoints.Select().Filter().OrderBy();
});

O EnableDependencyInjection() permite a sobrescrita das URLs, desta forma não é necessário especificar uma separada para o OData.

O OData pode aplicar filtro, seleção, ordenação e outros recursos ao retorno da API, acima são habilitados a seleção, filtro e ordenação.

Entretanto, para que o OData funcione, por fim, é necessário adicionar o atributo EnableQuery nos métodos dos controllers onde ele será aplicado:

[HttpGet]
[EnableQuery]
public IEnumerable<Pessoa> Get()
{
    var pessoas = _repository.GetAll();
    return pessoas;
}

Caso tenha implementado Entity Framework e o DbContext é acessado no controller, substitua o IEnumerable pelo IQueryable. Assim, o OData irá utilizar as capacidades do Entity Framework para aplicar as seleções.

Testando o OData na aplicação

Com o OData implementado na aplicação podemos testá-lo, para isso, a aplicação precisa conter alguns dados:

Lista dos dados da API sem nenhum filtro

Neles, é possível aplicar uma seleção com o atributo $select na querystring do endpoint (pessoa?$select=nome):

Lista dos dados da API com o filtro select

Pode ser informado mais de uma propriedade, separando elas por vírgula (e.g.pessoa?$select=nome,idade).

O mesmo processo se aplica a ordenação, com o uso do atributo $orderBy (pessoa?$orderBy=idade):

Lista dos dados da API com o filtro orderBy

Já para o filtro, pode ser aplicado uma das condicões abaixo:

Condição Descrição Exemplo
eq Igual $filter=idade eq 18
ne Diferente $filter=idade ne 18
gt Maior $filter=idade gt 18
ge Maior ou igual $filter=idade ge 18
lt Menor $filter=idade lt 18
le Menor ou igual $filter=idade le 18
and E lógico $filter=idade gt 18 and idade lt 30
or Ou lógico $filter=idade gt 18 or idade lt 30
not Negação lógica $filter=not endswith(nome,‘Silva’)

Por exemplo (pessoa?$filter=idade gt 30):

Lista dos dados da API com o filtro filter

Por fim, também é possível combinar mais de um atributo na querystring (pessoa?$select=nome&$filter=not endswith(nome,'Silva')):

Lista dos dados da API com os filtros de select e filter

C# (C Sharp) - APIs REST com ASP.NET Web API
Curso C# (C Sharp) - APIs REST com ASP.NET Web API
Conhecer o curso

Conclusão

Este artigo apenas introduz o OData, ele possui muitos recursos e merece sua atenção caso esteja trabalhando com APIs RESTful no ASP.NET Core.

Caso a API retorne uma grande quantidade de dados, a ordenação ($orderBy) e filtro ($filter) podem ser muito custosas, então nesta situação é necessário analisar se suas habilitações compensam. Se não, recomenda-se deixá-los inativos.

Nos próximos artigos abordei mais recursos do OData, até lá!

Autor(a) do artigo

Wladimilson M. Nascimento
Wladimilson M. Nascimento

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

Todos os artigos

Artigos relacionados Ver todos