Manipulando arquivos no azure storage: FullStackPlayGround
Tags: azure, dotnet, angular, braziliandevs
Published: 2021-09-23 00:24:21

Oi pessoal!

Nesse vou mostrar como manipular arquivos do azure storage com c#, e ainda fornecer um exemplo de como a aplicar em um aplicativo angular.

## Você precisa ter instalado em sua máquina:
- [Windows Terminal](https://www.microsoft.com/pt-br/p/windows-terminal/9n0dx20hk701)
- [NodeJs](https://nodejs.org/en/download/)
- [Angular - CLI](https://angular.io/cli)
- [Azure - CLI](https://docs.microsoft.com/pt-br/cli/azure/install-azure-cli-windows?tabs=azure-cli)

e também uma assinatura no [Azure](https://portal.azure.com/)

## Move Storage
O move storage é um aplicativo angular com uma interface amigável para fazer as operações que serão demonstradas nesse artigo.
Tanto a api quanto o app estão no mesmo repositório.
As operações que vamos ver são:

* [Criar um container](#criar-container)
* [Listar containers](#listar-container)
* [Deletar container](#deletar-container)
* [Listar arquivos em um container](#listar-arquivos)
* [Fazer upload de um ou mais arquivos](#upload-arquivos)
* [Fazer download de um arquivo](#download-arquivo)
* [Deletar um arquivo de um container](#deletar-arquivo)
* [Mover arquivo de um container para outro](#mover-arquivo)

Faça uma pré-visualização do projeto:
{% youtube rgX1n9rolSc %}

Faça clone ou dê uma espiada no código do projeto:
{% github silverio27/MoveStorage no-readme %}

>Navegue no terminal até a pasta raiz do projeto onde se encontra o Readme.md

## Azure

Faça login na sua conta do azure e defina uma conta padrão para esse tutorial, assim você garante que a operação será feita no tenant correto:

```CMD
az login
az account set --subscription <subscriptionid>
```

Vamos definir algumas variáveis no terminal, para facilitar a escrita dos demais comandos

>substitua o __\<nome exclusivo do storage\>__ por por exemplo:
**storagelsv1990**, na qual a primeira parte é o nome do app, e a segunda parte são as iniciais do seu nome seguido do ano em que nasceu, (Lucas Silvério Vargas, 1990 - lsv1990).
>essa é só uma dica para dar um nome ao storage, que deve ser exclusivo. 

```CMD
$group="MoveStorage"
$storageName="<nome exlusivo do app>"
```

Agora vamos criar um grupo de recursos e criar o nosso storage
```CMD
az group create -n $group -l eastus2
az storage account create -n $storageName -g $group -l eastus2 --sku Standard_LRS
```

Obtenha a string de conexão e guarde, vamos utilizar mais adiante no nosso aplicativo:
```CMD
az storage account show-connection-string -g $group -n $storageName

```

Agora crie um container e vamos subir alguns arquivos de exemplo:

```CMD
az storage container create -n container1 --account-name $storageName
az storage blob upload-batch -d container1 -s .\documents\ --account-name $storageName 
```

##Aplicativo
No terminal navegue até o aplicativo angular e levante o servidor.
```CMD
cd .\src\app\
npm i
ng s -o
```

Em outra aba do terminal navegue até a api e levante a aplicação dotnet.
```CMD
cd .\src\api\
dotnet run
```
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyaxn0as7l6ix92c68s2.png)
 

Adicione uma conexão ao storage, vamos colar a connection string que obtivemos anteriormente e informar um apelido para essa instância de gerenciamento:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzucn0fyrpg814x50lzo.png)

Selecione o container1 e será carregado e os arquivos serão listados:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ngq2cxpnrkm9s6d3hhxo.png
)

Crie uma nova instância de gerenciamento com a mesma connection string e vamos criar um novo container pelo próprio aplicativo:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zxifa98jmsjp2cw76r8.png)
>O nome pode conter apenas letras minúsculas, números e hifens e deve começar com uma letra ou com um número. Cada hífen deve ser precedido e seguido por um caractere que não seja um hífen. O nome também deve ter entre 3 e 63 caracteres.

Selecione um arquivo e arraste para área de arquivos do container2, e o arquivo será copiado de um container para o outro:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9eh5mbkdqifmit6ynwz.png)
>Também é possível mover entre storages diferentes.

No repositório você consegue ver como construí essa dinâmica no front-end, mas nesse artigo vou explanar apenas os métodos em c#.

##WebApi

### Disclaimer
Essa é uma aplicação para fins didáticos, nela eu passo as connection strings de alguns métodos pelos parâmetros da url. Em produção não fazemos assim, connection strings são informados em arquivos de configuração como o appsettings.json.

O método **DecodeUrl** é usado para fazer o decode da connection string e um tratamento para sinais de "+" que as mesmas podem possuir
```C#
private static string DecodeUrl(string connectionString) => HttpUtility.UrlDecode(connectionString).Replace(" ", "+");
```
Leia mais sobre isso [aqui](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).

## Comandos
Os comandos foram declarados como Records para diminuir a escrita de código e também estão no arquivo de controller, devido a natureza simples desse projeto:
```C#
    public record StorageConnection(string ConnectionString, string Alias);
    public record Container(string Name, StorageConnection Connection);
    public record FileConvert(string Name, string Base64);
    public record Upload(Container Container, List<FileConvert> FilesBase64);
    public record MoveFile(Container From, Container To, string File);
    public record NewContainer(string ConnectionString, string ContainerName);
```

## Criar um novo container <a name="criar-container"></a>

```C#
[HttpPost("container")]
public ActionResult AddContainer(NewContainer container)
{
    var storage = new BlobServiceClient(container.ConnectionString);
    storage.CreateBlobContainer(container.ContainerName);
    return Created("", new { response = "Arquivo transferido" });
}
```

## Listar containers <a name="listar-container"></a>

```C#
[HttpGet("containers/{connectionString}")]
public ActionResult GetContainers(string connectionString)
{
     var storage = new BlobServiceClient(DecodeUrl(connectionString));
     var response = storage.GetBlobContainers().Select(x => x.Name);
     return Ok(response);
}
```

## Deletar um container <a name="deletar-container"></a>

```C#
[HttpDelete("container/{connectionString}/{containerName}")]
public ActionResult RemoveContainer(string connectionString, string containerName)
{
    var blob = new BlobContainerClient(DecodeUrl(connectionString), containerName);
    blob.Delete();
    return NoContent();
}
```



## Listar arquivos de um container <a name="listar-arquivos"></a>

```C#
[HttpGet("files/{connectionString}/{containerName}")]
public ActionResult GetFiles(string connectionString, string containerName)
{
    var container = new BlobContainerClient(DecodeUrl(connectionString), containerName);
    var response = container.GetBlobs().Select(x => x.Name);
    return Ok(response);
}
```

## Deletar um arquivo <a name="deletar-arquivo"></a>

```C#
[HttpDelete("file/{connectionString}/{containerName}/{fileName}")]
public ActionResult RemoveFile(string connectionString, string containerName, string fileName)
{
    var blob = new BlobClient(DecodeUrl(connectionString), containerName, fileName);
    blob.Delete();
    return NoContent();
}
```

## Upload de arquivos <a name="upload-arquivos"></a>

```C#
[HttpPost("upload")]
public ActionResult Upload(Upload upload)
{
    var container = new BlobContainerClient(upload.Container.Connection.ConnectionString, upload.Container.Name);
    foreach (var file in upload.FilesBase64)
    {
        var data = file.Base64.Substring(file.Base64.IndexOf(",") + 1);
        container.UploadBlob(file.Name, new MemoryStream(Convert.FromBase64String(data)));
    }
    return Created("", new { response = "Arquivos enviados" });
}
```

## Download de um arquivo <a name="download-arquivo"></a>

```C#
[HttpGet("download/{connectionString}/{containerName}/{fileName}")]
public async Task<ActionResult> DownloadFile(string connectionString, string containerName, string fileName)
{
    var blob = new BlobClient(DecodeUrl(connectionString), containerName, fileName);
    BlobProperties properties = await blob.GetPropertiesAsync();
    var result = await blob.DownloadContentAsync();
    return File(result.Value.Content.ToArray(), properties.ContentType, "");
}
```
## Mover um arquivo de um storage/container para outro storage/container <a name="mover-arquivo"></a>

```C#

[HttpPost("move")]
public async Task<ActionResult> MoveFile(MoveFile moveFile)
{
    BlobContainerClient sourceContainer = new(DecodeUrl(moveFile.From.Connection.ConnectionString), moveFile.From.Name);
    BlobContainerClient destContainer = new(moveFile.To.Connection.ConnectionString, moveFile.To.Name);
    BlobClient destBlob = destContainer.GetBlobClient(moveFile.File);
    await destBlob.StartCopyFromUriAsync(GetSharedAccessUri(moveFile.File, sourceContainer));
    await sourceContainer.DeleteBlobAsync(moveFile.File);
    return Created("", new { response = "Arquivo transferido" });
}
private static Uri GetSharedAccessUri(string blobName, BlobContainerClient container)
{
    DateTimeOffset expiredOn = DateTimeOffset.UtcNow.AddMinutes(60);
    BlobClient blob = container.GetBlobClient(blobName);
    Uri sasUri = blob.GenerateSasUri(BlobSasPermissions.Read, expiredOn);
    return sasUri;
}
```
>Veja mais sobre o método de cópia e os motivos de gerar uma UriSaS [aqui](https://docs.microsoft.com/pt-br/learn/modules/copy-blobs-from-command-line-and-code/)
 
Espero que isso te ajude!