Blazor WebAssembly - Upload de arquivos com InputFile

Hoje veremos como enviar arquivos para o servidor em uma aplicação Blazor WebAssembly usando o componente InputFile.

Se você esta chegando agora e não sabe o que é o Blazor leia o artigo ASP .NET Core - Iniciando com o Blazor - Macoratti; se você já conhece e quer saber mais pode fazer o curso de Blazor Essencial.  


Continuando o artigo anterior vejamos agora como enviar arquivos usando o componente InputFile em uma aplicação Blazor com hospedagem WebAssembly.

Como vimos no artigo anterior, carregar e salvar arquivos em um aplicativo Blazor Server é relativamente simples. Seu código está sendo executado no servidor e você pode acessar facilmente o sistema de arquivos do servidor para salvar os arquivos carregados.

No entanto, um aplicativo Blazor WebAssembly não pode fazer isso diretamente porque o código está sendo executado dentro dos limites do navegador. Portanto, você precisa fazer algum trabalho adicional para passar os arquivos do lado do cliente para o servidor e salvá-los no servidor.

Vamos ver como isso pode ser feito na prática.

Recursos usados:

Blazor WebAssembly - Usando InputFile

Abra o VS 2022 Community e selecione a opção Create a New Project;

A seguir selecione a opção Blazor WebAssembly App e clique em next;

Informe o nome do projeto :  BlazorWasmUpload;

A seguir define as configurações conforme a figura abaixo e clique em Create;

Observe que definimos a opção - ASP.NET Core hosted - assim teremos uma solução com 3 projetos: Client, Server, Shared.

Clicando no botão Create iremos obter a seguinte estrutura da solução:

Com o projeto criado vamos fazer as seguintes alterações, ajustes e configurações :

Exclua os arquivos abaixo e suas referências do projeto Client:

Exclua o controlador WeatherForecastController do projeto Server.

No arquivo _Imports.razor do projeto Client vamos incluir uma referência a :

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorWasmUpload.Client
@using BlazorWasmUpload.Client.Shared
@using System.IO
@using BlazorWasmUpload.Shared

O primeiro permite acesso ao componente InptuFile e segundo permite criar e escrever em arquivos no servidor e o terceiro permite acesso a classe Arquivo que deverá ser criada no projeto Shared com o seguinte código:

namespace BlazorWasmUpload.Shared
{
    public class Arquivo
    {
        public string? NomeArquivo { get; set; }
        public byte[]? ConteudoArquivo { get; set; }
    }
}

A seguir, no arquivo NavMenu.razor da pasta Shared inclua mais um item de menu para exibir a opção Upload:

...
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
       <li class="nav-item px-3">
             <NavLink class="nav-link" href="/upload">
                <span class="oi oi-cloud-upload" aria-hidden="true"></span> Upload
             </NavLink>
   
   </li>
    </ul>
</div>
...

Crie uma pasta images dentro de wwwroot e inclua nesta pasta a imagem upload1.jpg que iremos usar no projeto.

A seguir altere o código do arquivo Index.razor para:

@page "/"

<PageTitle>Index</PageTitle>

<img src="/images/upload1.jpg" />

<h3>Blazor Server - InputFile</h3>

A seguir vamos criar um novo componente chamado FileUpload na pasta Pages do projeto Cliente e incluir o código abaixo neste arquivo:

@page "/upload"

@inject HttpClient Http

<h2>Blazor WebAssembly - Upload</h2>

<h3>@Message</h3>

<form @onsubmit="OnSubmit">
    <InputFile OnChange="OnInputFileChange" multiple />
    <br /><br />
    <button type="submit">Enviar arquivo(s) selecionado(s)</button>
</form>

@code {
    string Message = "Nenhum arquivo selecionado";
    IReadOnlyList<IBrowserFile> arquivosSelecionados;

    private void OnInputFileChange(InputFileChangeEventArgs e)
    {
        arquivosSelecionados = e.GetMultipleFiles();
        Message = $"{arquivosSelecionados.Count} arquivo(s) selecionado(s)";
    }

    private async void OnSubmit()
    {
        foreach (var file in arquivosSelecionados)
        {
            Stream stream = file.OpenReadStream();
            MemoryStream ms = new MemoryStream();
            await stream.CopyToAsync(ms);
            stream.Close();

            Arquivo Arquivo = new Arquivo();
            Arquivo.NomeArquivo = file.Name;
            Arquivo.ConteudoArquivo = ms.ToArray();
            ms.Close();

            await Http.PostAsJsonAsync<Arquivo>("/api/fileupload", Arquivo);
        }
        Message = $"{arquivosSelecionados.Count} arquivo(s) enviado(s)...";
    }
}

