Blazor pressapochista : Il primo progetto – Seconda parte

Da Visual Studio 2019 Preview approntate tutto per creare un nuovo progetto: scegliendo la voce Blazor sarà possibile al set di template dedicati.

Template tipo 1 – Blazor Server App (anche chiamata al bar amichevolmente Blazor server-side)

Template tipo 2 – Blazor WebAssembly App (al bar viene chiamato Blazor client-side)

Template tipo 3 – Blazor WebAssembly App hosted in Asp.Net Core (anche chiamata amichevolmente Blazor client-side hosted in Asp.Net core server)

Oss.: Giustamente potete osservare che le voci proposte dal nostra amato Visual Studio sono solo due, ma prima di pensare che possa aver detto una fesseria date un’occhiata al flag in basso a destra nominato Asp.Net Core hosted: questo permette di creare i template 2 e 3. Lo stesso flag è indisponibile se si sceglie il template 1.

I template esposti propongono in sostanza due “hosting-model” di Blazor: il primo template permette un hosting di tipo server-side, gli altri due client-side.

Ora parlare del modello di hosting model Blazor così, a freddo, mi rendo conto che è un po’ da sciagurati: ma ci provo lo stesso e, siccome il blog è mio e decido io cosa metterci, ho deciso di proporre in questo momento questi concetti, in modo tale che nel prossimo li possiate raffinare e comprendere appieno.

Torniamo bomba: i template 2 e 3 predispongono dei progetti nei quali il codice in esecuzione sul browser sarà scritto in C# e mandato in esecuzione nel browser con l’ausilio di webassembly.

Ma quale è la differenza tra questi due template ? Il template 2 è una pura web-app Blazor: in pratica crea solo i file necessari a essere mandati in esecuzione sul browser, e quindi è una pura web-app client-side.

Questa, per poter funzionare, come ci si aspetta non necessita di alcuna logica lato server. Detto in altro modo questo template crea un progetto cui il cui risultato è una serie di file che compongono la web-app e che possono essere mandati in esecuzione direttamente nel browser.

Quindi con questo template è possibile creare una web-app che può essere ospitata proprio come un sito web statico (Apache, IIS, etc etc): in linkografia propongo anche un simpatico e originale esempio di hosting con Azure Storage.

Però nella realtà le cose difficilmente sono così semplici: una web-app di qualsiasi tipo, e quelle scritte con Blazor non fanno differenza, spesso necessita di interagire con logica lato server, per esempio per poter ottenere dei dati ivi conservati grazie all’interazione con web-api lato che interrogano un database.

E’ qui che entra in gioco il progetto template di tipo 3: adottando questo ci si trova configurato e funzionante un web-server .Net core ottimizzato per poter esporre web-api a una applicazione Blazor.

Inoltre fornisce quanto necessario per fornire tutti i file necessari al funzionamento della web-app Blazor quando viene interrogato: in pratica rappresenta un web-hosting del nostro progetto già pronto e configurato + web-api che può usare questo.

Il template di tipo 1, invece, permette di creare un progetto Blazor che in realtà è eseguito lato server: l’interazione client-server avviene grazie al framework SignalR.

I vantaggi che si ottengono sono principalmente due.

  • La creazione delle pagine delle pagine è un’operazione svolta esclusivamente lato server, e quindi al browser vengono inviate esclusivamente sotto forma di HTML/javascript/css etc etc. Questo permette di eseguire la web-app anche su browser che ancora (!) non supportano le webassembly. Quindi se si vuole scrivere una web-app usando Blazor anche, per esempio, per internet explorer 11, che non supporta webassembly, o si usano le polyfills dedicate oppure si usa questo hosting-model che questo template rappresenta. Il modello utilizzato per creare web-app è la stessa nel caso di Blazor completamente client-side (template 2 e 3), tenendo ovviamente conto che in questo caso eventuali interazioni con remoto sono più semplici poiché si sta programmando lato server ! L’aggiornamento delle pagine avviene in modo ottimizzato grazie a SignalR, e in modo totalmente trasparente.
  • Il debug, che ancora negli altri hosting model non funziona nel modo corretto o non è implementato compiutamente, in questo caso funziona in modo completo e senza grosse limitazioni.

Quindi è possibile anche dire che questo template propone un progetto Blazor fake, nel senso che anche se la modalità di programmazione è la stessa però in questo caso il browser riceve solo gran pagine Html e javascript, e il codice gira tutto sul server.

Oss.: E’ abbastanza facile passare da progetti Blazor server-side a client-side: per cui se si progetta il web-app in modo corretto in linea teorica è possibile creare in un primo momento la web-app (o parte di essa) con hosting model di tipo server-side (template 1), per esempio poter sfruttare al massimo le possibilità di debug, e solo successivamente passare lo stesso a client-side.

Nel prossimo di questo post analizzeremo il template 3 (Blazor Asp .Net Core Hosted): diamo alla soluzione il nome di BlazorTest in modo da essere consistenti con il seguito.

Blazor Asp .Net Core Hosted

La solution creata dal template è costituita da tre progetti: BlazorTest, BlazorTest.Server e BlazorTest.Shared.

In modo consistente con quanto esposto precedentemente il primo progetto costituisce la parte client Blazor: quanto in questo progetto sarà realmente in esecuzione sul browser.

L’altro progetto costituisce invece la parte server: questa è delegata a fornire servizi di supporto. Nella pratica rende disponibili i file necessari a essere eseguiti nella parte client sul browser nonchè espone le web-api che saranno eventualmente utilizzate.

Il progetto BlazorTest.Shared permette invece di definire parti comuni utilizzate sia dal lato server che dal lato client: per esempio definizione di oggetti che saranno scambiati tra le due parti costitutive della web-app via web-api.

Il punto di partenza di “tutto il cinema” è il file index.html che si trova nella cartella wwwroot: è il primo file a essere reso disponbile al client che contatta il server.

Il file indica la pagina principale e ogni pagina richiesta nella navigazione sostituirà il tag app.

Nella navigazione della web-app si accederà a diversi URL: come noto ognuno di questi può essere scomposto in segmenti. tralasciando la parte hostname questi sono separati da un carattere /.

https:/nomehost/Pagina1

Ad ogni segmento corrisponde una route, e nel nostro caso a una diversa pagina Blazor. L’attività di associare un URL a un contenuto è in generale chiamato routing. Nel nostro caso per supportare l’attività di routing occorre utilizzare l’istruzione @page.

Per esempio analizzate la pagina /Pages/Index.razor.

In questo caso la pagina è associata a una richiesta di questo tipo. https:/nomehost/

In questo caso,invece, la pagina è associata a una richiesta di questo tipo. https:/nomehost/Counter

Oss.: Tutte la pagine sono conservate sotto la cartella Pages.

Oss.: Troverete curiosa la presenza del tag SurveyPrompt: per aumentare il riuso del codice in Blazor è possibile definire in sostanza dei tag personalizzati. Questi sono chiamati Blazor component. Lavorano in tutto e per tutti come i tag usuali di HTML solo che questa volta il contenuto visualizzato è definito in modo custom in file appositi.

Se si manda in esecuzione il progetto ci si rende conto che la pagina proposta è un po’ più complessa di wwwroot/index.html + Pages/Index.razor: questo perchè in tutto quanto esposto non ho ancora fatto cenno alle pagine di template nè al funzionamento a più basso livello di un web-app Blazor.

Appena il browser di connette a qualsiasi pagina verrà restituito in una prima fase il contenuto di wwwroot/index.html.

Come si può vedere questa contiene nella sostanza il riferimento a un file javascript blazor.webassembly.js. Questo (che, per inciso, è l’unico file javascript necessario a far funzionare tutta la baracca) prende il controllo di tutto, e si occupa di scaricare tutti i file necessari supplettivi.

Tra questi uno dei primi scaricati è mono.wasm, che è il runtime per .NET in formato webassembly. E’ il cuore di tutto.

Ma nella pratica quale è il ruolo di questo file ? mono.wasm è in grado di “digerire” e mandare in esecuzione file compilati in bytecode .Net.

In buona sostanza è in grado di mandare in esecuzione dll compilate da progetti .Net (quindi in formato CIL). Pertanto verranno scaricate insieme a mono.wasm tutte le dll library dei progetti che che compongono la web-app (nel nostro caso Blazor.Client.dll e Blazor.Shared.dll che altro non sono che library .Net Standard). Fatto questo verranno poste in esecuzione con l’ausilio del runtime mono.wasm.

Ecco uno dei ruoli di BlazorTest.Server: lavorando in concerto con blazor.webassembly.js si occupa d fornire tutti questi file necessari al funzionamento della web-app.

Occorre anche osservare che se il progetto BlazorTest.Client dipende da altri progetti, o anche componenti di terze parti, allora anche queste dll sono rese disponibili in modo automagico e richieste in download da blazor.webassembly.js al fine di poter mettere in esecuzione in modo compiuto la web-app.

Quindi in index.html dopo aver scaricato i file necessari e messo in esecuzione in tutto tramite mono.wasm si rintraccia la pagina richiesta (routing): il contenuto sostituirà il tag app.

Sono certo che tutto questo Vi suggerisce una domanda: perchè non compilare direttamente il codice dei progetti Blazor direttamente in wasm ? E invece doversi basare su mono.wasm ??

Bè, in prima battuta occorre dire che è una delle possibilità che sarà aggiunta nel prossimo a Blazor (..o almeno questi sono i rumors…).

Occorre poi osservare che scaricando le dll e mandadole in esecuzione è possibile aggiungere anche dll di terze parti di cui non si ha il codice costitutivo e che sono utilizzati all’interno della web-app.

Il terzo punto, quello più importante, è che M$ ha deciso per ora di fare così. Pertanto…..

Personalmente penso che l’idea fondante sia che il primo compilatore che era disponibile per trasformare un linguaggio ad alto livello in webassembly sia stato quello per C++. Siccome il runtime di mono (che è in grado di mandare in esecuzione dll .net) è scritto in C++, è stato un attimo trasformarlo in WASM.

A questo punto il rendering della pagina richiesta avviene attraverso la pagine di layout.

Infatti occorre precisare che ad ogni pagina richiesta è possibile associare una pagina di layout (se non diversamente specificato esplicitamente): tale layout nel nostro caso è Pages/Shared/MainLayout.razor.

Quindi al tag app viene sostituita la pagina di layout e all’istruzione @Body viene “innestata” finalmente la pagina /Pages/Index.razor.

La tecnica pagina richiesta che viene visualizzata all’interno di una pagina di layout, la quale a sua volta viene sostituita al tag app di index.html può sembrare un pò complessa, ma in realtà, una volta compresa, permette un grande riutilizzo del codice.

Per esempio: caratteristiche (orpelli e/o menù) che devono essere presenti in tutta la web-app -> index.html

caratteristiche che devono essere presenti solo su una serie di pagine -> layout e associazione di questo alle pagine coinvolte

Al successivo accesso a un’altra pagina costitutiva della web-app non avverrà alcun accesso al web-server: semplicemente il codice scaricato precedentemente è in grado di gestire la richiesta (grazie a mono.wasm e di tutte le dll del progetto Blazor).

Oss.: Ovviamente se la nuova pagina deve accedere a qualche web service tale operazione sarà eseguita, ma quello che voglio dire è che la web-app Blazor una volta acceduta la prima volta non ha bisogno di accedere ad altri contenuti per poter funzionare, perchè tutto le funzonalità sono gestite da codice client-side.

Ora il fatto di mandare in esecuzione dll nel browser, seppure tramite il runtime mono.wasm, può sembrare una cosa che apre degli scenari inquietanti: se la libreria dll (magari di terze parti) prova ad accedere al file system del sistema ospitante per fare operazioni “strane” ??

Semplice: viene tutto bloccato e non c’è da preoccuparsi.

Infatti il codice WASM viene eseguito in una sandbox uguale a quella in cui gira javascript.

Pertanto è più corretto dire che è possibile includere nel nostro progetto web-app client-side librerie di terze parti ad eccezione di quelle che contengono codice incompatibile con quello che è possibile mandare in esecuzione in WASM di un browser.

Conclusioni

Quanto esposto in questo post è abbastanza sommario e propone il fuzionamento di Blazor per sommi capi: Vi invito a leggerne di più nei link che Vi allego.

Mi premeva, però, dare una serie di input sui punti salienti del framework, che spesso ritengo non siano adeguatamente e compiutamente affrontati.

Nel prossimo della serie affronteremo una serie di utilizzi pratici del framework.

….E come mai lanciando localmente index.html non funziona nulla ?

Penso sia utile dire ancora qualcosa sul progetto BlazorTest.Client: come detto il risultato prodotto da questo progetto è quanto sarà in esecuzione lato client, grazie a WASM. In pratica è costituito da un set di file statici (mono.wasm, i file di supporto javascript, le dll compilate dei progetti BlazorTest.Client e BlazorTest.Shared nonchè le dll delle relative dipendenze) che quindi sono in grado di funzionare compiutamente all’interno di un browser.

Se per curiosità si tenta di fare il deploy del solo progetto BlazorTest.Client su di una cartella locale, e quindi si lancia index.html così ottenuto tramite doppio click su questo, ci si accorge che non funziona nulla, contrariamente a quanto ci si aspetterebbe. Infatti essendo file statici dovrebbe bastare teoricamente questa sola azione.

Questo comportamento anomale, invece, è perfettamente consistente con le sicurezze presenti in ogni browser: pur essendo file statici le sicurezze implementate NON permettono l’esecuzione di webassembly da cartelle locali semplicemente tramite accesso a file.

Pertanto affinchè il browser mandi in esecuzione webassembly occorre sempre che si connetta a un un server che è in grado di fornire gli stessi file: questo può avvenire tramite l’ausilio di protocollo http o anche altri protocolli alternativi supportati.

Questa cosa per rapidi test che si vogliono intavolta può essere una scocciatura: esiste un trucco per mandare in esecuzione la sola parte client-side da cartella locale ?

Un modo veloce per ottenere il risultato è l’adozione di Web Server for Chrome (vedere in linkografia): utilizzando questo plugin è possibile ottenere un web-server in miniatura. Basta puntarlo al path Client\bin\Release\netstandard2.0\publish\BlazorTest.Client\dist e tutto funziona come previsto.

Ovviamente eseguendo il deploy del progetto BlazorTest.Server, anche in locale, e lanciando BlazorTest.Server.exe, che crea un web-server locale (è un normale progetto Asp .Net Core), e connettendosi a questo tutto funziona come ci si aspetta. Anzi come detto il progetto BlazorTest.Server serve proprio a questo (oltre che a fornire eventuali web-api) !

Se però si fosse utilizzato il template 2, Blazor client-side non si avrebbe la possibilità di avere un progetto server: tale template, infatti, come detto implementa solo il codice che sarà eseguito dal lato client all’interno del browser (in sostanza a BlazorTest.Client).

Linkografia

Blazor pressapochista – Introduzione – Prima parte
Host a client-side Blazor app as a static website in Azure Storage
Web Server for Chrome