ASP.net Core APP 06 – Armazenando imagens

O Azure Storage é um serviço que permite o armazenamento de grandes volumes de dados, ele trabalha com alguns modelos de diferentes de armazenamento:

  • Contêineres – que armazenam qualquer tipo de arquivo no formato de objetos
  • Sistema de arquivos compartilhados
  • Tabelas – no formato relacional
  • Filas – conjunto de mensagens em formato de objetos JSON

Para maiores detalhes a respeito da integração de aplicações com Azure Storage veja a documentação oficial da Microsoft em: https://docs.microsoft.com/pt-br/azure/storage/blobs/storage-quickstart-blobs-dotnet#upload-blobs-to-a-container

O primeiro passo para fazer nossa aplicação encaminhar as mensagens para o Azure Storage é acessar sua conta do Azure e no menu principal clicar na opção criar recurso.

Na próxima tela, no campo de busca digite Armazenamento e selecione a opção Conta de armazenamento.

Clique no botão Criar.

Selecione a sua assinatura, e um grupo de recurso.

Na parte inferior desta mesma tela, informe os seguintes parâmetros:
– Nome da conta de armazenamento: storagefaegaspnetcoreSEUNOME
– Tipo da conta StorageV2
– Replicação: LRS
Então clique em avançar

Na próxima etapa mantenha a opção Ponto de extremidade público, para permitir que possamos conectar no Storage de fora do Azure. Clique em avançar.

Na próxima etapa mantenha as configurações padrões e clique em Avançar.

Avance até a última tela de confirmação das configurações e clique no botão Criar.

Aguarde alguns instantes até a conclusão da criação do recurso e clique no botão ir para o recurso.

Nesta tela inicial temos uma visão geral do recurso de Storage que foi criado. Clique no link Contêineres no centro desta página.

Na próxima tela, clique no botão +Contêineres.

Na próxima tela, informe o nome do novo contêiner como: shoppingfaeg clique no botão OK para confirmar a criação.

Precisamos agora recuperar a string de conexão com o serviço, para isso selecione a opção Chave de Acesso. Mantenha essa janela aberta, em breve vamos retornar para copiar a string de conexão.

Agora vamos alterar o código da aplicação para conectar no Azure Storage e enviar os arquivos, mas para isso precisamos instalar uma biblioteca. Para isso na pasta principal da aplicação execute o comando:

dotnet add package Microsoft.Azure.Storage.Blob

Como primeira alteração na aplicação vamos criar uma nova interface no pacote de serviço chamada IStorageService, esse serviço será responsável por conectar ao Azure Storage e enviar o arquivo para armazenamento.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace myshop.Services
{
    public interface IStorageService
    {
         Task<string> saveFile(IFormFile file);
    }
}

Agora vamos criar a implementação da interface, para isso na mesma pasta de Services, crie uma classe chamada StorageService. Observe que no construtor temos uma variável que deve receber a string de conexão com o serviço do Azure Storage, em seguida vamos implementar o método saveFile(), que recebe a instância da class IFormFile, connecta no Azure Storage criando um contêiner chamado shoppingfaeg, e em seguida gera um nome dinâmico para o arquivo que será salvo impedindo a sobre escrita de arquivos com o mesmo nome. Por fim o arquivo é enviado para o Azure Storage e seu novo nome é retornado.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;

namespace myshop.Services
{
    public class StorageService : IStorageService
    {
        private CloudStorageAccount storageAccount;
        public StorageService(  )
        {
            var storageConnectionString = "AQUIVAIASTRINGDECONEXAODOAZURESTORAGE";
            CloudStorageAccount.TryParse(storageConnectionString, out storageAccount);
        }
        public async Task<string> saveFile(IFormFile file)
        {
            if(storageAccount != null){
                CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
                CloudBlobContainer cloudBlobContainer = 
                    cloudBlobClient.GetContainerReference("shoppingfaeg");
                await cloudBlobContainer.CreateIfNotExistsAsync();
                
                string newName = System.Guid.NewGuid().ToString() + file.FileName;
                var cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(newName);
                await cloudBlockBlob.UploadFromStreamAsync(file.OpenReadStream());
                return newName;
            }
            return string.Empty;
        }
    }
}

Em seguida precisamos registrar esse novo serviço na aplicação, para isso vamos alterar o código da classe Startup, para no método ConfigureServices inserir a seguinte linha para registrar o novo serviço.

services.AddScoped<IStorageService,StorageService>();

Agora podemos alterar o código do formulário do produto para incluir o campo para fazer o upload do arquivo da imagem do produto e em seguida, salvar o novo nome do arquivo que será armazenado no Azure Storage, para isso vamos modificar o código da classe Product do pacote Models, para inserir o atributo Photo e o atributo FileNameStorage. Observe que anotamos a propriedade Photo com o decorador BsonIgnore, com isso esse campo não será serializado para o banco de dados Mongo.

using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace myshop.Models
{
    public class Product
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public float Price { get; set; }
        [BsonIgnore]
        public IFormFile Photo { get; set; }
        public string FileNameStorage { get; set; }
    }
}

Agora vamos modificar o formulário do controlador Product, para permitir que o formulário envie dados e arquivos binários no mesmo POST para o servidor, para isso vamos alterar a declaração do nosso FORM.

@using (Html.BeginForm("Form","Product",FormMethod.Post,new { enctype="multipart/form-data"})){

Em seguida vamos incluir um novo conjunto de elementos HTML para desenhar o botão de seleção do arquivo para upload.

<div class="form-group">
              @Html.LabelFor(m => m.Photo)
              @Html.EditorFor(m => m.Photo)
              @Html.ValidationMessageFor(m => m.Photo)
          </div>  

Agora precisamos modificar nossa classe ProductController para primeiro receber por injeção de dependência a instância do IStorageService.

private IStorageService storageService;
        public ProductController(IProductService productService, IStorageService storageService)
        {
            this.productService = productService;
            this.storageService = storageService;
        }

Ainda nesta classe, vamos modificar o código do método Form, para chamar de forma assíncrona o método saveFile da classe StorageService. Importante observar que a assinatura do método Form foi modificada para permitir aguardar o envio do arquivo de forma assíncrona.

[HttpPost]
        public async Task<IActionResult> Form(Product product){
            if (ModelState.IsValid)
            {
                var fileName = await this.storageService.saveFile(product.Photo);
                product.FileNameStorage = fileName;
                this.productService.save(product);

                return RedirectToAction("Index");
            }
            return View(product);
        }

Agora coloque a aplicação em execução e em seguida, acesse a url: https://localhost:5001/Product/Form faça o cadastro de um novo produto selecionando um arquivo de imagem para o produto.

Após a conclusão da inserção do registro e o envio do arquivo para o Azure Storage, o registro será apresentado na tela inicial da aplicação.

E se você acessar o contêiner no Azure Storage, observará que o arquivo com o mesmo nome foi inserido com sucesso.