Blazor - CRUD usando o Google Cloud Firestore - II


Hoje vamos continuar a criação da aplicação Blazor que realiza um CRUD básico usando o banco de dados Cloud Firestore.

Continuando a primeira parte do artigo vamos definir o modelo de domínio,  a camada de acesso aos dados e a API.

Criando o modelo de dominio

Vamos criar o nosso modelo de domínio no projeto Shared.

Inclua uma pasta Models neste projeto e nesta pasta crie a classe Aluno com o código abaixo:

using Google.Cloud.Firestore;
namespace Blazor_Firestorer.Shared.Models
{
    [FirestoreData]
    public class Aluno
    {
        public string AlunoId { get; set; }
        [FirestoreProperty]
        public string Nome { get; set; }
        [FirestoreProperty]
        public string Email { get; set; }
        [FirestoreProperty]
        public string Cidade { get; set; }
        [FirestoreProperty]
        public string Sexo { get; set; }
    }
}

Observe que decoramos classe Aluno com o atributo [FirestoreData]. Isso nos permitirá mapear esse objeto para a coleção do Firestore. Apenas aquelas propriedades da classe, que são marcadas com o atributo [FirestoreProperty], são consideradas quando salvamos o documento em nossa coleção.

Não precisamos salvar o AlunoId em nosso banco de dados, pois ele é gerado automaticamente. Ao obter os dados, vincularemos o ID do documento gerado automaticamente à propriedade AlunoId.

Da mesma forma, usaremos a propriedade Data para vincular a data de criação da coleção enquanto buscamos o registro. Usaremos essa propriedade Data para classificar a lista de funcionários pela data de criação do arquivo. Portanto, não aplicamos o atributo [FirestoreProperty] a essas duas propriedades.

A seguir vamos criar a classe Cidade definindo a propriedade Nome:

using Google.Cloud.Firestore;
namespace Blazor_Firestorer.Shared.Models
{
    [FirestoreData]
    public class Cidade
    {
        public string Nome { get; set; }
    }
}

Essa classe será usada para popular o dropdownlist exibindo a lista de cidades para o usuário.

Criando a camada de acesso a dados

Vamos criar a camada de acesso a dados no projeto Server.

Crie uma pasta Data no projeto Server e a seguir crie a interface IAlunoDAL onde iremos definir o contrato com os métodos para acesso persistência dos dados:

using Blazor_Firestorer.Shared.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Blazor_Firestorer.Server.Data
{
    public interface IAlunoDAL
    {
        public Task<List<Aluno>> GetAlunos();
        public void AddAluno(Aluno employee);
        public void UpdateAluno(Aluno employee);
        public Task<Aluno> GetAluno(string id);
        public void DeleteAluno(string id);
        public Task<List<Cidade>> GetCidades();
    }
}

A seguir crie a classe AlunoDAL que deve herdar de IAlunoDAL e implementar o contrato definido conforme o código abaixo:

