DEV Community

Cover image for Struttura e funzionamento di un'applicazione Blazor Server
Marco Santoni
Marco Santoni

Posted on • Updated on

Struttura e funzionamento di un'applicazione Blazor Server

Dopo la (molto) breve panoramica su Blazor Server e Blazor WebAssembly, in questo post cominciamo a scendere nel pratico e vediamo come è organizzata un'applicazione Blazor Server.

Come abbiamo visto nella panoramica al precedente articolo, un'applicazione sviluppata con Blazor Server viene eseguita interamente sul server come una normale applicazione ASP.NET Core e successivamente inviata al client tramite l'ausilio di SignalR. Dal momento che l'applicazione viene eseguita interamente sul server è facile intuire che anche il Render Tree (di cui abbiamo accennato nella panoramica) in questo hosting model viene generato lato server e successivamente, come ho appena scritto, inviato al client tramite SignalR per essere utilizzato per effettuare gli aggiornamenti della pagina, la gestione degli eventi e le chiamate a JavaScript. In questo caso quindi non ci sarà bisogno di dover scaricare sul client l'applicazione né, tantomeno, il runtime di .NET facendoci risparmiare un po' di tempo (e traffico) al primo avvio.

Ripropongo lo schema che abbiamo già visto la volta scorsa:

Blazor Server

Struttura dell'applicazione

Vediamo ora come deve essere strutturato un progetto che fa uso di Blazor Server.

Partiamo da un'applicazione web vuota, creata con il comando dotnet new empty, e successivamente creiamo i file e le directory che ci serviranno per capire come funziona Blazor Server.

Visual Studio Project Structure

Cosa ho aggiunto a quanto generato dal template?

  • La directory Pages che è la directory dove l'engine di ASP.NET Core, di default, va a ricercare le Razor Pages.
  • La directory wwwroot che di default contiene tutte le risorse statiche dell'applicazione (CSS, pagine HTML, immagini, eventuali JavaScript, ecc.). Il percorso può essere modificato in fase di creazione dell'host builder (vedere il metodo UseWebRoot).
  • Il file _Host.cshtml all'interno della directory Pages. Questa è la pagina principale che fornisce l'HTML di base dell'applicazione dove poi verranno caricati i Razor Components (possiamo vederla un po' come la master page delle vecchie applicazioni Web Forms).

Vediamo ora invece "cosa c'è dentro" il file _Host.cshtml.

File _Host.cshtml

@page "/"
@namespace BlazorServerApp.Pages

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Blazor Server app</title>
        <base href="~/" />
    </head>
    <body>

        <script src="_framework/blazor.server.js"></script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Diciamo subito che non si tratta di un file HTML statico ma bensì di una Razor Page.

La direttiva @page ci indica che la pagina risponderà alla route "/", mentre la direttiva @namespace serve a fare in modo che la pagina sia parte del namespace BlazorServerApp.Pages.

Non stiamo vedere l'HTML che immagino tutti conosciamo, però facciamo attenzione al tag <script>...</script> che carica uno script che non è presente nel nostro progetto, ovvero, _framework/blazor.server.js. Questo è lo script che viene caricato dal client e che ha il compito di stabilire la connessione di SignalR con il serer.

È importante sapere che per fare in modo che lo script _framework/blazor.server.js sia caricato c'è bisogno che il middleware che fornisce i file statici sia attivato (vedremo più avanti come fare).

La Razor Page così come è proposta verrà mostrata sempre come una pagina bianca ma, per il momento, non preoccupiamocene. Nel corso di questo articolo andremo ad aggiungere il codice necessario per renderla funzionale ai nostri scopi.

Iniziamo ora a configurare la nostra applicazione in modo da poter utilizzare Blazor Server. Per abilitare questo hosting model è necessario aggiungere nel metodo ConfigureServices della classe Startup due servizi che ci sono messi a disposizione nativamente da ASP.NET Core:

  • Il servizio che consente di usare le Razor Pages nell'applicazione (services.AddRazorPages()).
  • Il servizio Server-Side di Blazor (services.AddServerSideBlazor()).
    • Con l'aggiunta di questo servizio si aggiunge anche il servizio per l'utilizzo di SignalR.
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
}
Enter fullscreen mode Exit fullscreen mode

Successivamente dobbiamo configurarli e questo lo andiamo a fare nel metodo Configure, sempre nella classe Startup.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");  
    });
}
Enter fullscreen mode Exit fullscreen mode

La parte che ci interessa osservare è quella all'interno del middleware Endpoints. Infatti, a differenza di un'applicazione ASP.NET Core classica, non andiamo a mappare le routes ma bensì l'hub SignalR per Blazor.

Creiamo il nostro primo componente

