Blazor  -  Sítio do Zecão


 Hoje vamos criar uma aplicação Blazor para mostrar alguns recursos como a interoperabilidade JavaScript e o tratamento de imagens de áudio.

Hoje vamos criar um aplicação Blazor que vai tratar com a interoperabilidade Javascript para emitir sons, e, como exemplo vamos criar um galeria de imagens de animais que costumam ser encontrados em um sítio onde cada animal vai ter uma breve descrição e onde poderemos ouvir o som característico do animal.

Neste projeto vamos usar imagens de animais e arquivos de áudio no formato WAV onde para cada animal teremos o respectivo arquivo de áudio com o som característico do animal.

As imagens dos animais foram obtidos da internet e os arquivos de áudio podem ser obtidos no seguinte endereço : http://audiocidades.utopia.org.br/biblioteca/biblioteca_sons_animais.html

Criando o projeto no VS 2022

Abra o VS 2022 e clique em New Project e a seguir selecione o template Blazor Server App e informe o nome SitioApp :

A seguir defina as configurações conforme abaixo e clique em Create:

Vamos limpar o projeto removendo os componentes Counter.razor, FetchData.razor, SurveyPrompt.razor e o conteúdo da pasta Data do projeto.

No arquivo Index.razor vamos definir o código abaixo que vai ser a página de apresentação da aplicação e que vai exibir apenas uma imagem:

@page "/"
<PageTitle>Index</PageTitle>
<h1>Sitio do Zecão</h1>
<hr />
<img src="/sitio/caipira2.jpg" />

 

No arquivo NavMenu.razor vamos ajustar o código para exibir dois itens no menu: Sitio e Animais

..
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Sítio
            </NavLink>

        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="/sitio">
                <span class="oi oi-plus" aria-hidden="true"></span> Animais
            </NavLink>

        </div>
    </nav>
</div>
..

Vamos criar na pasta wwwroot do projeto as seguintes pastas:

  1. audio - contém os arquivos de áudio no formato wav
  2. images - contém as imagens dos animais
  3. sitio - contém a imagem de apresentação da aplicação
  4. js  - contém o arquivo javascript para executar e parar a execução do som

Na pasta Data do projeto vamos criar a classe  Animal com o código:   

namespace SitioApp.Data;

public class Animal
{
    public static IEnumerable<AnimalInfo> GetAll => new[]
    {
        new AnimalInfo("Abelha", "Poleniza as plantas e nos dá o mel."),
        new AnimalInfo("Cachorro", "O melhor amigo do home."),
        new AnimalInfo("Cavalo", "Imponente e saltitante."),
        new AnimalInfo("Coruja", "Séria e sombria"),
        new AnimalInfo("Galinha", "Nós dá ovos e cisca tudo."),
        new AnimalInfo("Galo", "O despertador do sítio."),
        new AnimalInfo("Ganso", "Altivos e barulhentos."),
        new AnimalInfo("Gato", "Preguiçoso e mansinho."),
        new AnimalInfo("Gaviao", "Sempre a espreita"),
        new AnimalInfo("Ovelha", "Quietas e solidárias."),
        new AnimalInfo("Papagaio", "Barulhentos e alegres."),
        new AnimalInfo("Pato", "Desengonçado e lento."),
        new AnimalInfo("Peru", "Faz gluuu gluuu"),
        new AnimalInfo("Porco", "Gosta de uma boa lama"),
        new AnimalInfo("Sapo", "Pegando moscas e insetor"),
        new AnimalInfo("Vaca", "Vamos tirar o Leite")
    };

    public sealed record AnimalInfo(string Nome, string Descricao)
    {
        public string ImagemUrl => $"/images/{Nome.ToLowerInvariant()}.png";
        public string WavUrl => $"/audio/{Nome.ToLowerInvariant()}.wav";
    }
}

