Desenvolvimento

20 mar, 2018

Utilizando o Azure Storage

Publicidade

Introdução

Esse artigo tem como objetivo demonstrar de maneira prática, como armazenar dados estruturados e não estruturados utilizando o Blob e o Tables, ambas funcionalidades do Azure Storage.

Porém, antes de colocarmos a mão na massa, vamos entender de maneira breve o que é cada uma dessas funcionalidades.

Blob File

O Blob é um serviço geralmente utilizado para armazenar grandes quantidades de dados não estruturados, tais como texto ou binários que podem ser acessados através de uma requisição HTTP ou HTTPS.

Tables

Tables é o serviço que armazena dados NoSQL na nuvem de maneira estruturada, fornecendo um repositório chave/atributo. Vale lembrar que as tables não possuem schema.

Bom, agora sem mais delongas, vamos para a parte prática.

Criando nosso Storage no Azure

Nessa primeira parte, vamos acessar o portal do Microsoft Azure através do link: https://portal.azure.com/ e efetuar o nosso login.

Feito isso, vamos utilizar a busca do Azure para achar o serviço Storage Account, conforme nos mostra a figura seguir e selecioná-lo.

Agora, vamos preencher o Name e o Resource Group (caso já possua um, não é necessário criar outro) para criar nossa Storage Account, conforme nos mostra a figura abaixo:

Depois de finalizado o processo de criação da nossa Storage Account, clique sobre ela e em seguida, clique na opção Access Keys que fica logo abaixo de Settings. Feito isso, copie sua Connection String clicando no botão ao lado, conforme a seguir.

Agora vamos começar a segunda parte, que é no Visual Studio.


Criando o Projeto

Vamos criar um projeto do tipo ASP.NET Core Web.API em branco, conforme a figura acima.

Definindo as configurações do Projeto

Clique sobre o projeto que acabamos de criar e clique em propriedades (Windows) ou Options (Mac), acesse as configurações de execução Default e crie uma nova variável de ambiente – que eu vou chamar de CONNECTION_AZURE – e como valor, vamos passar aquela Connection String que copiamos do nosso ambiente Azure passos atrás, conforme mostra a próxima figura.

Depois, na aba ASP.NET Core vamos definir nossa url padrão como index.html, e em seguida vamos clicar em OK.

Agora vamos voltar a nossa solution, criar uma pasta chamada Entities e dentro dela vamos criar uma classe chamada Pessoa, com o seguinte código:

using Microsoft.WindowsAzure.Storage.Table;

namespace Imasters.Entities
{
    public class Pessoa : TableEntity
    {
        public Pessoa() { }

        public Pessoa(string nome, string email)
        {
            this.PartitionKey = nome;
            this.RowKey = email;
        }

        public string FotoUrl { get; set; }
    }
}

Observe que precisamos herdar a classe TableEntity para mapearmos os objetos para o C#, além disso criamos um construtor personalizado usando nome e e-mail como chave e atributo para que sejam únicos na tabela.

Agora vamos criar uma pasta chamada Interfaces, e dentro dela criar a interface IImasterInterface.cs com o código abaixo:

using System.Collections.Generic;
using System.Threading.Tasks;
using Imasters.Entities;
using Microsoft.AspNetCore.Http;

namespace Imasters.Interfaces
{
    public interface IImasterInterface
    {
        Task<string> UploadBlobFileAsync(IFormFile file);
        Task Save(string name, string email, string documentUrl);
        Task<List<Pessoa>> List();
    }
}

No código acima criamos os métodos de Upload, Salvar e Listar que serão implementados logo abaixo pelo nosso serviço.

No serviço, primeiramente vamos criar uma pasta Services e em seguida vamos criar uma classe chamada ImasterService.cs com o seguinte código (comentários no próprio código para melhor entendimento):

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Imasters.Entities;
using Imasters.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;

namespace Imasters.Services
{
    public class ImasterService : IImasterInterface
    {
        /// <summary>
        /// Uploads the BLOB file async.
        /// </summary>
        /// <returns>Metódo que efetua o upload da foto da pessoa no Blob Storage.</returns>
        /// <param name="file">File.</param>
        public async Task<string> UploadBlobFileAsync(IFormFile file)
        {
            //Recupera a Connection String da variável de ambiente que definimos no inicio do projeto.
            var storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("CONNECTION_AZURE"));

            //Variável que representa o endpoint do blob na Storage Account.
            var blobClient = storageAccount.CreateCloudBlobClient();

            //Tenta recuperar o container em que será salvo o Blob, se não existir ele cria. Obs: O nome do container não deve conter letras maisucúlas.
            var container = blobClient.GetContainerReference("meucontainer");
            await container.CreateIfNotExistsAsync();

            //Recupera a referência do endereço do blob e faz o upload do mesmo.
            var blockBlob = container.GetBlockBlobReference(file.FileName);
            using (var fileStream = file.OpenReadStream())
            {
                await blockBlob.UploadFromStreamAsync(fileStream);
            }

            return blockBlob.Uri.ToString();
        }

        /// <summary>
        /// Save the specified name, email and documentUrl.
        /// </summary>
        /// <returns>Método que salva os dados cadastrados nas tabelas do Storage do Azure.</returns>
        /// <param name="name">Name.</param>
        /// <param name="email">Email.</param>
        /// <param name="documentUrl">Document URL.</param>
        public async Task Save(string name, string email, string documentUrl)
        {
            //Recupera a Connection String da variável de ambiente que definimos no inicio do projeto.
            var storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("CONNECTION_AZURE"));

            //Cria o client para a tabela.
            var tableClient = storageAccount.CreateCloudTableClient();