Ora che abbiamo capito le basi di come funziona e come si configura un'applicazione Blazor Server, è arrivato il momento di andare un passo oltre e creare il nostro primo componente che mostrerà sulla pagina il classico Hello World.

Nella direcotry Pages andiamo a creare il componente. Lo chiameremo Hello.razor.

<h1>@title</h1>

@code {
    private string title = "Hello World from Blazor Server!";
}
Enter fullscreen mode Exit fullscreen mode

Ora che abbiamo il nostro componente dobbiamo fare in modo che il browser lo vada a renderizzare. Per fare questo abbiamo una pagina che ha proprio l'incarico di mostrare i componenti. Ricordate?! Stiamo parlando della _Host.cshtml.

Andiamo quindi a modificare la Razor Page in modo da dirgli cosa mostrare.

@page "/"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@namespace BlazorServerApp.Pages

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Blazor Server app</title>
        <base href="~/" />
    </head>
    <body>
        <component type="@typeof(Hello)" render-mode="ServerPrerendered" />

        <script src="_framework/blazor.server.js"></script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Grazie al tag helper component, eseguendo l'applicazione, riusciamo a vedere renderizzato il nostro Razor Component che sarà mostrato sulla pagina _Host.cshtml.

Il tag helper component per funzionare ha bisogno che venga specificato l'attributo type con il tipo del componente che dovrà essere renderizzato. Il parametro render-mode non è obbligatorio e può assumere i segueni valori: Server, ServerPrerendered e Static. Noi useremo ServerPrerendered.

Proviamo ad eseguire quanto abbiamo fatto fino ad ora. Funziona, ma ovviamente con un risultato come questo non possiamo certo apprezzare le opportunità che ci vengono offerte da Blazor. Modifichiamo quindi ulteriormente il nostro Razor Componet in modo da poter toccare con mano qualcosa di più "dinamico": facciamo in modo che quando si preme un pulsante venga aggiunto un item ad una lista e quando se ne preme un altro venga invece eliminato l'ultimo item inserito. Inoltre, vogliamo visualizzare un testo che ci indica il numero di items presenti nella lista. A scopo didattico il nostro item sarà un semplice stringa.

@using Microsoft.AspNetCore.Components.Web

@namespace BlazorServerApp.Pages

<h1>@title</h1>

@if (_list.Count == 0)
{
    <p>In your list there is no elements.</p>
}
else
{
    <p>In your list there @(_list.Count == 1 ? "is 1 element" : $"are {_list.Count} elements").</p>
}

<button @onclick="AddToList_OnClick">Add to list</button>
<button @onclick="RemoveFromList_OnClick">Remove from list</button>

<ul>
    @foreach (string s in _list)
    {
        <li>@s</li>
    }
</ul>

@code {
    private string title = "Hello World from Blazor Server!";
    private List<string> _list = new List<string>();

    private void AddToList_OnClick()
    {
        int count = _list.Count;
        _list.Add($"Item {++count}");
    }

    private void RemoveFromList_OnClick()
    {
        if (_list.Count == 0)
        {
            return;
        }

        int lastIdx = _list.Count;
        _list.RemoveAt(--lastIdx);
    }
}
Enter fullscreen mode Exit fullscreen mode

Testiamo subito le modifiche appena effettuate al nostro componente e proviamo a premere i pulsanti Add to list e Remove from list. Il risultato che otteniamo è che vengono aggiunti o rimossi items ad una lista lato server e poi visualizzati nel browser in un elemento HTML ul presente sulla pagina, senza che la stessa venga ricaricata, in maniera analoga a come se lo avessimo fatto con JavaScript. Mica male, vero?!

Il codice è abbastanza semplice, non c'è molto da spiegare. L'unica cosa importante da notare è che è stato importato il namespace Microsoft.AspNetCore.Components.Web con la direttiva @using. In questo modo possiamo utilizzare l'handler @onclick per gestire l'evento click dell'elemento button facendo in modo da eseguire il nostro metodo.

Prima di lasciarci, ricordiamo ancora una volta che in questo hosting model anche se non vediamo un refresh dell'intera pagina, il codice è interamente esguito sul server ed inviato al browser grazie a SignalR.

Conclusioni

Spero di essere riuscito a spiegare un po' le basi di come funziona un applicazione Blazor Server. Avrei voluto toccare altri argomenti (come il routing, il ciclo di vita dei componenti Razor e la gestione degli eventi) ma per non mettere troppa carne al fuoco ho deciso di trattare singolarmente questi argomenti in altri post.

Lasciatemi un commento se secondo voi è non sono stato chiaro o sono stato poco preciso su qualcosa. Le critiche mi aiutano a migliorare la qualità dei miei post e, soprattutto, mi aiutano a migliorare le mie abilità di dev 😀.

Discussion (0)