Articoli

Creare una SPA: struttura

da

Cominciamo a mettere le mani sul codice, creando la struttura della nostra Single Page Application. L’idea è quella di guidarvi nella realizzazione di una applicazione enterprise con Blazor, partendo da una classica CRUD (Create, Read, Update e Delete) di una entità e rifattorizzando man mano il codice, fino a renderlo riutilizzabile e condivisibile tra diversi progetti.

Come avete potuto leggere negli articoli precedenti, c’è una differenza di fondo tra Blazor Server e Blazor WebAssembly, quindi dove necessario faremo le poche ma dovute distinzioni tra i due. Assicuratevi di aver installato i template di Blazor, come spiegato nel primo articolo: si parte!

La struttura del progetto con Blazor Server

Creiamo un nuovo progetto specificando il nome della cartella da creare e in cui vogliamo generare il progetto:

dotnet new blazorserver -o event-manager-server

Se non preferite la riga di comando potete usare il classico wizard di creazione di un nuovo progetto in Visual Studio. In entrambi i casi sarà creato un progetto ASP.NET Core pre configurato per l’uso di Blazor Server. Chi ha esperienza con le versioni precedenti del framework troverà una importante novità: la fusione del file Program.cs con quello Startup.cs. Adesso quindi c’è un unico file dove viene configurata l’applicazione con l’aggiunta del supporto per le pagine Razor (builder.Services.AddRazorPages()) e per i componenti Blazor (builder.Services.AddServerSideBlazor()). Vengono poi impostati gli endpoints di Blazor: l’Hub SignalR (app.MapBlazorHub()) e l’indirizzo verso il quale navigare in caso di pagina non trovata (app.MapFallbackToPage("/_Host")).

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
var app = builder.Build();
...
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

Una volta configurato il tutto, andiamo a definire la nostra interfaccia utente, sfruttando un concetto molto utilizzato nei framework di sviluppo front-end moderni: il Componente. L’idea è quella di prendere la nostra interfaccia e suddividerla in elementi più piccoli che chiamiamo componenti. In Blazor si parla di Blazor Components: tecnicamente sono file con estensione .razor nei quali andiamo a inserire il nostro markup, le direttive Razor e il codice C# associato al componente.

Approfondiremo in un apposito articolo questo concetto, per il momento accontentiamoci di sapere che in Blazor Server la definizione dell’interfaccia parte da una singola pagina (da cui il nome Single Page Application), chiamata solitamente _Host.cshtml, che contiene l’HTML di base e un primo componente. Il markup HTML è in realtà contenuto in un file _Layout.cshtml ben noto a chi ha utilizzato nel passato il framework ASP.Net. Qui di seguito vediamo il markup risultante dalla fusione dei due files

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>event-manager-server</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <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">🗙</a>
    </div>

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

Come potete vedere, niente di diverso da una classica pagina HTML, fatta eccezione per alcuni elementi. Subito sotto il body potete vedere l’utilizzo di un componente Blazor, denominato App e corrispondente al file App.razor presente nel progetto.

    <component type="typeof(App)" render-mode="ServerPrerendered" />

Questo componente sarà elaborato lato server (render-mode="ServerPrerendered") e sarà la radice dell’albero di componenti che costituirà la nostra interfaccia.

Sotto questo componente c’è un blocco opzionale che in caso di errore mostra un messaggio che, in fase di staging o produzione, invita a ricaricare la pagina, nel caso invece siate in fase di sviluppo, indica di verificare gli errori nella console del browser.

<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">🗙</a>
</div>

Infine la parte più importante:

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

Questo link scarica nel browser la componente client di Blazor Server, responsibile della comunicazione con il server mediante SignalR.

La struttura del progetto con Blazor WebAssembly

Per Blazor WebAssembly dobbiamo solo modificare il nome del template e il nome del progetto:

dotnet new blazorwasm -o event-manager-wasm

Anche in questo caso potete utilizzare il wizard di Visual Studio se non siete amanti della riga di comando. La struttura base del progetto è molto diversa dalla precedente, proprio perchè in questo caso non c’è elaborazione server, e il risultato sarà servito come un insieme di file statici.

 var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
...
await builder.Build().RunAsync();   

Andiamo a instanziare il builder della configurazione predefinita di hosting. L’oggetto che ci viene restituito è di tipo WebAssemblyHostBuilder, che ci espone la collection RootComponents: un insieme di oggetti composti dalle coppie Compontente/selettore. Nel nostro caso aggiungiamo un singolo componente radice (sì, è possibile averne più di uno, ma ne parleremo nei prossimi articoli), il componente App, che identificheremo nel markup con un selettore div avente id uguale ad app . Infine invochiamo il metodo Build(), di tipo WebAssemblyHost, che ci mette a disposizione la chiamata asincrona RunAsync(), che avvia il nostro server e lo mette in attesa delle richieste.

La nostra Single Page, in questo caso denominata index.html, si trova nella cartella wwwroot perchè, come già detto, in questa versione di Blazor andiamo a servire risorse statiche, dato che non avviene elaborazione server-side, come invece accade con il file _Host.cshtml di Blazor Server. La struttura è molto simile alla precedente:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>event-manager-wasm</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <div id="app">Loading...</div>
    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

Lo script che carichiamo prima di chiudere il <body> (<script src="_framework/blazor.webassembly.js"></script>) è quello di avvio e configurazione della versione WebAssembly di Mono (dotnet.wasm), che caricherà le nostre DLL e avvierà il rendering client-side.

Facciamo un po’ di pulizia

Adesso che abbiamo la struttura di base, facciamo qualche piccola modifica e un po’ di pulizia. Eliminiamo dalla cartella Pages i file Counter.razor e FetchData.razor.

Nella cartella Shared troviamo il file NavMenu.razor, il componente che rappresenta il menu dell’applicazione. Apriamolo ed eliminiamo le voci di menu Counter e FetchData, che puntano ai file che abbiamo eliminato. Sostituiamo anche il titolo del menu in “Event Manager”. Il codice risultante è il seguente per entrambi i progetti:

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">Event Manager</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
    </ul>
</div>

@code {
    ...
}

Dalla cartella Pages aprimo il file Index.razor e sostituiamo il contenuto con un messaggio di benvenuto. Utilizziamo anche una delle novità di Blazor nella versione 6: il componente PageTitle.

@page "/"
<PageTitle>Event Manager</PageTitle>
<h1>Event Manager</h1>
<p>Benvenuti nella Single Page Application scritta in Blazor per la gestione degli eventi.</p>
<p>Selezionare dal menu laterale l'opzione desiderata.</p>

Infine, dal progetto Blazor Server eliminiamo la cartella Data che contiene un servizio con dei dati fake, utilizzato dalla pagina FetchData per mostrare l’utilizzo di dati dal back-end. Nel file Program.cs, andiamo di conseguenza ad eliminare la registrazione della classe WeatherForecastService. Stesso lavoro di pulizia va fatto anche nel progetto WebAssembly, ma in questo caso ci basta eliminare la cartella wwwroot > sample-data.

Avviamo il progetto (dotnet run da riga di comando o F5 da Visual Studio) e verifichiamo il risultato del nostro lavoro:

La tipica bellezza estetica di cui è capace uno sviluppatore… ma funziona! Trovate il codice sorgente, organizzato in branch per ogni articolo della serie, qui.

Conclusioni

Per oggi ci fermiamo qui, nel prossimo articolo vedremo nel dettaglio che cos’è un componente, sia dal punto di vista logico che dal punto di vista tecnico, e come questo concetto sia il mattoncino con cui costruire la user interface della nostra Single Page Application.

Scritto da: