ASP.NET Core - Apresentando e usando OData - I


Neste artigo vamos apresentar o recurso OData e como podemos usá-lo em aplicações ASP .NET Core para criar Web API Restful com recurso de consultas e filtro flexíveis.

A partir da ASP .NET Core 2.0 foi disponiblizado o suporte a OData (Open Data Protocol) como um pacote nuget( https://www.nuget.org/packages/Microsoft.AspNetCore.OData)  permitindo a criação de endpoints com OData v4.0 em múltiplas plataformas.

Mas oque é OData ?

O OData (Open Data Protocol) é um padrão OASIS aprovado pela ISO/IEC que define um conjunto de práticas recomendadas para criar e consumir Web APIs.

E por que eu deveria considerar a utilização de OData ?

A implementação dos padrões OData torna mais fácil consumir uma API pelos seus clientes e permite a criação de consultas flexíveis e legíveis por meio das convenções da URL do OData.

Usando as convenções da URL OData, você pode expor uma API muito mais limpa e genérica e permitir que o cliente da API especifique suas necessidades por meio de requisições.

Convenções OData

Vou apresentar a seguir um resumo das convenções OData para que você tenha uma visão geral do como a implementação do protocolo atua.

As convenções de URL do OData são um conjunto de comandos que você pode passar para a API por meio da cadeia de consulta de chamada HTTP.

Um URL OData é normalmente composta por três partes:

  1. A URL raiz do serviço -  É o endereço raiz da API
  2. O caminho do recurso  - Identifica o recurso que você deseja obter
  3. As opções de consulta -  É como você define para a API o formato no qual você precisa que os dados sejam entregues.


        
https://localhost:XXX/service/Categorias(1)/Produtos?$top=2$orderby=Nome

         URL raiz do serviço                  caminho do recurso       opções de consulta
 

As opções de consulta (Query Options) são usadas para definir como a nossa API vai entregar os dados e pode conter dados de pesquisa, ordenações, filtros, contagens, projeções, etc.

Destacando as opções de consulta (Query Options), pois são elas que vão tornar nossa API mais flexível, a seguir temos as principais convenções usadas para as Query Options:

Como você pode ver, muitos dos comandos são bem parecidos com o que você tem na maioria das linguagens de consulta comuns.

Para entender melhor vou propor um exemplo prático onde primeiro vou criar uma Web API usando o VS 2019 Community e a ASP .NET Core 2.2 que vai expor serviços para Alunos e Escolas onde vamos criar duas APIs, ou seja, dois controladores: AlunosController e EscolasController.

A seguir vamos implementar OData nesta API de forma bem simples de forma a não termos que alterar praticamente nada em nossos controladores e ver como o OData vai permitir criar consultas flexíveis em nossas APIs.

Criando o projeto API no VS 2019 Community

Vamos criar uma aplicação ASP .NET Core Web Application usando o template API no Visual Studio 2019 Community chamada Api_OData1;

Abra o VS 2019 Community e selecione : Create a new Project

Selecionando em Language : C#, Platform: All Platforms e em Project Type: web podemos selecionar o template ASP .NET Core Web Application:

A seguir vamos informar o nome do projeto, sua localização e nome da solução conforme abaixo:

Finalmente selecione a plataforma .NET Core e ASP .NET Core 2.2 e o template API.

Marque a opção para configurar HTTP e não vamos usar Autenticação, ao final clique em Create.

Ao final teremos um projeto Web API que iremos usar para definir nossas APIs.

A seguir instale os pacotes para usar o Entity Framework Core no projeto via menu Tools -> Manage Nuget Package For Solutions;

Selecione Browse e localize e instale o pacote : Microsoft.EntityFrameworkCore.SqlServer.

Definindo o modelo de domínio e o contexto

Vamos criar uma pasta Models no projeto e nesta pasta criar duas classes Aluno e Escola que serão nosso modelo de domínio.

A classe Aluno:

using System.ComponentModel.DataAnnotations;
namespace Api_OData1.Models
{
    public class Aluno
    {
        public int AlunoId { get; set; }
        [Required]
        [MaxLength(80)]
        public string Nome { get; set; }
        [Required]
        public int Nota { get; set; }
        public int EscolaId { get; set; }
        public Escola Escola { get; set; }
    }
}

A classe Escola:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
namespace Api_OData1.Models
{
    public class Escola
    {
        public Escola()
        {
            Alunos = new Collection<Aluno>();
        }

        public int EscolaId { get; set; }
        [Required]
        [MaxLength(80)]
        public string Nome { get; set; }
        [Required]
        [MaxLength(2)]
        public string Estado { get; set; }
        [Required]
        [MaxLength(80)]
        public string Cidade { get; set; }
        public virtual ICollection<Aluno> Alunos { get; set; }
    }
}

A seguir vamos definir na pasta Models a classe de contexto AppDbContext que herda de DbContext.

using Microsoft.EntityFrameworkCore;
namespace Api_OData1.Models
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {}
        public DbSet<Escola> Escolas { get; set; }
        public DbSet<Aluno> Alunos { get; set; }
    }
}