A classe Animal define o método GetAll que retorna uma lista contendo o nome e uma breve descrição sobre o animal. Aqui estamos usando o tipo record para produzir as propriedades ImagemUrl e WavUrl que estão sendo geradas a partir da pasta images e audio respectivamente a partir de onde montamos o caminho da imagem e do respectivo arquivo de áudio.

Aqui estamos simplificando o código e não estamos usando um banco de dados para armazenar as informações. (Fique a vontade para incrementar o projeto.)

No arquivo _Imports.razor vamos definir o código que será usado pelos componentes:

@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 SitioApp
@using SitioApp.Shared
@using SitioApp.Data

A seguir vamos criar na pasta Pages o componente Sitio.razor com o código abaixo:

@page "/sitio"

<h3>
    <i class="oi oi-home" aria-hidden="true"></i>
    Sitio do Zecão
</h3>

<div class="container-fluid">
    <div class="row equal">
        @foreach (var animal in Animal.GetAll)
            {
                <Animais
                    Nome="@animal.Nome"
                    ImagemUrl="@animal.ImagemUrl"
                    WavUrl="@animal.WavUrl">
                    @animal.Descricao
                </Animais>
            }
        </div>

</div>

Este componente vamos obter a lista de animais definida na classe Animal e resolver os valores das propriedades Nome, ImagemUrl e do arquivo de áudio e descrição.

Criando o componente Animais

Na pasta Shared do projeto crie o componente Animais.razor com código a seguir:

@inject IJSRuntime Js
@implements IDisposable

<div class="col-3 d-flex pb-3">
    <div class="card" style="width: 20rem;">
        <img class="card-img-top" src="@ImagemUrl" alt="Card imagem">
        <div class="card-body">
            <h5 class="card-title">@Nome</h5>
            <p class="card-text">
                @ChildContent
            </p>
            @if (EmExecucao)
            {
                <a class="btn btn-primary btn-block" @onclick="PararAudio">
                    <i class="oi oi-media-stop" aria-hidden="true"></i>
                    Parar
                </a>
            }
            else
            {
                <a class="btn btn-primary btn-block" @onclick="ExecutarAudio">
                    <i class="oi oi-media-play" aria-hidden="true"></i>
                    Ouvir
                </a>
            }
            <audio @ref="Audio">
                <source src="@WavUrl" type="audio/wav">
                Seu navegador não suporta o elemento audio
            </audio>

        </div>
    </div>
</div>

