Articoli

Da Web Forms a Blazor Server

da

Vediamo come funziona la versione Server di Blazor, resa disponibile a partire da .NET Core 3.0.

C’era una volta ASP.NET Web Forms

Ricordate come funzionava (e funziona ancora…) ASP.NET Web Forms? Una pagina HTML, all’interno di una grande form, con un campo nascosto (il famoso ViewState) contenente una stringa crittografata rappresentante lo stato della pagina, cioè il valore corrente di tutti i controlli server nella pagina. Questa form, mandata al server ogni volta che l’interazione dell’utente richiedesse un aggiornamento dell’interfaccia, scatenava l’esecuzione del motore di ASP.NET Web Forms, che aggiornava l’HTML e il valore del ViewState in base agli handler del code behind. Il nuovo HTML, comprensivo del nuovo ViewState, veniva rimandando indietro (processo chiamato Postback) alla stessa pagina.

Alla fine dei conti, lo sviluppo web è basato su questo meccanismo: catturo l’interazione dell’utente, faccio una richiesta HTTP al server e ottengo una risposta HTTP che uso per aggiornare l’interfaccia. Web Forms cercava solo di automatizzare questo processo, per nascondere i dettagli di funzionamento del protocollo ai programmatori Microsoft che venivano dal mondo Desktop, che erano abituati a ragionare in termini di Form, controlli e Handler di eventi nel code behind.

Questa astrazione portava con se però una grossa inefficenza in termini di scambio dati e di esperienza utente, visto che nella maggior parte dei casi viene spostata avanti e indietro tutta la pagina, con un refresh dell’interfaccia ad ogni interazione. La cosa fu mitigata con l’arrivo dell’ Update Panel, che rendeva asincrono lo scambio utilizzando AJAX in maniera trasparente al programmatore, risolvendo il problema del refresh.

Quando questa astrazione è diventata ingombrante e abbiamo accettato l’idea di doverci scrivere l’HTML (anzichè generarlo dai controlli server) e controllare l’interazione con il server, utilizzando JavaScript dove necessario, siamo passati a ASP.NET MVC: è stato un po’ come togliere le rotelle alla bici, all’inizio ci siamo demoralizzati per qualche caduta, ma poi abbiamo scoperto che potevamo correre senza limitazioni.

ASP.NET Core, Razor e SignalR

Se conoscete già ASP.NET Core, SignalR e Razor, avete già tutti gli elementi per utilizzare Blazor Server. Questa è probabilmente una delle principali ragioni che ha creato tanto entusiasmo attorno al framework. Facciamo un piccolo recap per chi non ha ancora avuto la possibilità di apprezzare il nuovo framework Microsoft.

Da ASP.NET a ASP.NET Core

Con l’introduzione di ASP.NET MVC nel 2008, gli sviluppatori Microsoft cominciarono (pian piano…) ad apprezzare il nuovo modello di sviluppo web, basato su un adattamento del pattern MVC. Qualche anno dopo fu aggiunto al framework un gemello per la realizzazione di API RESTASP.NET WEB API. Il tutto strettamente legato a IIS, l’application framework di Microsoft distribuito principalmente con le versioni server di Windows. Quindi per un periodo abbastanza lungo abbiamo avuto il nostro Windows Server, sul quale installavamo IIS e il .NET Framework che forniva ASP.NET come base di integrazione con IIS, che ci metteva a disposizione 3 framework tra cui scegliere: ASP.NET Web Forms, ASP.NET MVC e ASP.NET WEB API. Una infrastruttura un po’ ridondante in alcuni punti, ma che serviva allo scopo.

A un certo punto però, per ragioni che avremmo compreso fino in fondo solo anni dopo, il legame tra ASP.NET e IIS è cominciato ad andare un po’ stretto. Questo portò all’introduzione di OWIN, uno standard aperto che definisce una interfaccia tra un web server .NET e una applicazione web. Insieme allo standard venne fornito anche una implementazione, chiamata Katana, che contrattualizzava il legame tra ASP.NET e IIS. Il tutto fu introdotto con la versione 4 di MVC, fornendo di fatto la possibilità di far girare applicazioni ASP.NET MVC su web server diversi da IIS.

