Articoli

Aggiungere Blazor ad un sito statico con Blazor Custom Elements

da

Tra le varie feature rilasciate con .NET 7, colpisce l’uscita dalla fase sperimentale dei Custom elements. Questa funzionalità ci permette di riutilizzare i nostri componenti Blazor come HTML Components standard all’interno di soluzioni che utilizzano tecnologie differenti da Blazor stesso.

Ad esempio potremmo integrare un nostro componente Blazor all’interno di un progetto che utilizza React piuttosto che Angular oppure anche un semplice sito statico. In questo articolo introdurremo i Blazor Custom Elements, provando ad aggiungere un semplice componente di rating di un post che aggiungeremo ad un sito statico che utilizza Hugo come motore di generazione dei contenuti.

Creazione del progetto Blazor

Partendo dal presupposto di avere già un sito web costruito con Hugo, andiamo a creare il progetto Blazor, in questo caso Blazor WebAssembly, che conterrà il nostro componente.

Possiamo utilizzare la CLI di .NET e sfruttare il template di progetto empty rilasciato sempre con .NET 7 in questo modo:

dotnet new blazorwasm-empty -n NomeProgetto

e, una volta creato il progetto, aggiungiamo il riferimento al package NuGet Microsoft.AspNetCore.Components.CustomElements che ci servirà per abilitare la funzionalità dei custom elements.

Creiamo il nostro componente

Creato il progetto di partenza e aggiunto il package necessario, possiamo iniziare a creare il nostro componente. Per mantenere un minimo di ordine nel codice andiamo a creare una cartella Components e al suo interno creiamo un file che chiameremo Rating.razor, andando ad inserire per il momento il seguente markup:

<div class="row align-items-center">
    <div class="col-md-auto">
        Ti è piaciuto questo post?
    </div>
    <div class="col-md-auto">
        <button type="button" class="btn btn-lg unlike-btn">
            <i class="far fa-thumbs-up"></i>
        </button>
    </div>
    <div class="col-md-auto">
        <button type="button" class="btn btn-lg unlike-btn">
            <i class="far fa-thumbs-down"></i>
        </button>
    </div>
</div>

Creato il componente andiamo a registrarlo nel nostro Program.cs in questo modo:

builder.RootComponents.RegisterCustomElement<Rating>("post-rating");

Da sottolineare come l’identificativo che diamo al nostro custom element (nel nostro caso “post-rating”) debba necessariamente contenere un hyphen, ovvero il carattere “-” (https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements). Fatto questo il nostro componente potrà essere utilizzato come un elemento HTML utilizzando proprio il nome che gli abbiamo assegnato. Ad esempio nel nostro caso andremo ad aggiungere il seguente tag nelle pagine dove vogliamo includere il nostro elemento:

<post-rating></post-rating>

Aggiungiamo il componente Blazor al nostro sito statico

Creata la nostra prima versione del nostro componente è giunto il momento di provare ad utilizzarlo nel nostro sito statico.

Per farlo dobbiamo assicurarci di includere nelle pagine HTML i link ai seguenti file javascript:

_framework/blazor.webassembly.js
_content/Microsoft.AspNetCore.Components.CustomElements/Microsoft.AspNetCore.Components.CustomElements.lib.module.js

Il primo dei due file javascript è necessario per abilitare il funzionamento di Blazor (in questo caso WebAssembly), mentre il secondo è quello relativo alla libreria che attiva la funzionalità dei Custom Elements.

Per recuperare questi due file, dobbiamo copiare le cartelle _framework e _content che troviamo nella folder wwwroot di pubblicazione del nostro progetto Blazor WebAssembly, che va appunto buildato e pubblicato utilizzando il comando dotnet publish.

Assicuriamoci inoltre di specificare nel tag head della nostra pagina HTML anche il tag base come mostrato di seguito:

<base href="/" />

Questo tag ci permette di specificare lo URL di base per tutti gli URL relativi definiti nel nostro documento.

Una volta modificata la nostra pagina HTML e copiate le cartelle relative a Blazor WebAssembly nella root del nostro sito web statico proviamo ad aggiungere nel punto in cui vogliamo visualizzare il componente il seguente tag:

<post-rating></post-rating>

Lanciamo a questo punto il nostro sito statico in un browser, apriamo i Developer Tools del nostro browser e nella console dovremmo trovarci di fronte ad un errore di questo genere:

elemento #app non trovato

Questo errore è dovuto al fatto che la nostra applicazione Blazor WebAssembly cerca un elemento con id app dove andare a caricare il componente App.razor.

Per risolvere il problema, ai fini dell’articolo, possiamo tranquillamente rimuovere i componenti di cui non abbiamo bisogno, file App.razor incluso, dal nostro progetto Blazor WebAssembly. In alternativa, nel caso volessimo utilizzare l’applicativo Blazor per provare il nostro componente in modo più agevole, possiamo modificare il caricamento del Razor component App mettendolo sotto condizione, ad esempio solo se l’HostEnvironment è di tipo Development come mostrato qui sotto:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

if (builder.HostEnvironment.IsDevelopment())
{
    builder.RootComponents.Add<App>("#app");
    builder.RootComponents.Add<HeadOutlet>("head::after");
}
//...

Rieseguendo l’applicazione l’errore in console descritto in precedenza dovrebbe sparire.