using Blazor_Firestorer.Shared.Models;
using Google.Cloud.Firestore;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Blazor_Firestorer.Server.Data
{
    public class AlunoDAL : IAlunoDAL
    {
        string projectId;
        FirestoreDb fireStoreDb;
        public AlunoDAL()
        {
            string arquivoApiKey = 
 @"D:\_blazor\Blazor_Firestorer\Blazor_Firestorer\Server\FirestoreApiKey\blazorcrudfirestore-1565827c5156.json";
            Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", arquivoApiKey );
            projectId = "blazorcrudfirestore";
            fireStoreDb = FirestoreDb.Create(projectId);
        }
        public async Task<List<Aluno>> GetAlunos()
        {
            try
            {
                Query alunoQuery = fireStoreDb.Collection("alunos");
                QuerySnapshot alunoQuerySnapshot = await alunoQuery.GetSnapshotAsync();
                List<Aluno> listaAluno = new List<Aluno>();
                foreach (DocumentSnapshot documentSnapshot in alunoQuerySnapshot.Documents)
                {
                    if (documentSnapshot.Exists)
                    {
                        Dictionary<string, object> city = documentSnapshot.ToDictionary();
                        string json = JsonConvert.SerializeObject(city);
                        Aluno novoAluno = JsonConvert.DeserializeObject<Aluno>(json);
                        novoAluno.AlunoId = documentSnapshot.Id;
                        listaAluno.Add(novoAluno);
                    }
                }
                List<Aluno> listaAlunoOrdenada = listaAluno.OrderBy(x => x.Nome).ToList();
                return listaAlunoOrdenada;
            }
            catch(Exception ex)
            {
                var erro = ex.Message;
                throw;
            }
        }
        public async Task<Aluno> GetAluno(string id)
        {
            try
            {
                DocumentReference docRef = fireStoreDb.Collection("alunos").Document(id);
                DocumentSnapshot snapshot = await docRef.GetSnapshotAsync();
                if (snapshot.Exists)
                {
                    Aluno aluno = snapshot.ConvertTo<Aluno>();
                    aluno.AlunoId = snapshot.Id;
                    return aluno;
                }
                else
                {
                    return new Aluno();
                }
            }
            catch
            {
                throw;
            }
        }
        public async void AddAluno(Aluno aluno)
        {
            try
            {
                CollectionReference colRef = fireStoreDb.Collection("alunos");
                await colRef.AddAsync(aluno);
            }
            catch
            {
                throw;
            }
        }
        public async void UpdateAluno(Aluno aluno)
        {
            try
            {
                DocumentReference alunoRef = fireStoreDb.Collection("alunos").Document(aluno.AlunoId);
                await alunoRef.SetAsync(aluno, SetOptions.Overwrite);
            }
            catch
            {
                throw;
            }
        }
        public async void DeleteAluno(string id)
        {
            try
            {
                DocumentReference alunoRef = fireStoreDb.Collection("alunos").Document(id);
                await alunoRef.DeleteAsync();
            }
            catch
            {
                throw;
            }
        }
        public async Task<List<Cidade>> GetCidades()
        {
            try
            {
                Query cidadesQuery = fireStoreDb.Collection("cidades");
                QuerySnapshot cidadesQuerySnapshot = await cidadesQuery.GetSnapshotAsync();
                List<Cidade> listaCidades = new List<Cidade>();
                foreach (DocumentSnapshot documentSnapshot in cidadesQuerySnapshot.Documents)
                {
                    if (documentSnapshot.Exists)
                    {
                        Dictionary<string, object> city = documentSnapshot.ToDictionary();
                        string json = JsonConvert.SerializeObject(city);
                        Cidade novaCidade = JsonConvert.DeserializeObject<Cidade>(json);
                        listaCidades.Add(novaCidade);
                    }
                }
                return listaCidades;
            }
            catch
            {
                throw;
            }
        }     
    }
}

Aqui estamos implementando os métodos:

Cabe destacar que no construtor da classe definimos o caminho do arquivo JSON na variável arquivoApiKey que contém as informações das configurações do projeto no Firestore. 

public AlunoDAL()
{
            string arquivoApiKey = 
 @"D:\_blazor\Blazor_Firestorer\Blazor_Firestorer\Server\FirestoreApiKey\blazorcrudfirestore-1565827c5156.json";
            Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", arquivoApiKey );
            projectId = "blazorcrudfirestore";
            fireStoreDb = FirestoreDb.Create(projectId);
 }

Definimos também a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS que vai obter as informações do arquivo JSON.

A variável projectId deve ser definida como o ID do seu projeto Firestore e será usada para criar a instância de acesso ao Firestore na nuvem.

Essa instância, fireStoreDb , será usada para realizar todas as operações de acesso ao Firestore.

Não podemos esquecer de registrar o serviço definindo o código abaixo no método ConfigureServices da classe Startup do projeto Server:

   public void ConfigureServices(IServiceCollection services)
   {
            services.AddTransient<IAlunoDAL, AlunoDAL>();
            services.AddControllersWithViews();
            services.AddRazorPages();
    }

Criando o controlador AlunosController

Vamos criar agora a nossa API de acesso aos dados no projeto Server.

Na pasta Controllers crie o controlador AlunosController e defina o código abaixo neste arquivo:

using Blazor_Firestorer.Server.Data;
using Blazor_Firestorer.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Blazor_Firestorer.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AlunosController : ControllerBase
    {
        private readonly IAlunoDAL _aluno;
        public AlunosController(IAlunoDAL aluno)
        {
            _aluno = aluno;
        }
        [HttpGet]
        public Task<List<Aluno>> Get()
        {
            return _aluno.GetAlunos();
        }
        [HttpGet("{id}")]
        public Task<Aluno> Get(string id)
        {
            return _aluno.GetAluno(id);
        }
        [HttpPost]
        public void Post([FromBody] Aluno aluno)
        {
            _aluno.AddAluno(aluno);
        }
        [HttpPut]
        public void Put([FromBody] Aluno aluno)
        {
            _aluno.UpdateAluno(aluno);
        }
        [HttpDelete("{id}")]
        public void Delete(string id)
        {
            _aluno.DeleteAluno(id);
        }
        [HttpGet("GetCidades")]
        public Task<List<Cidade>> GetCidades()
        {
            return _aluno.GetCidades();
        }
    }
}

Note que o código está bem enxuto e já podemos partir para a definição da interface com o usuário no projeto Client.

Na próxima parte do artigo vamos criar os componentes Razor para acessar e persistir dados realizando o CRUD.

"Por isso não desfalecemos; mas, ainda que o nosso homem exterior se corrompa, o interior, contudo, se renova de dia em dia."
2 Coríntios 4:16

Referências:


José Carlos Macoratti