            //Tenta recuperar o objeto que representará a tabela pessoa, se não existir ele cria. Obs: O nome da tabela não deve conter letras maisucúlas.
            var table = tableClient.GetTableReference("pessoa");
            await table.CreateIfNotExistsAsync();

            //Cria o objeto pessoa.
            var person = new Pessoa(name, email);
            person.FotoUrl = documentUrl;

            //Efetua a inserção na tabela.
            var tableOperation = TableOperation.Insert(person);
            await table.ExecuteAsync(tableOperation);
        }

        /// <summary>
        /// Metódo que irá listar todos os cadastros efetuados.
        /// </summary>
        /// <returns>The list.</returns>
        public async Task<List<Pessoa>> List()
        {
            //Recupera a Connection String da variável de ambiente que definimos no inicio do projeto.
            var storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("CONNECTION_AZURE"));

            //Cria o client para a tabela.
            var tableClient = storageAccount.CreateCloudTableClient();

            //Recupera o objeto que representará a tabela pessoa.
            var table = tableClient.GetTableReference("pessoa");

            //Inicializa a variável que receberá os dados.
            var tableQuery = new TableQuery<Pessoa>();

            //Inicializa a variavel que representa o token para null para garantir que se comece no inicio da tabela.
            TableContinuationToken continuationToken = null;

            //Inicializa a variável que receberá o resultado.
            TableQuerySegment<Pessoa> tableQueryResult = null;

            //Lê toda a tabela.
            do
            {
                tableQueryResult = await table.ExecuteQuerySegmentedAsync(tableQuery, continuationToken);
                continuationToken = tableQueryResult.ContinuationToken;
            } while (continuationToken != null);

            return tableQueryResult.Results;
        }
    }
}

Neste próximo passo, vamos criar um Controller que servirá para orquestrar nossas rotas, e para isso, na pasta Controllers já previamente criada na inicialização do projeto, crie o ImasterController.cs com o seguinte código:

using System.Collections.Generic;
using System.Threading.Tasks;
using Imasters.Entities;
using Imasters.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Imasters.Controllers
{
    [Route("api/[controller]")]
    public class ImasterController : Controller
    {
        private IImasterInterface _imasterService;

        public ImasterController(IImasterInterface imasterService)
        {
            _imasterService = imasterService;
        }

        [HttpPost]
        public async Task<IActionResult> Post(string name, string email, IFormFile file)
        {
            var url = await _imasterService.UploadBlobFileAsync(file);
            await _imasterService.Save(name, email, url);
            return Redirect("../index.html");
        }

        [HttpGet]
        public async Task<List<Pessoa>> Get()
        {
            var people = await _imasterService.List();
            return people;
        }
    }
}

Como vocês podem observar o código acima, ele é bem simples e apenas chama os métodos previamente implementados no ImasterService.cs.

Agora, para finalizar a parte “backend” do projeto vamos adicionar a injeção de dependência e habilitar nosso projeto para ler arquivos estáticos (CSS, Imagens e etc). Para isso, no arquivo Startup.cs no método ConfigureServices adicione a seguinte linha:

services.AddScoped<IImasterInterface, ImasterService>();

E no método Configure adicione as seguintes linhas:

app.UseDefaultFiles();
app.UseStaticFiles();

Agora finalmente finalizamos todo o nosso “backend”, faltando apenas uma página HTML para o formulário e outra página HTML para a listagem dos dados que seguem abaixo:

Index.html

<form class="form-labels-on-top" method="post" enctype="multipart/form-data" action="api/imaster">
            <div class="form-title-row">
                <h1>Cadastro de Pessoas</h1>
            </div>
            <div class="form-row">
                <label>
                    <span>Nome</span>
                    <input type="text" name="name">
                </label>
            </div>
            <div class="form-row">
                <label>
                    <span>Email</span>
                    <input type="email" name="email">
                </label>
            </div>
            <div class="form-row">
                <label>
                    <span>Foto</span>
                    <input type="file" name="file" />
                </label>
            </div>
            <div class="form-row">
                <button type="submit">Salvar</button>
            </div>
        </form>

List.html

<div class="main-content">
        <div class="list">
            <div class="row list-title-row">
                <div class="col-md-4"><strong>Nome</strong></div>
                <div class="col-md-4"><strong>Email</strong></div>
                <div class="col-md-4"><strong>Documento</strong></div>
            </div>
            <div id="result"></div>
        </div>
    </div>
    <script>
        $(document).ready(function () {
            var uri = 'api/imaster';
            $(document).ready(function () {
                $.getJSON(uri).done(function (data) {
                    $.each(data, function (key, value) {
                        var valido;
                        if (value.isValid)
                            valido = "Sim";
                        else
                            valido = "Não";
                        $("#result").append("<div class='row list-row'>");
                        $("#result").append("<div class='col-md-3'>" + value.partitionKey + "</div>");
                        $("#result").append("<div class='col-md-3'>" + value.rowKey + "</div>");
                        $("#result").append("<div class='col-md-4'><img src='" + value.fotoUrl +"' width='120' style='margin-top: -30px;' /></div>");
                        $("#result").append("</div>");
                    });
                });
            });
        });
    </script>

Agora é só rodar o projeto e efetuar um cadastro. Porém, você verá que a imagem não vai aparecer e acontecerá um erro de permissão. Isso porque, por padrão, qualquer container criado é Private, portanto você deve alterar isso para Public para que você veja a imagem corretamente conforme mostra na figura abaixo:

Obviamente o Azure fornece opções de segurança para controlarmos acesso de nossos arquivos dentro de um Storage, mas acho que esse assunto fica para um próximo artigo.

Espero que tenham gostado e até a próxima!