Vamos entender o código :

1- Injetamos uma instância de HttpClient para

@inject HttpClient Http

2- A propriedade Message apenas exibe mensagens:

<h5>@Message</h5>

3- Criamos um formulário que será submetido no evento OnSubmit onde estamos usando o componente InputFile usando o atributo multiple que permite selecionar mais de um arquivo, e que define o método OnInputFileChange onde vamos tratar a exibição de quantos arquivos foram selecionados.

<form @onsubmit="OnSubmit">
    <InputFile OnChange="OnInputFileChange" multiple />
    <br /><br />
    <button type="submit">Enviar Arquivo(s) selecionado(s) para Upload</button>
</form>

O botão Enviar apenas submete o formulário.

No bloco de código do arquivo temos:

4- A definição do texto da mensagem inicial e a definição da variável arquivoSelecionados como sendo uma lista somente leitura do tipo IBrowserFile que representa os dados de um arquivo selecionado pelo InputFile :

string Message = "Nenhum arquivo selecionado";
IReadOnlyList<IBrowserFile> arquivoSelecionados;

5- O método OnInputFileChange trata o evento OnChange do componente InputFile e obtém a lista de arquivos selecionados alterando o texto da mensagem:

 private void OnInputFileChange(InputFileChangeEventArgs e)
    {
        arquivoSelecionados = e.GetMultipleFiles();
        Message = $"{arquivoSelecionados.Count} arquivo(s) selecionado(s)";
    }

6- No método assíncrono OnSubmit estamos tratando o envio do formulário.

Primeiro verificamos se existem arquivos selecionados para a seguir percorrer a lista de arquivos usando um bloco foreach e ler cada arquivo e a seguir usamos o método OpenReadStream() para abrir um fluxo através do qual o conteúdo do arquivo carregado pode ser lido e a seguir copiamos o arquivo para a memória usando o método CopyToAsync().

Como queremos salvar o arquivo carregado no servidor criamos uma nova instância do arquivo e atribuímos o nome e a seguir o conteúdo do arquivo que foi copiado para a memória, e, a seguir usamos a instância de HttpClient para postar o arquivo para o servidor usando o endpoint /api/fileupload/ e passando o arquivo selecionado:

 private async void OnSubmit()
 {
        foreach (var file in arquivosSelecionados)
        {
            Stream stream = file.OpenReadStream();
            MemoryStream ms = new MemoryStream();
            await stream.CopyToAsync(ms);
            stream.Close();

            Arquivo Arquivo = new Arquivo();
            Arquivo.NomeArquivo = file.Name;
            Arquivo.ConteudoArquivo = ms.ToArray();
            ms.Close();

            await Http.PostAsJsonAsync<Arquivo>("/api/fileupload", Arquivo);
        }
        Message = $"{arquivosSelecionados.Count} arquivo(s) enviado(s)...";
}

Atenção !!! Os arquivos enviados estão sendo salvos na pasta Uploads que deverá ser criada dentro da pasta wwwroot no projeto Server.

Agora vamos definir o código do controlador FileUploadController no projeto Server que vai receber o arquivo e escrever o arquivo no local indicado.

using BlazorWasmUpload.Shared;
using Microsoft.AspNetCore.Mvc;

namespace BlazorWasmUpload.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class FileUploadController : ControllerBase
    {
        private readonly IWebHostEnvironment _env;
        public FileUploadController(IWebHostEnvironment env)
        {
            _env = env;
        }

        [HttpPost]
        public void Post(Arquivo Arquivo)

        {
            try
            {
                var path = $"{_env.WebRootPath}\\Uploads\\{Arquivo.NomeArquivo}";
                var fs = System.IO.File.Create(path);
                fs.Write(Arquivo.ConteudoArquivo, 0, Arquivo.ConteudoArquivo.Length);
                fs.Close();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }
    }
}

Agora é só alegria...

Executando o projeto teremos o resultado abaixo:

Podemos melhorar a implementação feita neste exemplo em diversos aspectos como definindo um tamanho limite para envio dos arquivos e implementar um tratamento de erros mais robusto.

Pegue o projeto aqui:    BlazorWasmUpload.zip (sem as referências)

"Porque, se vivemos, para o Senhor vivemos; se morremos, para o Senhor morremos. De sorte que, ou vivamos ou morramos, somos do Senhor."
Romanos 14:8

 


Referências:


José Carlos Macoratti