Prima di proseguire, ricapitoliamo quanto fatto fino ad ora:

  1. Abbiamo aggiunto al progetto Blazor WebAssembly il package Microsoft.AspNetCore.Components.CustomElements, registrando il nostro componente con l’istruzione builder.RootComponents.RegisterAsCustomElement<Rating>(“post-rating”).
  2. Abbiamo pubblicato il nostro progetto Blazor tramite il comando dotnet publish, copiando le folder _framewok e _content nel nostro sito web statico. Per quanto riguarda Hugo, abbiamo sfruttato le cartelle legate al template, aggiungendo i riferimenti ai file _framework/blazor.webassembly.js e _content/Microsoft.AspNetCore.Components.CustomElements/Microsoft.AspNetCore.Components.CustomElements.lib.module.js al file footer.html che si trova nella cartella themes/<nome_del_mio_tema>/layouts/partials
  3. Abbiamo aggiunto il tag <base href=”/” /> nelle pagine html. Per quanto riguarda Hugo il tag è stato aggiunto al file head.html che si trova nello stesso percorso del file footer.html accennato in precedenza.
  4. Sempre per quanto riguarda Hugo abbiamo aggiunto il tag <post-rating></post-rating> del custom element generato nella pagina di template per il dettaglio di un contenuto. Nello specifico si tratta del file single.html che si trova nel percorso layouts/_default situato all’interno della folder del nostro tema.

Aggiungiamo dei comportamenti al componente

A questo punto proviamo a complicare leggermente in nostro componente, aggiungendo la gestione del voto e un parameter che conterrà lo URL della pagina che si sta votando. Modifichiamo il nostro componente come segue:

@inject IRatingService Service @* questo service farà poi delle chiamate HTTP *@

<div class="row align-items-center">
    <div class="col-md-auto">
        Ti è piaciuto questo post?
    </div>
    <div class="col-md-auto">
        <button type="button" class="btn btn-lg unlike-btn" aria-label="Mi piace" @onclick="LikePostAsync">
            <i class="far fa-thumbs-up"></i>
            @if (numberOfLikes > 0)
            {
                <span class="badge badge-light">@numberOfLikes</span>
            }
        </button>
    </div>
    <div class="col-md-auto">
        <button type="button" class="btn btn-lg unlike-btn" aria-label="Non mi piace" @onclick="DislikePostAsync">
            <i class="far fa-thumbs-down"></i>
            @if (numberOfDislikes > 0)
            {
                <span class="badge badge-light">@numberOfDislikes</span>
            }
        </button>
    </div>
</div>

@code {
    [Parameter]
    [EditorRequired]
    public string Page { get; set; } = string.Empty;

    private PostRatingsModel ratings = new PostRatingsModel(0, 0);

    private int numberOfLikes = 0;

    private int numberOfDislikes = 0;

    private async Task LikePostAsync()
    {
        await Service.LikePostAsync(Page); // chiamo API per votare il post (like)
        numberOfLikes++;
    }

    private async Task DislikePostAsync()
    {
        await Service.DislikePostAsync(Page); // chiamo una API per votare il post (dislike)
        numberOfDislikes++;
    }

    protected override async Task OnInitializedAsync()
    {
        ratings = await Service.GetPostRatingsAsync(Page); // chiamo una API per recuperare l'elenco dei voti
        if (ratings is not null)
        {
            numberOfLikes = ratings.NumberOfLikes;
            numberOfDislikes = ratings.NumberOfDislikes;
        }
    }
}

Come si può vedere dal codice, i due bottoni chiamano i rispettivi metodi di un servizio che farà poi delle chiamate HTTP per aggiornare il numero di “mi piace” o “non mi piace”, inoltre sull’OnInitializeAsync() viene chiamato il caricamento dei voti fatti in precedenza, aggiornando i badge con il numero totale di votazioni.

Terminate queste aggiunte ripubblichiamo il nostro progetto Blazor WebAssembly e seguiamo lo stesso procedimento svolto in precedenza, copiando le folder _framework e _content nel nostro sito statico.

Copiate le folder andiamo nel file HTML dove abbiamo agganciato il nostro custom element e andiamo a modificarlo per potergli passare tramite parameter lo URL della pagina corrente. Per quanto riguarda Hugo possiamo usare la seguente sintassi:

<post-rating page="{{ .Permalink | relURL }}"></post-rating>

Che trasforma il permalink della pagina in URL relativo. Terminato il tutto, rieseguiamo il nostro sito statico e verifichiamo che il nostro componente funzioni correttamente:

Rating del post

Conclusioni

In questo articolo abbiamo visto come è possibile integrare un componente Blazor in un sito statico utilizzando la funzionalità dei Blazor Custom Elements. Questa feature ci abilita la possibilità di esporre i nostri componenti come HTML Custom Elements e di conseguenza riutilizzarli in tecnologie differenti.

Il codice sorgente del caso d’uso descritto è consultabile al seguente repository su GitHub: https://github.com/albx/morialberto.it. Sempre nel repository indicato è presente una GitHub Action che si occupa di fare deploy dell’applicazione su una Azure Static Web App e che contiene anche gli step che si occupano di pubblicare l’applicazione Blazor e copiare i file necessari nelle folder di Hugo.

Alla prossima!