@code {

    bool EmExecucao { get; set; }   

    [Parameter]
    public string? Nome { get; set; }

    [Parameter]
    public string? ImagemUrl { get; set; }

    [Parameter]
    public string? WavUrl { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private DotNetObjectReference<Animais>? animal;

    private ElementReference Audio { get; set; }

    private async Task ExecutarAudio()
    {
        await Js.InvokeVoidAsync("executarAudio", Audio);
        EmExecucao = true;
    }

    private async Task PararAudio()
    {
        await Js.InvokeVoidAsync("pararAudio", Audio);
        EmExecucao = false;
    }

    [JSInvokable]
    public async Task OnEnd()
    {
        EmExecucao = false;
        StateHasChanged();
    }
   

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            animal = DotNetObjectReference.Create(this);
            await Js.InvokeVoidAsync("initAudio", Audio, animal);
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    public void Dispose()
    {
        animal?.Dispose();
    }
}

Vamos entender o código:

No inicio estamos injetando uma dependência IJSRuntime, que nos permitirá interagir com elementos JavaScript e DOM do lado do cliente. Veremos a variável Js usada posteriormente em nosso bloco @code.

A seguir estamos usando um Card do Bootstrap para exibir a imagem, o titulo e estamos usando @childContent e o recurso RenderFragment para obter o conteúdo do componente filho Animal e obter os valores das propriedades.

Nota: No Blazor quando definimos uma marcação dentro de um componente,  ele vai assumir que deve ser atribuído a um parâmetro no componente que descende da classe RenderFragment e é chamado ChildContent.

Criamos dois botões de comando onde usamos os eventos onclick para executar e parar o áudio através dos métodos ExecutarAudio e PararAudio.

Definimos a tag HTML audio onde usamos o atributo @ref com o tipo ElementReference. O tipo nos permite manter uma referência DOM a um elemento HTML; vamos usá-lo para passar nossa tag de áudio para nosso JavaScript para reproduzir e parar o som de nossos animais.

Nota: Quando precisamos de uma referência a um elemento HTML, devemos decorar esse elemento (ou componente Blazor) com @ref. Identificamos qual membro em nosso componente manterá uma referência ao elemento HTML criando um membro com o tipo ElementReference e o identificamos no elemento usando o atributo @ref

No nosso projeto, o atributo @ref mapeia diretamente para nossa propriedade privada Audio.

Nos métodos ExecutarAudio e PararAudio estamos usando o IJSRuntime e estamos interagindo com o nosso elemento audio DOM e portanto usamos a propriedade Js para invocar nossas funções Javascript. Assim esses métodos são responsáveis por gerenciar o estado de EmExecucao alternando o valor para true ou false.

Outro atributo essencial para lidar com a interoperabilidade do JavaScript é o JsInvokeAttribute.

O InvokeVoidAsync no IJSRuntime permite chamar funções JavaScript sem esperar nenhum valor de retorno. Essas funções devem estar disponíveis na variável global window. O primeiro parâmetro de InvokeVoidAsync é a função JavaScript que você deseja invocar. Você pode passar tantos parâmetros subsequentes e eles serão passados ​​para a função JavaScript. Os ElementReferences passados ​​para InvokeVoidAsync serão convertidos automaticamente para o HTMLElement.

Este recurso permite que nosso JavaScript do lado do cliente chame um método .NET usando SignalR. Criamos essa ponte usando a classe DotNetObjectReference e precisamos criar a referência em nosso método OnAfterRenderAsync porque nosso componente é acessível.

Nota: Para chamar um método em uma instância de objeto .NET, primeiro precisamos passar uma referência ao objeto para JavaScript. Não podemos passar nosso objeto diretamente porque queremos fornecer ao JavaScript uma referência ao nosso objeto em vez de uma representação serializada Json de seu estado e fazemos isso criando uma instância da classe DotNetObjectReference.

Agora vamos tratar das funções Javascript que vamos usar em nosso projeto.

Usar o Blazor significa escrever menos Javascript mas se precisar podemos usar a interoperabilidade do Blazor com o JavaScript e assim melhorar a interface com usuário usando recursos Javascript. Se você é daqueles que procura evitar JavaScript completamente, lamento dizer mas isso às vezes não será possível.

Felizmente, no contexto do nosso projeto vamos precisar usar um JavaScript mínimo. Vamos criar um novo arquivo audio.js JavaScript dentro da pasta /wwwroot/js/ e colar as seguintes funções no arquivo :

function initAudio(element, reference){
    element.addEventListener("ended", async e => {
        await reference.invokeMethodAsync("OnEnd");
    });
}

function executarAudio(element) {
    pararAudio(element);
    element.play();
}

function pararAudio(element) {
    element.pause();
    element.currentTime = 0;
}

Neste código estamos interagindo com nossos elementos de referência encontrados em nosso componente. Essa é a verdadeira magia do Blazor, permitindo interações perfeitas de servidor e cliente com muito pouco código.

Em seguida, precisaremos referenciar este script em nosso arquivo _Layout.cshtml, localizado no diretório Pages acima da referência ao blazor.server.js :

...

<body>
@RenderBody()

<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">X</a>
</div>

<script src="/js/audio.js"></script>
<script src="_framework/blazor.server.js"></script>
</body>
</html>

Agora é só alegria...

Executando o projeto teremos o seguinte resultado:

Clicando no menu Animais teremos a exibição do componente Sitio.razor  que vai utilizar o componente Animais.razor :

Para ouvir o áudio basta clicar no botão de comando...

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

"O ímpio tem muitas dores, mas àquele que confia no Senhor a misericórdia o cercará."
Salmos 32:10

Referências:


José Carlos Macoratti