Aggiungiamo che in quel periodo i rilasci di ASP.NET seguivano quelli del .NET Framework, nonostante il web si evolvesse ad una velocità più alta del framework di base. Ma la vera ragione che portò a un momento di rottura fu il fatto che ASP.NET non era stato creato pensando agli scenari Cloud, e Microsoft stava per puntare tutto sul suo Cloud: Microsoft Azure.

Nel 2016 fu rilasciata la prima versione di ASP.NET Core, basata su un nuovo framework .NET cross-platform che fu chiamato .NET Core. Gli aggiornamenti di ASP.NET non dovevano più essere legati al framework sottostante, quindi tutto è stato basato su NuGet. Con l’occasione sono stati unificati i framework MVC e WEB API, abbandonando definitivamente Web Forms. Il framework fornisce di base strumenti integrati per configurazione, logging e Dependency Injection, e ci si può costruire la propria pipeline HTTP, utilizzando i middleware Owin: una vera e propria rivoluzione. Grazie al rilascio contestuale di Kestrel, un web server cross-platform compatibile OWIN, è possibile far girare .NET Core su Windows, Linux e MacOS, aprendo scenari incredibili.

Il .NET Framework è ancora lì (per il momento), accanto a .NET Core, utilizzando le Standard Libraries per condividere il codice tra i due mondi. Il prossimo passo sarà .NET 5, che unificherà tutto lo stack tecnologico Microsoft.

Razor

In ASP.NET il rendering server-side delle pagine web è sempre stato affidato al View Engine, il motore che analizza il misto tra markup, codice .NET, direttive e componenti custom e li traduce in HTML per il browser.

In ASP.NET Web Forms il View Engine di default era ASPX, che utilizzava i simboli <% e %> per introdurre nel markup il codice C# o Visual Basic:

<ul>
<% foreach (var prodotto in Prodotti)
   { %>
    <% if (prodotto.Disponibile)
       { %>
       <li><%=prodotto.NomeProdotto%> disponibile</li>
    <% }
       else
       { %>
       <li><%=prodotto.NomeProdotto%> non disponibile</li>
    <% } %>
<% } %>
</ul>

Nel passaggio a MVC fu introdotto un nuovo View Engine chiamato Razor, con una sintassi meno invasiva di ASPX e che utilizza il simbolo @ per introdurre nel markup il codice C# o Visual Basic:

<ul>
@foreach (var prodotto in Prodotti)
{
    @if (prodotto.Disponibile)
    {
    <li>@prodotto.NomeProdotto disponibile</li>
    }
    else
    {
    <li>@prodotto.NomeProdotto non disponibile</li>
    }
}
</ul>

Razor diventò il default per i progetti MVC e la sua semplicità di utilizzo lo ha portato a diventare parte integrante di una versione semplificata di ASP.NET MVC chiamata Razor Pages, un modello nel quale al posto di Controller e View viene introdotto il concetto di Page che contiene sia il markup che il codice che la gestisce.

SignalR

Nello sviluppo web di solito è il client che apre una connessione al server, fa una richiesta HTTP e ottiene una risposta HTTP, con la quale la connessione viene chiusa. Spesso però abbiamo bisogno di sapere se lato server è cambiato qualcosa.

Possiamo sicuramente fare delle richieste periodiche in maniera automatica per verificare se qualcosa è cambiato, ma questa operazione, che viene chiamata Polling, è decisamente dispendiosa. Una possibile alternativa è il Long Polling, che consiste nel mantenere attiva una connesione dal client al server in uno stato Pending: quando ci sarà un aggiornamento lato server, questo potrà essere comunicato utilizzando la connessione aperta. La risposta chiuderà la connessione, sarà quindi compito del client aprirne un’altra, ripetendo il processo. Sicuramente meno dispendioso del Polling, ma i browser moderni supportano delle valide alternative.

