Blazor Server - Visualizador de arquivos PDF

Hoje veremos como criar uma aplicação Blazor Server para visualizar arquivos PDF. Para isso vamos usar alguns recursos do Blazor dentre eles 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.  

Vamos criar uma aplicação Blazor Server e usando os recursos do Blazor vamos construir uma aplicação para visualizar arquivos PDF.

Recursos usados:

Criando o projeto Blazor Server

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

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

Informe o nome do projeto :  BlazorVerPDF;

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

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

Exclua os arquivos abaixo e suas referências:

Vamos criar no projeto a pasta Data e criar nesta pasta a classe ArquivoPDF que representa o nosso modelo de domínio que representa o arquivo que desejamos exibir:

namespace BlazorVerPDF.Data;

public class ArquivoPDF
{
    public int ArquivoId { get; set; }
    public string Nome { get; set; } = "";
    public string Path { get; set; } = "";
    public List<ArquivoPDF> Arquivos { get; set; } = new List<ArquivoPDF>();
}

A seguir vamos criar uma pasta Services no projeto e definir os serviços que iremos usar em nosso projeto.  Vamos definir os seguintes serviços :

  1. Serviço para enviar arquivos 
  2. Serviço para tratar os arquivos PDFs

Para criar os serviços vamos definir a interface e sua implementação em uma classe concreta. Começando com o primeiro serviço vamos criar a interface IUploadService:

using Microsoft.AspNetCore.Components.Forms;
namespace BlazorVerPDF.Services;
public interface IUploadService
{
    Task ArquivoUpload(IBrowserFile arquivo);
}

A seguir temos a sua implementação na classe UploadService:

using Microsoft.AspNetCore.Components.Forms;

namespace BlazorVerPDF.Services;

public class UploadService : IUploadService
{
    private long tamanhoMaximoPermitido = 1024 * 500;//512000
    IWebHostEnvironment _hostingEnvironment;

    public UploadService(IWebHostEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public async Task ArquivoUpload(IBrowserFile arquivo)
    {
        if (arquivo.Size > tamanhoMaximoPermitido)
        {
            throw new ArgumentException("Tamanho do arquivo excede o limite permitido");
        }
        else
        {
            try
            {
                var path = Path.Combine(_hostingEnvironment.WebRootPath, "Uploads", arquivo.Name);
                await using FileStream fs = new(path, FileMode.Create);
                await arquivo.OpenReadStream().CopyToAsync(fs);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Note que temos um limite de tamanho que estamos verificando para poder enviar os arquivos. Arquivos maiores que 500 KB vão lançar uma exceção.

Agora vamos criar a interface IArquivoService :

using BlazorVerPDF.Data;
namespace BlazorVerPDF.Services;
public interface IArquivoService
{
    List<ArquivoPDF> GetPdfs();
}

A seguir temos a sua implementação na classe ArquivoService:

using BlazorVerPDF.Data;

namespace BlazorVerPDF.Services;

public class ArquivoService : IArquivoService
{
    IWebHostEnvironment _hostingEnvironment;
    public ArquivoService(IWebHostEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public List<ArquivoPDF> GetPdfs()
    {
        List<ArquivoPDF> arquivos = new List<ArquivoPDF>();
        string path = $"{_hostingEnvironment.WebRootPath}\\Uploads\\";

        int arquivoId = 1;

        foreach (string pdfPath in Directory.EnumerateFiles(path, "*.pdf"))
        {
            arquivos.Add(new ArquivoPDF()
            {
                ArquivoId = arquivoId++,
                Nome = Path.GetFileName(pdfPath),
                Path = pdfPath
            });
        }
        return arquivos;
    }
}

Com isso temos na pasta Services os serviços implementados e vamos agora incluir esses serviços no contêiner DI definindo na classe Program o código abaixo:

using BlazorVerPDF.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<IArquivoService, ArquivoService>();
builder.Services.AddScoped<IUploadService, UploadService>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();

No arquivo _Imports.razor vamos incluir as referências abaixo:

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorVerPDF
@using BlazorVerPDF.Data
@using BlazorVerPDF.Services
@using BlazorVerPDF.Shared

O namespace Microsoft.AspNetCore.Components.Forms permite acesso ao componente InptuFile.

No arquivo NavMenu.razor da pasta Shared inclua mais um item de menu para exibir a opção PDFs:

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

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

Crie também a pasta Uploads na pasta wwwroot que vai receber os arquivos PDF para serem exibidos.

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

@page "/"
<PageTitle>Index</PageTitle>

<h2>Visualizador de PDF</h2>

<img src="/images/verpdf1.png" />

Criando o componente para exibir Erros

Na pasta Shared vamos criar o componente Error.razor que será usado para logar e exibir erros na nossa aplicação.

O código do componente é o seguinte:

@using Microsoft.Extensions.Logging
@inject ILogger<Error> Logger

<CascadingValue Value=this>
    @ChildContent
</CascadingValue>

<div class="alert alert-danger" role="alert">
   <h3>@ErrorMessage</h3>
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
    [Parameter]
    public string ErrorMessage { get; set; } = "";

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {Type} Message: {Message}",
            ex.GetType(), ex.Message);
     }
}

O código deste componente foi obtido do site da MSDN.

Este componente passa a si mesmo como um componente CascadingValue para componentes filho. Assim para usar este componente vamos envolver o componente Router no componente App com o componente Error.

<Error>   
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
</Error>

Isso permite que o componente Error se espalhe para qualquer componente do aplicativo onde o componente Error é recebido como um CascadingParameter.

Na segunda parte do artigo iremos criar o componente VerPDF.razor onde vamos usar os serviços criados e usar o componente InputFile para enviar e tratar arquivos PDF realizando também sua visualização.

"A minha porção é o Senhor, diz a minha alma; portanto esperarei nele."
Lamentações 3:24

 


Referências:


José Carlos Macoratti