Agora precisamos registrar o contexto como um serviço, definir o provedor do banco de dados e a string de conexão no método ConfigureServices da classe Startup:

      public void ConfigureServices(IServiceCollection services)
      {
          var connection = @"Data Source=Macoratti;Initial Catalog=EstudoDataBase;Integrated Security=True";
          services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connection));
            services.AddControllers().AddNewtonsoftJson();
      }

Com a configuração pronta podemos aplicar o Migrations para gerar o banco de dados e as tabelas no SQL Server.

Abrindo uma janela do Package Manager Console e digitando o comando : add-migration Inicial

Este comando cria o script de migração na pasta Migrations.

Para criar o banco de dados e as tabelas digite o comando : update-database

Após o processamento do comando acima, vamos abrir o SQL Server Management Studio, e assim veremos o banco de dados e as tabelas criadas e o relacionamento entre elas definido:

As tabelas foram criadas mas estão vazias e temos que incluir alguns dados de teste.

Para popular as tabelas do banco de dados vamos criar uma nova migração digitando o comando :
add-migration PopulaDB

Isso vai criar um script com os métodos Up e Down vazios.

No método Up() vamos incluir os comandos SQL - Insert Into - para incluir dados nas tabelas Alunos e Escolas.

No método Down() vamos usar o comando - Delete From -  para deletar dados  e assim desfazer a ação anterior:

Abaixo vemos o código completo nos métodos:

using Microsoft.EntityFrameworkCore.Migrations;

namespace Api_OData1.Migrations
{
    public partial class PopulaDB : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            //escolas

            migrationBuilder.Sql("Insert Into Escolas(nome, estado, cidade) Values('Escola Técnica Federal','SP','Campinas')");
            migrationBuilder.Sql("Insert Into Escolas(nome, estado, cidade) Values('Escola Industrial Paulista','SP','Piracicaba')");
            migrationBuilder.Sql("Insert Into Escolas(nome, estado, cidade) Values('Instituto Sobral Filho','SP','Sorocaba')");
            migrationBuilder.Sql("Insert Into Escolas(nome, estado, cidade) Values('Instituto Getulio Vargas','MG','Itajuba')");
            migrationBuilder.Sql("Insert Into Escolas(nome, estado, cidade) Values('Escola Técnica de Santos','SP','Santos')");

            //alunos
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Pedro Escobar',8,1)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Marina Silva',7,1)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Ricardo Rocha',6,5)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Amanda Lima',9,1)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('José Santos',5,2)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('José Bueno Silva',7,2)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Antônio Sanches',6,3)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Marisa Ribeiro',8,3)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Bianca Lima Sá',9,4)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Jessica Silva',6,4)");
            migrationBuilder.Sql("Insert Into Alunos(nome, nota, escolaid) Values('Jefferson Rodrigues',7,5)");
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            //alunos
            migrationBuilder.Sql("Delete From Escolas");
            //escolas
            migrationBuilder.Sql("Delete from Alunos");
        }
    }
}

Para executar o script acima digite na janela do Package Manager Console o comando : update-database

Pronto. Já temos o nosso banco de dados criado e as tabelas com dados de teste.

Agora temos tudo configurado e podemos criar os controladores no projeto.

Criando os controladores AlunosController e EscolasController

Vamos agora criar os controladores que representam nossas APIs para poder gerenciar os alunos e os escolas.

Clique com o botão direito do mouse sobre a pasta Controllers e selecione a opção Add Controller;

Na janela Add Scaffold escolha a opção API Controller with actions using Entity Framework;

Na janela a seguir informe os dados conforme mostrado na figura abaixo:

Após clicar no botão Add, nosso controlador AlunosController será criado na pasta Controllers.

Repita o procedimento para criar o controlador EscolasController:

Ao final teremos os nossos controladores com os métodos Actions definindo as operações CRUD usando os verbos http Get, Post, Put e Delete e podemos testar a nossa API.

Note que em ambos os controladores foram incluidos os atributos Route e ApiController:

[Route("api/[controller]")]
[ApiController]

Executando o projeto e acessando os endpoints :

Teremos o resultado abaixo no navegador :

1- api/escolas

2- api/alunos

Tudo bem, esse é o comportamento normal de uma API Rest.

Agora perceba que não temos muita flexibilidade para realizar consultas. Por exemplo, e seu quiser apenas retornar os nomes dos alunos ? e se que quiser filtrar apenas os alunos com nota maior que 7 ?

Na abordagem atual eu teria que criar consultas definindo novos endpoints para acessar os recursos.

Aqui é que entra o OData, e, na próxima parte do artigo veremos como implementar os seus recursos com apenas 5 linhas de código.

"Arrependei-vos, pois, e convertei-vos, para que sejam apagados os vossos pecados, e venham assim os tempos do refrigério pela presença do Senhor,
E envie ele a Jesus Cristo, que já dantes vos foi pregado."

Atos 3:19-20

Referências:


José Carlos Macoratti