Una di queste è l’utilizzo dei Server-Sent Event, che ci permette di ricevere aggiornamenti dal server restando in ascolto su un particolare endpoint. In pratica registriamo una callback JavaScript utilizzando uno speciale oggetto chiamato EventSource, a quel punto il server, utilizzando il content-type text/event-stream può invocare questa callback senza che ci siano attese lato client. I Server-Sent Event sono però monodirezionali (dal server al client), se abbiamo bisogno di connessioni bidirezionali senza attese possiamo utilizzare la specifica WebSocket.

Tutto bello, ma abbiamo bisogno di sapere se browser e server supportano la scelta che facciamo. Ed è proprio qui che entra in gioco SignalR: in maniera completamente trasparente al programmatore permette di utilizzare lo strumento migliore messo a disposizione da client e server. Questo significa che, se disponibile, SignalR utilizzerà le WebSocket, altrimenti proverà con i Server-Sent Events. Nel caso nessuno dei due sia disponibile, verrà utilizzato il Long Polling. Questa operazione di scelta del trasporto è automatica e non impatta sull’API messa a disposizione da SignalR.

Il programmatore deve solo preoccuparsi di definire una o più classi Hub, che verranno utilizzate come endpoint per la comunicazione. Trovate maggiori informazioni qui.

Blazor Server

Adesso che sappiamo come siamo arrivati ad oggi e quali sono le tecnologie che abbiamo a disposizione, siamo pronti per analizzare come funziona Blazor Server: si tratta di un nuovo framework per la realizzazione dell’interfaccia utente delle nostre applicazioni ASP.NET Core, definita utilizzando HTML e CSS e sfruttando Razor e C# per il suo aggiornamento dinamico.

Gli aggiornamenti dell’interfaccia avvengono lato server, e vengono inviati al client utilizzando SignalR. Vediamolo in funzione lanciando l’applicazione che abbiamo creato nell’articolo precedente, utilizzando il comando della .NET CLI dotnet run, o il classico F5 di Visual Studio.

Aprite Chrome o una versione del nuovo EDGE, nel mio caso EDGE per MAC, che come probabilmente saprete utilizza l’engine di Chromium. Aprite il pannello degli strumenti per gli sviluppatori, selezionate il TAB Network, Filter All e andate all’indirizzo https://localhost:5001:

Quello che vedete è il template di base di Blazor Server. Nel TAB Network potete vedere il traffico di rete, tra cui la WebSocket inizializzata da SignalR durante la negoziazione del trasporto. Se cliccate sul Filtro WS (WebSockets) del TAB Network, selezionate la WebSocket _blazor, cliccate sulla voce di menu Counter e poi sul pulsante Click me, vedrete quanto segue:

Nel Tab Messages, potete vedere lo scambio dati bidirezionale tra il client e il server: il click sul pulsante ha inviato tramite la WebSocket la richiesta al server, che ha aggiornato il valore di un contatore, rigenerato l’HTML, confrontato con l’HTML precedente e inviato il differenziale al client mediante la WebSocket. A quel punto il codice presente nello script blazor.server.js ha aggiornato il DOM del browser!

Conclusioni

Per oggi ci fermiamo qui, ma siamo riusciti ad analizzare il funzionamento di Blazor Server, ripercorrendo velocemente la storia che ha portato alla sua nascita e sottolineando come sia possibile riutilizzare tutte le conoscenze che abbiamo dello stack .NET Core per realizzare un front-end con la user experience di una Single Page Application.

La sfida tecnologica si gioca però sul tavolo WebAssembly, e nel prossimo articolo vedremo come sia possibile far girare il .NET Core Framework direttamente nel browser, senza nessun plug-in aggiuntivo.

Scritto da: