Qualche anno fa avevo iniziato a studiare il framework Blazor quando era ancora basato sulla versione di .NET antecedente alla 5.0 (v3.1 del 2019), realizzando anche una piccola applicazione WebAssembly per processare tag audio col browser, ma incontrai molte difficoltà tecniche, principalmente nell’interoperabilità JavaScript / CSS, e soprattutto il paradigma della programmazione mi sembrava eccessivamente farraginoso. L’idea di sviluppare applicazioni web front-end con il magnifico linguaggio C# è molto attraente. Tuttavia, la piattaforma, basata esclusivamente sui browser, impone limiti significativi al livello di astrazione raggiungibile.
La visione (utopistica) per la quale nutrivo grandi speranze era di sviluppare 1, o al massimo 2 layer di librerie Blazor di basso livello per l’astrazione base fondamentale. Su questa base fondamentale, avrei poi potuto adattare una serie di librerie .NET C# di più alto livello che, in parte, avevo già sviluppato. Questo mi avrebbe consentito di disaccoppiare il codice di alto livello dalla piattaforma sottostante e poter poi realizzare, con lo stesso identico code base, sia applicazioni web sia la loro versione desktop … e anche mobile! Ecco è rimasto giusto un sogno perchè poi le cose sono andate totalmente in merda 😏
Ma oggi con Blazor basato su .NET 8/9 le cose sembrano essere migliorate…
Ci sono tante novità interessanti come le Blazor Web App che consentono di creare applicazioni sia Server sia WebAssembly, potendo configurare il tipo di interazione in maniera granulare a livello di singolo componente/pagina, mentre in passato eri obbligato a scegliere sin dall’inizio dello sviluppo O un’app Server O un’app WebAssembly. Poi ora c’è l’Hot Reload: una froceria che ti permette di applicare le modifiche al codice di un’applicazione in esecuzione senza doverla riavviare completamente. Anche se a me stimolano poco la fantasia visto che l’interesse principale rimane esclusivamente il WebAssembly (per ora). Infatti, la più importante novità che considero eccitante è il miglioramento dell’interoperabilità con JavaScript e CSS!
Adesso le cose sono notevolmente migliorate! Prima di tutto ora ogni singolo componente/pagina Blazor può avere il suo codice JavaScript e CSS isolato, privato, non-pubblico e già solo questa è una grandissima novità. Inoltre adesso le chiamate bi-direzionali fra JavaScript e C# possono usare gli attributi [JSImport] e [JSExport], con una semplificazione estrema (e pulita) rispetto al passato. Infine è stata introdotta l’interfaccia IJSObjectReference che secondo me ha segnato la svolta definitiva nell’interoperabilità tra Blazor e JavaScript, poiché per la prima volta ha permesso ai metodi C# di ottenere e mantenere un riferimento diretto a un oggetto JavaScript; superando una delle principali limitazioni con cui mi ero scontrato prima di .NET 5.
La mia fiducia in Microsoft è ristabilita 😊 Ad ogni modo le sfide ingegneristiche non sono per niente terminate…
In questo momento sto sviluppando una libreria wrapper per adattare la File and Directory Entries API su Blazor. Uno degli ostacoli maggiori è gestire il caricamento iniziale del modulo JavaScript della libreria in ambiente “InProcess” (cioè WebAssembly). In questo ambiente, è possibile eseguire chiamate sincrone usando istanze di tipo IJSInProcessObjectReference. Il problema nasce proprio dal fatto che il caricamento del modulo (tramite import) è un’operazione intrinsecamente asincrona. Inizialmente avevo deciso di non implementare il servizio (singleton o scoped) e delegare ad ogni singola istanza del wrapper il caricamento autonomo del modulo tramite un Lazy<Task<IJSObjectReference>> e che ho visto essere un pattern di progettazione piuttosto comune nelle librerie NUGET di Blazor. Il problema nel mio specifico caso è che alcune istanze del wrapper “InProcess”, dal loro interno, devono poter creare nuove istanze di altre classi del wrapper, e in maniera sincrona, ma la natura del caricamento del modulo (tramite comando “import”) su Blazor è intrinsecamente asincrona. Facciamo un esempio pratico:
- Ho una classe di tipo
JFileSystemche è il mio wrapper della classe FileSystem dell’API. - La proprietà
rootrestituisce una nuova istanza della classeJFileSystemDirectoryEntryche è il mio wrapper di FileSystemDirectoryEntry dell’API. - Quando su Blazor accedo alla proprietà
fileSystem.root, questa internamente crea una nuova istanza diJFileSystemDirectoryEntryche però al suo interno non ha il modulo JavaScript ancora caricato e per usare il comando “import” c’è bisogno necessariamente di una chiamata asincrona. - Esempio di codice concreto:
var root = fileSystem.root;
var name = root.Name;
// ^ causa eccezione perchè non ha ancora il modulo caricato!
La soluzione iniziale che avevo adottato è quella di poter invocare il metodo InitializeAsync(), altro pattern piuttosto comune, ma questo andava di fatto a snaturare il percorso sincrono che si poteva seguire nella libreria. Per cui penso che obbligherò l’uso del servizio per istanziare le classi del wrapper e togliere il problema alla radice. Ci sono comunque alcuni accorgimenti tecnici di design che hanno stimolato la mia creatività, come implementare un factory pattern basandomi su espressioni lambda compilate, che mi consente di istanziare oggetti a una velocità quasi pari a quella ottenibile con il costruttore diretto tramite la parola chiave ‘new‘. Oppure all’uso di una classe nested per gestire la versione “InProcess” piuttosto che realizzare una gerarchia di base completamente dipendente.
Vedremo in corso d’opera come evolveranno le righe di codice, è decisamente ancora troppo presto, per il momento mi sento genuinamente positivo sulle possibilità offerte da Blazor 😉👍