Autenticazione e autorizzazione con ASP.NET Core, OpenId Connect e Azure Active Directory

L'uscita di ASP.NET Core – nell'estate del 2016 – si è accompagnata da un sistema di autenticazione/autorizzazione tutto nuovo e basato, come il resto del framework, sul concetto di middleware.

Il middleware è un pezzo di codice che si innesta nella pipeline di ASP.NET con lo scopo di occuparsi di una specifica attività nell'ambito della gestione di una richiesta HTTP. In questo caso, il middleware è responsabile di fare in modo che il richiedente sia autorizzato ad effettuare la richiesta, cioè ad accedere alla relativa risorse. Se il concetto non vi è del tutto chiaro, potete approfondirlo leggendo le nozioni fondamentali di middleware in ASP.NET Core.

Come era forse lecito attenderci, il nuovo sistema si è dimostrato un po' immaturo alla prova dei fatti: in particolare il meccanismo di integrazione tra i diversi middleware di autenticazione era un po' complesso e poteva portare a situazioni incoerenti in determinati scenari.

Il Team di ASP.NET ha quindi deciso di rivedere (nuovamente) il sistema dalle fondamenta e la versione aggiornata è uscita contemporaneamente ad ASP.NET Core 2.

In questa serie di post andremo alla scoperta dei suoi meccanismi di funzionamento, concentrandoci su uno scenario ben definito, quello dell'autenticazione/autorizzazione attraverso Azure Active Directory (in seguito AAD) usando il protocollo OpenId Connect (OIDC).

La maggior parte dei post che trattano questo argomento iniziano immancabilmente con una tediosa spiegazione della differenza tra autenticazione ed autorizzazione. Io mi limito a dire che l'autenticazione è il processo di determinazione dell'identità dell'utente, mentre l'autorizzazione definisce quali siano i suoi permessi dopo che l'identità è stata accertata. Basta. Se volete masochisticamente sviscerare ulteriormente la questione, trovate tonnellate di post in materia.

L'approccio che cercherò di tenere, ove possibile, è di partire dal codice e di introdurre i concetti teorici nel corso della trattazione. Io userò Visual Studio 2017 (versione 15.7, ma Visual Studio Code dovrebbe essere ugualmente adatto) e .NET Core SDK 2.1.200 (per sapere quale versione state utilizzando è sufficiente digitare dotnet --version dal prompt dei comandi).

Per tener conto di chi utilizza IDE diversi da Visual Studio, partiremo creando lo scheletro del progetto a riga di comando usando la CLI dotnet. Dopo esserci posizionati nella cartella in cui vogliamo creare il progetto, i comandi da lanciare sono i seguenti:

  • dotnet new sln --name LearningAuth (se pensiamo di usare Visual Studio e vogliamo creare il file di solution);
  • dotnet new web --name LearningAuth.WebApp (per creare un'applicazione web vuota)
  • dotnet sln add .\LearningAuth.WebApp\LearningAuth.WebApp.csproj (sempre se usiamo Visual Studio, per aggiungere l'applicazione alla solution)

Non mi addentro nei dettagli del progetto creato, perchè la documentazione Microsoft sui concetti fondamentali di ASP.NET Core è decisamente esaustiva.

Il progetto così come lo abbiamo creato si limita a mostrare a video la scritta "Hello world!" indipendentemente da quale sia la risorsa richiesta (ossia la URL digitata) e ovviamente non ha alcuna nozione di risorsa protetta da autorizzazione. Per introdurre il concetto, creiamo un nuovo semplice controller, la relativa view e configuriamo l'applicazione in modo che sia in grado di servirla al client.

Iniziamo creando una cartella Controllers nella root del progetto e aggiungendo un nuovo Controller (che chiameremo HomeController) al suo interno. Il controller avrà per ora un solo metodo Index che dovremo decorare con l'attributo Authorize:

Per creare la view clicchiamo con il destro all'interno del metodo e scegliamo Add View... dal menu contestuale:

Conserviamo le impostazioni di default suggerite da Visual Studio e clicchiamo su Add:

La view di risultante sarà estremamente scarna ma per ora ampiamente sufficiente allo scopo. Ci limiteremo ad aggiungere il codice @User.Identity.Name per stampare il nome dell'utente autenticato:

L'unico problema è che la view si basa una pagina di layout che attualmente non è presente nel nostro progetto. Creiamo allora una nuova cartella Shared sotto Views e aggiungiamo al suo interno una MVC View Layout Page:

Potremmo impostare la pagina di layout direttamente dentro la view, ma è più comodo aggiungere invece una MVC View Start Page e una MVC View Imports Page direttamente sotto la cartella Views. Potranno servirci anche in futuro.

Infine la configurazione: a differenza di quanto avveniva in ASP.NET Core 1, nella versione 2 è stato definito il nuovo metapackage Microsoft.AspNetCore.All che evita la necessità di includere singolarmente la pletora di pacchetti che compongono ASP.NET Core ed EntityFramework Core. Per tale motivo possiamo configurare direttamente il middleware di MVC senza dover manualmente aggiungere il package relativo.

Apriamo quindi il file Startup.cs e configuriamo il middleware di MVC. Per prima cosa dobbiamo registrare i servizi necessari ad MVC nello IoC container che è parte dell'architettura di Dependency Injection nativa di ASP.NET Core. Possiamo farlo aggiungendo il codice relativo nel metodo ConfigureServices. Quindi possiamo aggiungere il middleware MVC alla pipeline nel metodo Configure (che a mio modesto parere sarebbe stato meglio si chiamasse ConfigurePipeline per togliere ogni dubbio, ma tant'è). Il risultato finale deve essere questo:

Per concludere, è opportuno già da subito passare dal protocollo HTTP a quello HTTPS per garantire che le comunicazioni avvengano in forma crittografata in modo tale da prevenire il "tampering" dei dati. Apriamo le proprietà del progetto e nella scheda Debug selezioniamo la casella Enable SSL. Copiamo quindi l'indirizzo (tenetelo a mente perchè ci servirà anche in seguito) e incolliamolo come App URL:

Se preferite potete modificare direttamente il file launchSettings.json impostando le chiavi iisSettings:issExpress:applicationUrl e iisSettings:issExpress:sslPort):

Proviamo a lanciare nuovamente la nostra applicazione è il risultato sarà il seguente:

Il messaggio è forse un po' criptico ma in definitiva ci fornisce due informazioni interessanti: ci dice che l'Authentication Scheme non è stato trovato, e ci dice anche che l'errore si è verificato in un metodo chiamato ChallengeAsync.

Ma cos'è un authentication scheme? È un identificativo univoco del metodo di autenticazione che vogliamo usare nella nostra applicazione, o in una parte di essa. Anche se non è un caso molto frequente, dobbiamo considerare che potrebbe essere necessario gestire più di un tipo di autenticazione all'interno della stessa applicazione (ad esempio distinguendo i controller MVC per i quali vogliamo impostare un'autenticazione basata su cookie da quelli WebAPI per i quali vogliamo invece usare dei token JWT). Per consentire ad ASP.NET di gestire tali scenari, ogni metodo di autenticazione viene registrato tra i servizi con uno specifico identificativo univoco (che altri non è se non un nome in formato stringa).

Quando aggiungiamo un attributo authorize nel codice, possiamo indicare quale metodi di autorizzazione (tra quelli registrati) vogliamo usare per quel particolare controller o action, specificando il relativo schema. Se non lo facciamo (come nel nostro caso), il middleware userà lo schema di default. In questo caso non abbiamo definito neanche quello, da cui l'eccezione che abbiamo riscontrato.

Il problema deriva dal fatto che abbiamo sì richiesto che la nostra action fosse protetta da autorizzazione, ma non abbiamo configurato quale metodo di autenticazione vogliamo usare.

Come detto in apertura, siamo interessati ad esplorare lo scenario dell'autenticazione con Azure Active Directory usando il protocollo OpenId Connect. Sarebbe ora il momento di approfondire i fondamenti teorici di OpenId Connect e del middleware di autenticazione. Ma, visto che il manifesto Agile ci dice che è più importante un "software funzionante" di una "documentazione esaustiva" (sperando che Felice non ci legga) useremo invece un approccio mistico: arriviamo fideisticamente ad un applicazione funzionante e vi prometto poi di tornare indietro per dare un senso alle azioni intraprese.

Cominciamo abbandonando momentaneamente la nostra applicazione per spostarci sul portale di registrazione delle applicazioni di Microsoft. Visto che vogliamo usare AAD, dobbiamo prima "farci conoscere" per instaurare quel rapporto di "trust" tra la nostra applicazione e AAD che ci consentirà di delegargli la parte di autenticazione.

Registriamo quindi la nostra applicazione sul portale dopo aver fatto login con la nostra utenza, cliccando su Aggiungi un'app (non mi chiedete perché alcune pagine sono in italiano e altre in inglese):

Inseriamo il nome della nostra applicazione è clicchiamo su Create (ci sarebbe anche la possibilità di effettuare un setup guidato selezionando la casella relativa, ma per applicazioni ASP.NET Core tale opzione si risolve in un link ad un esempio di configurazione):

Dopo qualche istante veniamo indirizzati alla pagina con le proprietà della nostra applicazione appena registrata. Qui dobbiamo specificare che si tratta di un'applicazione web aggiungendo una nuova piattaforma nella sezione relativa:

Aggiungiamo una piattaforma Web e impostiamo quindi la URL di reindirizzamento, mantenendo la casella Consenti flusso implicito selezionata e ignorando per ora la URL di disconnessione:

La URL va chiaramente impostata in accordo con quella della nostra applicazione (https://localhost:44303/ nel mio caso) aggiungendo l'endpoint signin-oidc (vedremo in seguito di che si tratta).

Possiamo ora salvare le modifiche ma, prima di uscire, annotiamoci l'ID applicazione che si trova in alto sotto al nome:

Ora che Azure Active Directory "conosce" la nostra applicazione, possiamo configurarla per gestire l'autenticazione/autorizzazione in nostra vece per mezzo del protocollo OpenId Connect. Fortunatamente Microsoft ci mette a disposizione un middleware (in ASP.NET Core 2 questa definizione è leggermente scorretta ma passatemi l'imprecisione per evitare ulteriore complessità) che si prende carico di tutte le cerimonie di comunicazione tra la nostra applicazione e un generico provider OIDC.

La prima cosa da fare è, al solito, registrare i servizi nello IoC container:

Questo codice aggiunge i servizi generici del middleware di autenticazione e poi quelli specifici di OIDC. Ricorderete che il messaggio di errore che abbiamo ricevuto in precedenza parlava della mancata definizione di un DefaultChallengeScheme. Per risolvere il problema impostiamo tale schema pari allo schema di default di OIDC (cioè la stringa OpenIdConnect).

Ovviamente, per quanto intelligente possa essere, il middleware non è in grado di sapere con quale provider vogliamo comunicare. È quindi necessario un minimo di configurazione che può essere fatta direttamente nel codice o, come in questo caso, delegata al file di configurazione. Il modo più semplice di farlo è sfruttare il metodo di estensione Bind dell'interfaccia IConfigurationSection. È sufficiente che la struttura della classe di opzioni che vogliamo riempire corrisponda alla struttura della particolare sezione del file di configurazione. Nel nostro caso le chiavi presenti nella sezione OpenIdConnect saranno usate per valorizzare l'oggetto options di classe OpenIdConnectOptions:

L'Authority indica il tenant AAD da interrogare ed è composto dalla URL https://login.microsoftonline.com più l'identificativo del nostro tenant. Come identificativo possiamo usare il nome, ma l'ID è preferibile data la sua immutabilità certa nel tempo. Nel recuperare l'ID del nostro tenant AAD, possiamo aprire il portale di Azure e navigare in Azure Active Directory > Proprietà: ID Directory è il nostro uomo:

Se il vostro account ha accesso a più tenant, ricordatevi di verificare di essere collegati alla directory giusta verificando in altro a destra del portale:

Sistemata l'authority possiamo passare al ClientId: altro non è l'ID Applicazione che abbiamo annotato in fase di registrazione sul portale.

L'ultimo passo è aggiungere il middleware di autenticazione alla pipeline, ovviamente prima che MVC abbia modo di intervenire per gestire la richiesta:

Tutto sembra essere a posto, possiamo provare a lanciare nuovamente la nostra applicazione. Se tutto va bene dovremmo essere reindirizzati alla pagina di login di AAD:

E quindi, dopo essersi autenticati, nuovamente alla nostra applicazione... dove però ci attende una sorpresa:

La prima cosa che notiamo è che AAD ha effettivamente effettuato un redirect alla pagina https://localhost:44303/signin-oidc. Nonostante tale pagina non sia presente nella nostra applicazione, in qualche modo ASP.NET è riuscito a gestirla: vedremo prossimamente come e perchè.

La seconda è che il metodo SignInAsync è andato in eccezione per la mancanza dello schema DefaultSignInScheme, il che ci lascia particolarmente perplessi visto che il login è stato gestito da AAD e che sembrerebbe essere andato a buon fine. Che fa quindi il metodo SignInAsync? E che vuol dire quel messaggio? Lo scopriremo nel prossimo post, per ora limitiamoci a modificare leggermente il codice in Startup.cs in modo da farlo funzionare:

Stavolta ci siamo:

Happy coding!

Il codice di questo post è disponibile su GitHub.

Creare un’applicazione Aurelia con la dotnet CLI e i JavaScriptServices

Buone notizie per chi come me ha scelto Aurelia come framework di riferimento per scrivere Single Page Application. Un po' alla volta la creatura di Rob Eisenberg si sta ritagliando il suo spazio ed è ora un firstclass citizen nell'ultima versione della dotnet CLI, quella disponibile nella SDK 1.0 rc4 build 004771.

Per i pochi che si trovassero spaesati, stiamo parlando del tool a riga di comando (CommandLine Interface) che consente di eseguire tutte le operazioni legate alla generazione, esecuzione e pubblicazione di un applicativo basato su .NET Core.

È da qualche giorno disponibile una serie di pacchetti NuGet che estende i template di default della CLI consentendo di generare SPA basate sui principali framework presenti sul mercato. Accanto ad Angular e React fa bella mostra di sé anche il template per generare applicazioni basate su Aurelia. Vediamo cosa dobbiamo fare per utilizzarlo.

Come prima cosa dobbiamo assicurarci di avere installata la versione di SDK corrispondente alla build 004771 o superiore che potete eventualmente scaricare dal link precedente:

Dopo averlo installato, aprendo un prompt dei comandi e digitando dotnet --version dovreste ricevere l'indicazione 1.0.0-rc4-004771. A questo punto possiamo digitare dotnet new --help per verificare quali siano i template disponibili per la creazione di un nuovo progetto. Se avete appena installato la CLI potreste dover attendere un po'per il popolamento della cache locale:

Di base dovreste avere disponibili di seguenti template:

Per cui potremmo ad esempio digitare dotnet new web per creare una nuova applicazione ASP.NET Core vuota da usare come base per i nostri sviluppi.

Per installare i template dedicati alle applicazioni SPA dobbiamo invece digitare dotnet new --install Microsoft.AspNetCore.SpaTemplates::*:

Dopo aver installato un po' di package assortiti (nel mio caso 111) i nuovi template dovrebbero essere a vostra disposizione:

Proviamo subito a vedere "l'effetto che fa". Spostiamoci nella cartella dove vogliamo creare il progetto e digitiamo prima dotnet new aurelia e quindi dotnet restore. Il primo comando crea il progetto a partire dal template, il secondo effettua il restore dei due pacchetti NuGet utilizzati (NodeServices e SpaServices):

Il template contiene al suo interno il file di configurazione project.json che elenca tutti i pacchetti NPM necessari. Per ripristinarli è sufficiente digitare npm install dallo stesso prompt dei comandi. Dopo un po' di attesa abbiamo finalmente il nostro progetto pronto e possiamo lanciarlo con il comando dotnet run. Per default il progetto creato si mette in ascolto sulla porta 5000 ed è quindi sufficiente digitare http://localhost:5000 in un qualsiasi browser per navigarlo:

Ci sono alcune differenze da notare rispetto al progetto che sto portando avanti nella mia serie su Aurelia:

  • Il progetto generato dal template è per Visual Studio 2017. Come conseguenza il file di progetto xproj è stato sostituito dal file csproj e il file project.json è sparito. Per modificare il progetto potete quindi usare Visual Studio 2017, Visual Studio Code o ovviamente il vostro editor preferito. Il progetto è inoltre basato su ASP.NET Core 1.1 (invece che sull'1.0 come nella mia serie), ma questo è un aspetto che non al momento ha impatti e che affronteremo a breve anche noi;
  • Come si vede nello screenshot, il progetto così lanciato gira in ambiente di produzione, a differenza di quanto succede lanciando il progetto da dentro Visual Studio. Questo è dovuto al fatto che Visual Studio definisce per noi la variabile ASPNETCORE_ENVIRONMENT e la imposta al valore Development. Per ottenere lo stesso comportamento a riga di comando dobbiamo definire manualmente la stessa variabile, andando nell'apposita sezione del pannello di controllo o utilizzando il comando setx ASPNETCORE_ENVIRONMENT "Development" (in questo caso è necessario aprire un nuovo prompt dei comandi per "vedere" la nuova variabile);
  • Come avevamo avuto modo di dire, Aurelia supporta diversi package manager. Nella nostra serie stiamo usando JSPM mentre il progetto generato dal template per uniformità tra i vari framework utilizza invece Webpack. Il progetto include anche i concetti di bundling e minification che ancora non abbiamo affrontato nella serie, e potrebbe quindi essere un po' spiazzante il fatto di non trovare nella cartella wwwroot nessuno dei ViewModel e delle View che ci aspetteremmo di trovare (a dire il vero i file html mancano del tutto). Non è il momento di spiegare nel dettaglio il funzionamento di Webpack, in ogni caso se volete modificare il progetto i file che vi interessano si trovano nella cartella ClientApp;
  • Il progetto usa TypeScript invece che ES2015 per definire i ViewModel. Come vedrete se vi addentrerete tra i file, le differenze sono minime e si concretizzano soprattutto nella possibilità di tipizzare variabili e parametri. Se guardate in wwwroot vi accorgete che gli unici due file JavaScript (oltre a essere apparentemente incomprensibili) sono invece caratterizzati da una sintassi ES5. Questo è il risultato dell'uso del bundling di Webpack per ottimizzare prestazioni e compatibilità. Per capire meglio di cosa parlo provate a confrontare il tab Network dei Developer Tools (cioè l'F12) del vostro browser preferito: la differenza tra il numero di file caricati dal progetto della nostra serie e quelli di questo progetto vale più di mille parole (ma non disperate, ci arriveremo a suo tempo);
  • Il bundling di Webpack viene gestito attraverso un middleware ASP.NET Core dedicato che si trova nel package NuGet SpaServices referenziato nel progetto. Tale middleware è caricato nella pipeline di ASP.NET Core solo in ambiente di sviluppo ed è per questo che è fondamentale impostare la variabile ASPNETCORE_ENVIRONMENT come prima descritto. In caso contrario le vostre modifiche ai file presenti in ClientApp non sarebbero prese in considerazione da Webpack e i file in wwwroot rimarrebbero quelli originali
    ;
  • In teoria i JavaScriptServices supportano l'Hot Module Replacement ma in pratica, come si vede nello screenshot qui sopra, questa opzione è disabilitata nel template di Aurelia per via di un problema con il plugin che consente di fare funzionare il framework con Webpack. Intanto che il problema viene risolto cerchiamo di capire cosa ci stiamo perdendo: abbiamo detto in precedenza che, quando facciamo una modifica ai nostri file di progetto (html o js/ts), Webpack si occupa di aggiornare i file presenti in wwwroot che saranno poi quelli serviti al client. In un'applicazione SPA questo non è sufficiente per "vedere" la modifica nel browser. La coppia View/ViewModel è stata infatti già richiesta al server e da quel momento in poi il framework SPA la riutilizza per ridurre la necessità di effettuare richiesta al server. Per ottenere la vista modificata dobbiamo aggiornare l'intera pagina (in genere con F5, presupponendo di avere la cache del browser disabilitata) ma questo vuol dire ovviamente perdere completamente il contesto di esecuzione del nostro test, ritrovandosi con una nuova istanza applicativa. Ecco l'HMR vuole risolvere appunto questo problema, iniettando html e js aggiornati nella SPA senza perdere il contesto di esecuzione corrente.

Happy coding!

End-to-End Javascript Testing a DevOps@Work 2017

Sono ora disponibili slide (su docs.com) e demo (su GitHub) della sessione End-to-End Javascript Testing che ho presentato all'evento DevOps@Work 2017 di inizio febbraio.

Nel README, associato al repository sulla branch master, ci sono le istruzioni da seguire per far funzionare la demo. Potete navigare tra le tre branch per trovare il codice iniziale e quello delle due demo (la prima senza usare il pattern Page Object, la seconda usandolo).

Per qualsiasi problema non esitate a contattarmi via email o Twitter.

Colgo l'occasione per ringraziare tutti i partecipanti per l'attenzione e l'interesse che mi hanno dimostrato pur essendo a fine giornata. Come promesso ai tanti cui - vista l'ora tarda - non ho potuto dare risposta dopo la sessione, nelle prossime settimane affronterò qui sul blog alcuni dei temi che non abbiamo avuto modo di trattare nel corso della sessione.

Happy coding!

Aurelia e ASP.NET Core: aggiornare la versione di Aurelia usando JSPM

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Prima di proseguire nel nostro cammino è opportuno aggiornare la versione di Aurelia che stiamo utilizzando per tener conto delle nuove feature e delle fix rilasciate nel frattempo dal team di sviluppo. È possibile che la vostra installazione di Aurelia sia già aggiornata all'ultima versione, a meno che non abbiate cominciato il viaggio in contemporanea con il mio primo post. In ogni caso questa procedura vi potrà essere utile in futuro.

Ci sono due cose che possiamo aggiornare: JSPM e i pacchetti Aurelia che abbiamo aggiunto al nostro progetto (aurelia-framework e aurelia-bootstrapper).

Come sappiamo abbiamo installato JSPM tramite NPM aggiungendo direttamente la referenza dentro il file package.json. Per aggiornarlo abbiamo ora diverse opzioni.

La cosa più semplice è tornare a sfruttare la buona integrazione di NPM in Visual Studio. Basta aprire il nodo Dependencies del nostro progetto e nella cartella npm cliccare con il tasto destro sul pacchetto jspm (che nel mio caso si trova alla versione 0.16.48). Dal menu contestuale possiamo quindi scegliere Update Package per avviare l'operazione di (eventuale) aggiornamento:

In realtà, pur essendo ad oggi disponibile la versione 0.16.52, se proviamo a lanciare l'aggiornamento non accade nulla e il progetto rimane alla versione 0.16.48. Ciò dipende dal modo in cui ho configurato JSPM all'interno del file package.json.

Per capire meglio, passiamo momentaneamente alla seconda opzione cui accennavo in precedenza… la famigerata "riga di comando". Come tutti i tool nati fuori dal mondo Microsoft, NPM dispone ovviamente di un'interfaccia a riga di comando che è la principale modalità di utilizzo dello strumento (un po' come per .NET Core che dispone di una sua CLI, dotnet, che viene poi wrappata da Visual Studio per svolgere le medesime funzionalità con una UI grafica).

Quando lo installiamo, Visual Studio provvede ad installare una sua istanza di Node (comprensiva ovviamente di NPM) nella cartella C:\Program Files (x86)\Microsoft Visual Studio 14.0\Web\External (almeno per Visual Studio 2015). Si tratta dell'istanza che viene utilizzata dalla IDE per svolgere in background le attività scatenate ad esempio dall'Update Package di cui abbiamo parlato sopra. Se infatti guardiamo nella sezione Bower/npm della finestra di Output possiamo vedere che l'operazione che abbiamo appena eseguito è stata trasformata in una chiamata al comando npm update jspm:

Per quanto sia possibile utilizzare quella stessa istanza per i nostri scopi, addentrandosi nello sviluppo web basato su Aurelia (o su altri framework simili) è bene installare Node (e quindi NPM) direttamente sulla propria macchina. Per farlo andiamo sul sito di Node e scarichiamo l'ultima LTS disponibile per il nostro sistema operativo (nel mio caso la 6.9.5 per Windows x64).

Dopo aver scaricato il file di setup, lanciamolo e seguiamo le semplici istruzioni fino ad arrivare al termine del processo. L'installazione provvede a registrare la cartella di installazione tra le PATH di sistema rendendo in questo modo possibile utilizzare NPM da qualunque prompt dei comandi:

Sfruttiamo subito questa possibilità andando ad installare JSPM in maniera "globale" sulla nostra macchina usando il comando npm install -g jspm e apriamo quindi un altro prompt dei comandi nella cartella del progetto, utilizzando ad esempio la comoda voce disponibile nel menu contestuale dei Productivity Power Tools:

Dal prompt digitiamo npm outdated:

Questo comando consente di verificare quali siano i pacchetti obsoleti presenti nella nostra applicazione. Nel nostro caso ci indica che il pacchetto jspm si trova attualmente alla versione 0.16.48 (Current) mentre l'ultima disponibile è la 0.16.52 (Latest). Ci dice però che il pacchetto che noi vogliamo è proprio il 0.16.48 (Wanted) e per questo motivo il comando di update non lo aggiorna anche se lo lanciamo da riga di comando:

Come dicevo, tale comportamento è dovuto al modo in cui ho referenziato JSPM al momento della sua installazione. Ci sono infatti diversi modi per configurare gli aggiornamenti dei pacchetti in NPM e i principali e più utilizzati sono tre:

  • Una versione specifica: indicando uno specifico numero di versione (come ho fatto io scrivendo "jspm" : "0.16.48") istruiremo NPM ad utilizzare sempre e soltanto quella particolare versione (per questo motivo gli aggiornamenti che abbiamo tentato non sono andati a buon fine);
  • Una versione con tutte le sue patch: indicando un numero di versione preceduto dalla tilde (~) istruiremo invece NPM ad utilizzare quella versione o una qualsiasi delle sue successive patch, ad esempio la 0.16.52 e più in generale la 0.16.y con y >= 48;
  • Una versione con tutte le sue minor release: indicando un numero di versione preceduto dall'accento circonflesso (^) istruiremo infine NPM ad utilizzare quella versione o una qualsiasi delle sue successive minor release, ad esempio la 0.17.4 e più in generale la 0.x.y con x >= 16 e y qualsiasi.

NPM utilizza il semantic versioning per assegnare i numeri di versione ai pacchetti, quindi sia utilizzando la tilde che l'accento circonflesso ci garantiamo che nessun aggiornamento potrà portare delle "breaking changes" che possano determinare un disservizio della nostra applicazione (avremo modo di tornare su questo aspetto a breve).

Come dicevo queste tre opzioni sono così comuni che sono pienamente supportante dall'Intellisense in fase di modifica manuale del file package.json:

Modifichiamo quindi l'impostazione usando l'impostazione minor release ed eseguiamo nuovamente l'aggiornamento di JSPM.

Questa volta dovremo ritrovarci con la versione 0.16.52 installata.

Aggiornato JSPM è il momento di aggiornare Aurelia. In questo caso Visual Studio non ci supporta e dobbiamo quindi necessariamente andare a riga di comando. Apriamo nuovamente un prompt nella cartella del progetto e digitiamo jspm update:

JSPM ci notifica gli aggiornamenti che vengono fatti. Come possiamo vedere anche nel file config.json, dei due pacchetti solo aurelia-framework è stato aggiornato (dalla 1.0.7 alla 1.0.8) determinando un corrispettivo aggiornamento dei pacchetti ad esso collegati:

Anche in questo caso il comportamento di JSPM è dipeso dalle impostazioni che abbiamo indicato al momento dell'installazione (o per meglio dire in questo caso, che il tool Package Installer ha indicato per noi). In questo caso avevamo usato l'accento circonflesso, che ha determinato l'aggiornamento alla versione 1.0.8 di aurelia-framework e il mantenimento della versione 1.0.1 dell'aurelia-bootstrapper:

In effetti, se cerchiamo su NPM il pacchetto aurelia-bootstrapper, possiamo verificare che l'ultima versione disponibile è la 2.0.1 che ha quindi un numero di versione major differente:

Se cerchiamo direttamente su GitHub, possiamo verificare che l'ultima versione disponibile avente versione major 1 è proprio la 1.0.1 e per tale motivo JSPM non ha aggiornato questo pacchetto (che come detto in precedenza potrebbe avere delle breaking changes) come scelta conservativa per evitare malfunzionamenti alla nostra applicazione:

Per forzare l'aggiornamento del bootstrapper (cosa che non vi consiglio di fare, almeno fino al prossimo post) dobbiamo modificare il file package.json, ad esempio come segue:

Se eseguiamo nuovamente un jspm update avremo finalmente la nuova versione del bootstrapper installata nel progetto:

Se lanciamo nuovamente l'applicazione per verificare che tutto sia andato come previsto, ci accorgiamo purtroppo che la possibile breaking change di aurelia-bootstrapper ha avuto uno spiacevole impatto sul caricamento di uno dei moduli di Aurelia, l'aurelia-pal-browser. Come risultato l'applicazione non parte più e ci è chiarissimo il perché l'impostazione di base per regolare gli aggiornamenti sia quella con l'accento circonflesso che evita il passaggio automatico a major version successive:

Nel prossimo post cercheremo di capire cosa è successo e come rimediare all'inconveniente.

Happy coding!

Un po’ di ALM non fa mai male

Nelle prossime settimane sarà impegnato in due eventi su ALM e DevOps organizzati da DomusDotNet e GetLatestVersion.it.

Il 31 gennaio dalle 18.00 alle 19.00 sarò on-line su Live Meeting insieme agli altri amici di GetLatestVersion.it per Ask Me Anything, una sessione aperta di domande e risposte sulle tematiche legate all'ALM e a DevOps. Se avete qualche dubbio in materia, che sia teorico o pratico, è l'occasione ideale per fugarlo.

Il 10 febbraio sarò invece presso la sede Microsoft di Roma per DevOps@Work 2017, l'ormai classico appuntamento di inizio anno organizzato da DomusDotNet con la partecipazione di GetLatestVersion.it. Anche quest'anno ci saranno due track e tante sessioni tra cui poter scegliere con speaker di qualità eccezionale.

Per l'occasione introdurrò i più mattinieri a Visual Studio Team Services, cercando di realizzare l'ardua impresa di passare in un'ora dal requisito di business al codice funzionante su Azure attraverso pianificazione dell'attività, organizzazione del codice sorgente, verifica della qualità per mezzo di test automatizzati e gestione delle build e delle release.

Per gli stakanovisti dell'evento, a fine giornata parlerò invece di testing in ambiente JavaScript, esaminando le tecniche ed i framework che ci consentono di garantire la qualità anche delle nostre applicazioni web con una forte componente client, come le Single Page Application.

Non esitate a registrarvi ai link degli eventi. Vi aspetto on-line e di persona.

Happy coding!

Aurelia e ASP.NET Core: binding di base in Aurelia

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Al termine del post precedente ci eravamo lasciati con la necessità di idratare la nostra View con i dati provenienti dal ViewModel utilizzando un meccanismo detto binding. Vediamo quindi quali strumenti Aurelia ci mette a disposizione per gestire questa operazione.

Attribute binding, event binding e string interpolation

Riprendiamo quindi il nostro template e vediamo in che modo dobbiamo modificarlo per aggiungere la sintassi relativa al binding caratteristica di Aurelia:

Cominciamo da title (1) e master (2) per i quali utilizzeremo la string interpolation. Come suggerisce il nome, si tratta di una sintassi particolarmente utile nelle situazioni in cui si voglia usare testo dinamico proveniente dal ViewModel eventualmente insieme a testo statico inserito direttamente nella View. Non sorprenderà che Aurelia gestisca questo scenario facendo riferimento agli standard ES2015 per la string interpolation, cioè con la sintassi ${expression} in cui expression è una qualsiasi espressione JavaScript. Ad esempio <div>static text ${game.description} more static text </div> per inserire il valore della proprietà description in un div intercalandolo con del testo statico. Nel nostro caso avremo quindi:

Passiamo al button per unirsi ad una partita (3) e al link per partecipare ad una sessione di gioco (4). In questo caso abbiamo due problemi da risolvere: dobbiamo rendere visibili i due elementi a seconda del valore delle proprietà canJoin e canPlay, e dobbiamo eseguire il metodo join in seguito al click sul button. Per risolverli useremo le altre due sintassi previste da Aurelia, attribute ed event binding, più un'altra particolarità estremamente flessibile e potente di Aurelia: i custom attribute.

L'attribute binding è caratterizzato dalla sintassi attribute.command="expression". Dato un qualunque attributo (attribute), è possibile valorizzarlo con in risultato di un'espressione JavaScript (expression) attraverso l'applicazione di uno tra i comandi supportati (command). Approfondiremo il concetto dei comandi (che servono per definire direzione e frequenza degli aggiornamenti) in un prossimo post, per ora accontentiamoci di usare il comando generico bind che determina in maniera intelligente il tipo di comando specifico da applicare (con una modalità detta binding adattativo che è caratteristica di Aurelia). Potremmo ad esempio scrivere <a href.bind="game.url">Vai alla partita</a>. per utilizzare il contenuto della proprietà url come destinazione del link Vai alla partita.

Nel nostro caso abbiamo però un altro problema. In HTML5 non esiste (ovviamente) un attributo specifico che specifichi se vogliamo includere o meno un certo elemento nel DOM della pagina. Essendo una questione centrale in un'applicazione SPA, ogni framework che si rispetti ha dovuto affrontarla e risolverla. Invece di inventare una sintassi proprietaria (e magari astrusa) come per altri framework, Aurelia cerca come al solito di rimanere il più possibile aderente alle specifiche HTML5 e mette a disposizione degli attributi custom con cui poter decorare gli elementi del DOM per ottenere comportamenti personalizzati. Non solo Aurelia è fornito di alcuni custom attribute out-of-the-box, ma ci mette a disposizione l'architettura per poterne definire a nostra volta con una semplicità disarmante. Per oggi però non affronteremo questo tema ma ci limiteremo ad usare due di questi attributi builtin. Il primo è l'if è serve appunto a discriminare la presenza o meno di un elemento nel DOM a seconda dell'espressione utilizzata nel bind. In breve <div if.bind="expression"> rende il div parte della pagina o meno a seconda del risultato di expression.

Risolto il problema della visibilità, passiamo all'esecuzione del metodo join. In questo caso di tratta di reagire ad un evento del DOM per invocare un metodo del ViewModel. Per questo scenario Aurelia mette a disposizione l'event binding.

L'event binding sfrutta la stessa sintassi dell'attribute binding per consentirci di intercettare gli eventi che vengono lanciati dagli elementi del DOM. Le uniche differenze sono nei comandi supportati (trigger e delegate per l'event binding) e nella proprietà speciale $event che può essere usata nell'espressione JavaScript per ottenere un riferimento al DOM event originale. Anche in questo caso rimandiamo l'approfondimento dei due comandi, utilizzando al momento il delegate in maniera del tutto fideistica. Ad esempio <button click.delegate="run()">Esegui</button> per eseguire il metodo run in risposta al click sul button Esegui. Nel nostro caso quindi:

L'ultima cosa che ci resta da fare per completare la "tile" della partita è colorare il panel di verde o di rosso. Si tratta in questo caso di aggiungere il class panel-success o quello panel-danger al div che definisce la "tile" (cioè quello con class panel). Per farlo useremo ancora una volta la string interpolation utilizzando in questo caso un'espressione:

Ora che il frammento di HTML che descrive la partita è completo, non ci resta che iterare tutti gli elementi che compongono il nostro array e clonare il frammento iniettandolo di volta in volta nel DOM. Come già detto nel caso della visibilità, HTML non fornisce ovviamente un supporto nativo per questo scenario; non esiste quindi un attributo o un tag specifico per gestire questa operazione. Ecco che arriva in nostro soccorso il secondo custom attribute cui facevo riferimento in precedenza. Si tratta del repeat.for, per mezzo del quale possiamo indicare al framework una collezione su cui vogliamo iterare usando (manco a dirlo) la sintassi standard di ES2015 per il comando for, ossia item of collection. Aurelia prende automaticamente l'elemento del DOM su cui abbiamo apposto l'attributo come template per i vari elementi della lista e si occupa di clonare e iniettare nel DOM l'HTML risultante. In definitiva:

Non ci resta che il button che determina la creazione di una nuova partita. Anche in questo caso possiamo usare l'event binding scrivendo:

Prima di poter eseguire il nostro codice c'è un'altra operazione che dobbiamo portare a termine. Nello screenshot del post precedente abbiamo visto che ho utilizzato Bootstrap per migliorare un po' (nei limiti imposti dalla mia incompetenza di grafico) il look dell'applicazione. In primis dobbiamo quindi importare il relativo package da JSPM seguendo la procedura già descritta per i package di Aurelia. Fatto ciò dobbiamo fare in modo che il css di Bootstrap sia caricato insieme alla nostra applicazione. Possiamo ottenere questo risultato in tre modi:

  1. Aggiungendo un tag link che punti al css nella pagina principale index.html in modo che sia caricato non appena atterriamo sull'entry point della nostra applicazione:
  2. Usando un tag require che punti al css all'interno della nostra View app.html in modo da comunicare ad Aurelia (e quindi a JSPM) che abbiamo bisogno di quel file e che deve essere dinamicamente caricato (se non è già stato fatto):
  3. Specificando il css come risorsa globale durante la configurazione di Aurelia:

Trattandosi di una risorsa non specifica della View, ma piuttosto necessaria a livello di intera applicazione, la soluzione ideale sarebbe la terza (in realtà ci sarebbe anche una quarta opzione ma non facciamo confusione al momento). Visto però che non abbiamo ancora approfondito la configurazione, andrò per ora con la seconda soluzione.

Ed ora, F5 e via!

Prima di concludere voglio spendere due parole sul frammento di HTML che abbiamo usato come template della partita. Abbiamo visto in un post precedente che ogni View in Aurelia è un template HTML5 introdotto dall'apposito tag template. Nulla ci vieta, e anzi in alcuni casi prossimamente useremo proprio questo approccio, di estrarre il div che descrive la partita in un file esterno, strutturato a sua volta come template HTML5 (cioè incorporato in un tag template). A questo punto possiamo istruire Aurelia ad usare quel template per popolare la parte di pagina relativa alla partita, potendo allo stesso tempo riusare lo stesso file in altre pagine in cui abbiamo bisogno della stessa UI (con un meccanismo in parte simile a quello delle partial view di MVC). Questo meccanismo è detto in Aurelia composition e, come vedremo in un prossimo post, consente di comporre la propria pagina con un alto grado di flessibilità.

Happy coding!

P.S. Il codice sorgente è disponibile su GitHub, basta scaricare la release con lo stesso nome del post (Binding di base in questo caso).

Aurelia e ASP.NET Core: fondamenti di binding

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Se fino ad oggi abbiamo navigato sotto costa, è arrivato il momento di rompere gli indugi e puntare la prua al largo. Nel proseguo della nostra esplorazione dell'universo Aurelia proverò a costruire un'applicazione concreta, per evitare di cadere nel solito tranello delle presentazioni apparentemente perfette che poi crollano al primo caso reale. Io realizzerò un'applicazione per supportare on-line un gioco di ruolo, voi potete ovviamente seguire la stessa strada o realizzare qualcosa di diverso.

I requisiti della nostra applicazione

Prima di buttarci nel codice, devo necessariamente spendere due parole per chiarire il dominio applicativo: per chi non lo sapesse un gioco di ruolo è quello in cui una serie di giocatori si calano nella parte di personaggi immaginari e vivono le avventure (generalmente in stile fantasy) narrate da un altro giocatore, detto master, che rappresenta il deus ex-machina della storia. Nel mio caso sto parlando del GdR "originale", in cui i giocatori si riuniscono attorno ad un tavolo e il master racconta la storia e dipana le vicende. Si dà però il caso che nel frattempo i giocatori hanno messo su famiglia e non possono più vedersi con la facilità di prima. Urge quindi trasformare le sessioni reali in sessioni virtuali (su Skype) e urge un supporto informatico per tenere traccia dell'evoluzione dei personaggi (l'equivalente digitale della scheda cartacea).

Ora che abbiamo chiaro il contesto, vediamo quali sono i primi requisiti dell'applicazione:

  • Come ospite, voglio diventare membro della community per poter giocare;
  • Come membro, voglio autenticarmi per poter accedere ai miei dati e alle mie partite;
  • Come utente, voglio vedere la lista delle partite cui sto partecipando;
  • Come utente, voglio iniziare una nuova partita come master;
  • Come utente, voglio unirmi ad una partita in corso come giocatore.

Cominciare gli sviluppi dai primi due requisiti, oltre che essere tremendamente noioso, ci obbligherebbe ad un nuovo percorso teorico di studio delle modalità di autenticazione/autorizzazione di una SPA, tenendo anche conto che non ho alcuna intenzione di gestire registrazioni, nomi utenti e password ma voglio invece sfruttare i servizi offerti dai principali provider di autenticazione (Facebook, Google, Microsoft, …). Dato che vogliamo fermamente mettere un primo abbozzo dell'applicazione sotto l'albero di Natale, daremo i primi due requisiti per acquisiti e cominceremo a sviluppare dal terzo.

Per iniziare assumiamo quindi di avere un utente registrato ed autenticato che nella sua home page voglia: a) vedere la lista delle partite cui è coinvolto; b) dare inizio ad una nuova partita e c) unirsi ad una partita in corso.

Per quanto detto fino adesso, dovrebbe essere ormai chiaro che un'applicazione Aurelia si compone di due componenti: la View, che contiene il template grafico da utilizzare, e il ViewModel, che contiene i dati con cui idratare tale template per comporre la pagina finale. Un'ipotetica pagina di visualizzazione di un utente, ad esempio, avrà un certo template grafico (sempre uguale) che sarà poi riempito di volta in volta con i dati specifici del particolare utente.

Nel nostro caso, come prima cosa abbiamo quindi bisogno di un file JavaScript che conterrà le informazioni sulle partite, e di un file HTML che avrà lo scheletro della UI da far vedere all'utente. Abbiamo visto che per convenzione Aurelia carica una pagina che corrisponde alla coppia app.js/app.html. Non ci vogliamo ancora addentrare nei gorghi della configurazione, quindi per ora ci limiteremo a riusare gli stessi due file dell'esempio precedente, semplicemente aggiornandone il contenuto.

Il primo ViewModel

Se vogliamo mostrare un elenco di partite, un buon punto di partenza è avere un array contenente i dati delle stesse. In futuro tali dati arriveranno da una chiamata ad un apposito servizio ASP.NET Core, per il momento è più che sufficiente dichiarare un array hard-coded nel costruttore della classe che rappresenta il nostro ViewModel usando la sintassi ES2015:

Cominciamo con il descrivere una partita con un modello molto semplice che arricchiremo nel corso del tempo: Un identificativo univoco della partita (id), il nome della stessa (title), il nome del giocatore che svolge le funzioni di master (master) e due flag che ci dicono se possiamo unirci (canJoin) o se possiamo partecipare alla sessione di gioco corrente (canPlay).

Come vedete non c'è alcun obbligo di chiamare la classe con lo stesso nome del file (anche se è buona norma per i ViewModel per non generare confusione). Dato che già so che "a regime" la nostra coppia si chiamerà games.js/games.html, ho chiamato la classe direttamente Games.

Oltre ai dati sulle partite esistenti, per soddisfare gli ultimi due requisiti abbiamo bisogno dei due metodi per creare una nuova partita e per unirsi ad una partita esistente. Anche in questo caso ci limitiamo momentaneamente a due metodi fake con il solo scopo di verificare il funzionamento della pagina:

La prima View

Per capire come definire il template diamo prima un'occhiata al risultato finale che vogliamo ottenere:

Vogliamo quindi un panel bootstrap per ogni partita, il cui header sarà uguale al nome della partita (title) e il cui body riporterà il nome del master (master). Un button sempre nel body ci consentirà di unirsi alla partita (canJoin = true) o di accedere alla stessa (canPlay = true), mentre un altro button fuori dall'elenco permetterà di creare una nuova partita. Le partite "giocabili" saranno evidenziate in verde, le altre in rosso. Il frammento di HTML necessario ad ottenere il risultato di cui sopra (al netto delle informazioni sulle singole partite) è questo:

In questa porzione di HTML dobbiamo inserire il title (1) e il master (2). Dobbiamo inoltre fare in modo che il button (3) sia visibile solo se canJoin = true e che il suo click esegua il metodo join del ViewModel e che il link (4) sia invece visibile se canPlay = true. A seconda del valore di canPlay dobbiamo quindi aggiungere il class panel-success o quello panel-danger al panel (5). Tutto ciò va ovviamente ripetuto per ogni partita del nostro elenco duplicando l'intero div (6). Infine dobbiamo chiamare il metodo create del ViewModel al click sul button (7).

Cos'è il binding?

Il problema diventa quindi quale meccanismo utilizzare per gestire questo travaso di dati dal ViewModel alla View.

L'approccio più semplice è ovviamente dato da un'interazione "forte" tra le due parti. Il ViewModel conosce perfettamente la struttura della View ed è in grado di individuare le parti che devono essere idratate e di iniettare nelle stesse i valori presenti al suo interno (ad esempio utilizzando jQuery con una sintassi tipo $('#title').html(this.title). Questa operazione può essere tipicamente portata a termine al momento del caricamento, fornendo all'utente una pagina già completa di tutte le informazioni.

Le cose però si complicano quando dobbiamo gestire la visibilità di alcuni elementi o quando dobbiamo gestire una lista con la conseguente necessità di clonare e iniettare nuove porzioni di HTML. Ancor peggio quando il passaggio delle informazioni deve essere bidirezionale, quando cioè le informazioni aggiornate dall'utente sulla View devono essere ritrasmesse al ViewModel, tipicamente per dare avvio ad un processo di business o ad una semplice operazione di salvataggio delle stesse. Nasce infatti l'esigenza di individuare il momento in cui riportare le informazioni sul ViewModel, intercettando una serie di eventi sulla pagina (come il click sul pulsante di submit o la digitazione in un campo di input), e in breve tempo la gestione di questi scambi di dati può diventare molto complessa e tremendamente fragile per via dello stretto collegamento tra le due parti che costringe a tenere allineato il ViewModel ad ogni cambiamento della View.

Per ovviare a questa serie di problematiche, i framework che si basano sul pattern MVVM (come Aurelia) utilizzano un meccanismo detto binding. In cosa consiste? In parole povere si tratta di una sintassi specifica che consente di specificare all'interno della View i punti in cui è necessaria l'interazione con un corrispettivo elemento del ViewModel, che sia il valore di una proprietà che deve essere riportato sulla UI, un metodo che deve essere invocato al click di un pulsante o ancora un flag che determina la visibilità o meno di un elemento grafico. La sintassi è ovviamente riconosciuta dal framework che è quindi in grado di processare la View, individuare i punti di interazione e idratare la UI con i valori provenienti dal ViewModel. In aggiunta il framework si prende anche carico della successiva gestione della vita della View, intercettando gli eventi scatenati dall'interazione dell'utente, modificando il ViewModel e di riflesso aggiornando ulteriormente la View stessa. Tutto ciò potendo mantenere il ViewModel del tutto agnostico rispetto alla View.

Nel prossimo post vedremo quali strumenti Aurelia ci mette a disposizione per gestire il binding.

Merry coding and happy new year!

Aurelia e ASP.NET Core: convention-over-configuration

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Al termine di un precedente post ci eravamo lasciati con tre domande cui stiamo rispondendo prima di procedere.

Come ha fatto Aurelia a capire che volevamo mostrare il contenuto della pagina app?

Nel primo post di questa serie avevamo accennato al fatto che Aurelia è fortemente basato sul concetto di convention-over-configuration. Di che si tratta? In termini estremamente semplici l'idea si può così riassumere: in una qualsiasi applicazione (sia essa basata su SPA come in Aurelia o su un altro approccio, tipo MVC) ci troviamo costantemente ad affrontare problemi architetturali generici e comuni, come ad esempio banalmente, in che modo registriamo sul framework i componenti che vogliamo includere? E in che modo gli comunichiamo l'associazione tra una View e il suo ViewModel? Alcuni framework sono molto cerimoniosi da questo punto di vista e richiedono lunghe configurazioni che a volte trascendono nella puntualizzazione dell'ovvio. Aurelia, come altri framework basati sullo stesso concetto, cerca invece di "supporre" in maniera intelligente quello che potremmo aver necessità di fare applicando in autonomia una serie di convenzioni tratte dall'esperienza. Che succede se abbiamo esigenze specifiche che mal si conciliano con le convenzioni definite da Aurelia? Nessun problema, possiamo facilmente esplicitare una diversa configurazione e Aurelia ne terrà conto (ma vi assicuro che le convenzioni di Aurelia sono assolutamente sensate e logiche).

Già nel nostro primo semplicissimo esempio ci siamo imbattuti in questo approccio, e ancora avremo modo di farlo proseguendo nel nostro viaggio. Cosa è successo dietro le quinte che ha consentito alla pagina app di essere mostrata senza una nostra specifica indicazione? Cogliamo l'occasione per familiarizzare con il codice di Aurelia e addentriamoci nel framework per risolvere il "mistero".

Il codice contenuto nel file index.html è ridotto veramente al minimo. È quindi chiaro che se c'è della magia, questa è contenuta nel bootstrapper che viene caricato con la chiamata System.import('aurelia-bootstrapper').

Potremmo quindi aprire il file aurelia-bootstrapper.js che si trova in wwwroot/_libs/npm/aurelia-bootstrapper@1.0.1 ma ci troveremmo davanti a codice ES5. Il pacchetto presente su NPM e scaricato da JSPM è infatti già convertito in ES5 per garantire la massima compatibilità con i browser attualmente sul mercato. Nulla ci vieta ovviamente di cercare la soluzione qui, ma io andrò invece alla fonte, direttamente su GitHub, dove si trova il file originale in ES2015.

Il file si compone di una serie di variabili statiche e funzioni definite secondo la sintassi ES2015. Nulla di tutto ciò comporta esecuzione di codice al momento del caricamento. Ma se andiamo proprio in fondo al file, troviamo la dichiarazione di una constante starting il cui valore è pari al risultato della chiamata alla funzione run (manco a dirlo un oggetto che rappresenta una promise):

È questa quindi la funzione che viene eseguita immediatamente al caricamento del modulo e che determina l'avvio di Aurelia. Dopo aver eseguito un po' di codice di inizializzazione, la funzione run cerca nel documento i tag (nel nostro caso il body) che abbiamo marcato con l'attributo aurelia-app (ricorderete che nel precedente post avevamo detto che questo attributo serve ad indicare ad Aurelia in quale parte della pagina vogliamo caricare l'applicazione).

A giudicare dal codice sembrerebbe che Aurelia sia in grado di gestire contemporaneamente diverse applicazioni completamenti distinte e indipendenti. Così su due piedi non mi vengono in mente scenari in cui ciò potrebbe essere necessario, in ogni caso è un ulteriore attestato di quella flessibilità del framework che avremo modo di apprezzare più in concreto in altre situazioni. Per ogni attributo trovato viene chiamato il metodo handleApp per gestire l'inizializzazione dell'applicazione:

La funziona handleApp si limita a recuperare il valore dell'attributo aurelia-app e a passarlo alla funzione config che si occupa della configurazione:

Il parametro viene ricevuto dalla funzione config con il nome di configModuleId e memorizzato nella proprietà omonima dell'oggetto Aurelia appena creato. Questo ci fa capire cosa si aspetta Aurelia da quel modulo (nel caso in cui, come avverrà in futuro, decidessimo di specificarne il nome nell'attributo aurelia-app): una classe tramite la quale configurare il framework a nostro piacimento.

In ogni caso, visto che non abbiamo specificato un valore per l'attributo, aurelia.configModuleId sarà null, la porzione di codice subito sottostante sarà ignorata e, al termine della procedura di avvio (cioè nel ramo then della promise ritornata da start), il controllo passerà al metodo setRoot:

Il metodo setRoot è definito all'interno della classe Aurelia che fa parte del package aurelia-framework e si occupa di caricare la prima pagina dell'applicazione. Il metodo si aspetta un parametro opzionale root che di default è null (convention) il che ci fa capire che in altri scenari potremo passare espressamente la pagina che vogliamo sia l'entry point della navigazione (configuration). Visto che nel nostro caso root è null e che this.configModuleId è parimenti null (per quanto visto sopra), il metodo imposta la root ad "app" (convention) e poi la utilizza come primo ViewModel da caricare:

L'arcano è dunque risolto, abbiamo preso confidenza con il codice interno di Aurelia, e abbiamo anche avuto modo di vedere che il modulo che definisce la classe base del framework è scritto in TypeScript (come avrete notato dalla tipizzazione dei parametri e dal generic applicato alla Promise di ritorno).

Le prove tecniche di navigazione sono terminate, dal prossimo post prenderemo il largo sul serio.

Happy coding!

Aurelia e ASP.NET Core: i template di HTML5

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Al termine di un precedente post ci eravamo lasciati con tre domande cui stiamo rispondendo prima di procedere.

Che fine ha fatto il tag template che era nel file app.html?

Nell'esempio del post avevamo creato un semplicissimo file che conteneva un frammento di HTML con un tag template. Nella pagina inviata al browser tale tag era però sparito. Come mai?

Nel primo post di questa serie avevamo detto che Aurelia è stato progetto cercando il massimo rispetto degli standard HTML e ECMAScript esistenti. Quando per il funzionamento del framework c'è bisogno di un costrutto o di una sintassi dedicata, la scelta del Team è stata quella di inventare il meno possibile, riutilizzando se possibile quanto già standardizzato dalle ultime specifiche.

Nel caso delle View (come la pagina app.html del nostro esempio) la scelta è stata quindi l'utilizzo della sintassi HTML5 per i Template. Di cosa si tratta?

Nelle specifiche HTML precedenti alla versione 5 ha sempre spiccato la mancanza di un meccanismo di templating nativo. Stiamo parlando in sostanza dell'equivalente di ciò che è Razor per il mondo ASP.NET o Django per quello Python. L'esigenza è in definitiva quella di inviare al browser un frammento di HTML da non renderizzare direttamente ma da usare come stampo per creare porzioni di pagina più grandi e tipicamente uguali tra loro (a meno ovviamente dei dati specifici di ogni singola componente). Per capire meglio di cosa stiamo parlando, potremmo pensare ad una tabella composta da tante righe, tutte ovviamente uguali per quando attiene a struttura, layout e stili, con la sola differenza delle informazioni riportate in ognuna di esse.

Come detto fino ad oggi le specifiche non prevedevano una soluzione standard per tale necessità, e gli espedienti comunemente più usati (sia dai singoli programmatori che dalle librerie di componenti più diffuse) consistevano nell'incapsulare tali frammenti in tag script marcati con un attributo type personalizzato (come ad esempio <script type="text/x-my-templates">…</script>) o in elementi del DOM nascosti (<div style="display: none">…</div>). In tale modo era possibile poi individuarli, manipolarli e aggiunti alla pagina dinamicamente tramite JavaScript.

Entrambe queste soluzioni sono caratterizzate da limiti e difetti che sono stati fortunatamente superati in HTML5, grazie alla definizione di un nuovo tag dedicato, il template appunto.

Il vantaggio principale del tag template è dato dal fatto che, pur essendo presente sulla pagina, è del tutto inerte fino a quando non viene clonato e iniettato nuovamente nella pagina stessa sotto forma di frammento di HTML. Inerte vuol dire che il contenuto del template non viene processato al momento del caricamento, ma solo al momento della clonazione. Se ad esempio nel contenuto è presente un tag script, il codice JavaScript all'interno dello stesso non viene eseguito se non in fase di clonazione. In quello stesso momento sarà caricata ad esempio l'immagine cui fa riferimento l'attributo src di un eventuale tag img.

Se modificassimo il file app.html del post precedente in questo modo:

e provassimo poi a caricare il file direttamente in un browser pienamente compatibile con le specifiche HTML5 relative ai template (Chrome nel mio caso), potremmo verificare che né il contenuto HTML né il codice JavaScript della pagina vengono eseguiti al momento del caricamento:

Per rinforzare questa separazione tra la pagina e il template, anche i normali meccanismi di ricerca degli elementi del DOM, come l'arcinoto document.getElementById, non funzionano sugli elementi che fanno parte del contenuto del template, in modo da evitare che si possa, anche involontariamente, interagire con esso.

I Template fanno parte di un gruppo di quattro nuove tecnologie definite nelle specifiche di HTML5 (insieme a Custom Element, Shadow DOM e Import) che sono alla base del concetto di Web Component che avremo modo di approfondire più avanti quando parleremo di tecniche avanzate di composizione delle pagine in Aurelia.

Per ora ci basta sapere che tutte le nostre View saranno frammenti di HTML inclusi in un tag template da cui erediteranno proprietà e comportamenti. Al momento opportuno Aurelia si occuperà di recuperare il template, idratarlo con i dati provenienti dal ViewModel e iniettarlo nella posizione corretta del DOM.

Happy coding!

Aurelia e ASP.NET Core: moduli, classi e promise in ES2015

Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.

Al termine dell'ultimo post ci siamo lasciati con tre domande cui cercheremo di rispondere in tre post ravvicinati nei prossimi giorni.

Cos'è la strana sintassi che abbiamo usato nel file app.js?

Nel primo post di questa serie avevamo accennato al fatto che Aurelia è scritto interamente in ES2016, l'ultimissima versione delle specifiche JavaScript che va ulteriormente ad estendere il già innovativo ES2015 (per lungo tempo altrimenti noto come ES6).

Descrivere, anche in modo sommario, tutte le nuove caratteristiche di ES2015/6 ci farebbe deviare troppo dalla nostra rotta. Userò quindi il "solito" approccio di limitarmi a descrivere quello che ci è realmente utile al momento, lasciando eventuali ulteriori approfondimenti ad un prossimo post.

Classi

ES2015 introduce in JavaScript il concetto di classe. Proprio come in C# o in altri linguaggi ad oggetti, una classe è una matrice per creare oggetti: ogni oggetto al momento della creazione sarà fornito degli attributi (proprietà) e dei comportamenti (metodi) definiti nella classe.

Il concetto di oggetto non è ovviamente nuovo in JavaScript ma per via della complessità nella sua gestione a 360 gradi e dell'approccio radicalmente diverso dagli altri linguaggi ad oggetti, le nuove specifiche hanno aggiunto un po' di "syntactic sugar" per mezzo della nuova keyword class. Creare un oggetto in JavaScript è quindi ora molto simile alla sua controparte C#: si definisce una classe e si istanzia l'oggetto sempre usando new. La definizione delle proprietà della classe avviene direttamente all'interno del costruttore mentre i metodi possono essere aggiunti al corpo (FYI ES2016 ha ulteriormente esteso questo nuovo costrutto dando la possibilità di definire anche le proprietà nel corpo della classe, in maniera più simile a quanto facciamo con C#):

Con il nuovo costrutto, l'ereditarietà viene gestita per mezzo della keyword specifica extends:

Ho già avuto modo di dire che si tratta solo di "syntactic sugar". Dietro le quindi il funzionamento è ancora quello di ES5, basato su funzioni e prototype. Ma di questo parleremo magari meglio in un post dedicato, fuori da questa serie su Aurelia. In questo contesto ci basti sapere che questa sarà la sintassi che useremo ogni volta che dovremo definire un ViewModel da associare ad una View. Ma il pur banale ViewModel che abbiamo costruito presentava un'altra keyword "strana": export. Di che si tratta?

Moduli

Da sempre JavaScript soffre di problemi di scarsa organizzazione del codice e mancanza di controllo sulla visibilità dello stesso. Per farla breve:

  • Tutto ciò che viene definito (funzioni, variabili, ecc.) diventa un attributo dell'oggetto globale window il che, oltre a generare una certa confusione, espone le applicazioni a conflitti sui nomi quando si usano librerie provenienti da fonti diverse (che come sappiamo in JavaScript è la norma);
  • Come se non bastasse, tutto il codice scritto è esposto pubblicamente, non c'è modo di definire logiche interne ad un oggetto come facciamo ad esempio con i metodi privati in C#, e la dinamicità del linguaggio (inclusa la possibilità di sostituire il corpo di una funzione con codice completamente diverso a runtime con una semplice assegnazione) non fa che peggiorare le cose;
  • Spesso parti di codice definite in file o librerie diverse dipendono l'una dall'altra e non è sempre immediato garantire che il file che contiene una certa funzione venga caricato prima di quello che la utilizza, causando in qualche occasione eccezioni non previste.

Ovviamente la comunità di sviluppatori JavaScript non è stata semplicemente ferma a subire queste lacune del linguaggio ma ha piuttosto cercato forme alternative per ottenere il risultato desiderato (e come ormai dovrebbe essere chiaro in un linguaggio dinamico come JavaScript c'è quasi sempre un modo, magari non proprio ortodosso, per raggiungere il proprio scopo).

Dopo un primo tentativo di mitigazione tramite le cosidette Immediately-Invoked Function Expression (IIFE), una seconda risposta è arrivata dalle specifiche CommonJS e da quelle AMD (Asyncronous Module Definition) su cui si basa RequireJS. Alla base di questo nuovo approccio ci sono i concetti di modulo come unità indipendente e quelli di esportazione e importazione del contenuto dello stesso per definire i livelli di visibilità e le dipendenze tra i moduli.

ES2015 contribuisce a standardizzare quelle specifiche attraverso l'introduzione di due nuove keyword: export e import appunto. Ogni file JavaScript costruito secondo le specifiche costituisce un modulo a sé stante i cui elementi interni (variabili, funzioni, classi) vivono in isolamento rispetto agli altri moduli e al contesto globale. Gli elementi del modulo (uno o più) che si vogliono rendere pubblici devono essere esplicitamente marcati con la keyword export, come abbiamo fatto nel post precedente nel file app.js, e importati nel modulo che ne vuole usufruire (come si vede dalle "ondine" rosse, Visual Studio 2015 ad oggi non digerisce molto bene la sintassi ES2015 nel file js):

Aurelia si occupa di caricare i moduli che compongono i ViewModel automaticamente quando sia necessario il render della corrispettiva View, quindi tipicamente non avremo bisogno di importare i ViewModel nella nostra applicazione. Avremo invece bisogno di importare i moduli JavaScript che useremo ad esempio per incapsulare il codice di interfacciamento con i nostri servizi di back-end.

Promise

C'è un ultimo aspetto di ES2015 di cui dobbiamo parlare anche se non l'abbiamo incontrato nel primo semplicissimo ViewModel. Aurelia è infatti fortemente basato sulla programmazione asincrona. La quasi totalità delle funzioni del framework è progettata per ritornare immediatamente il controllo al chiamante mentre prosegue l'esecuzione delle operazioni, salvo poi notificare il completamento delle stesse a tempo debito.

Il concetto di programmazione asincrona è ben noto agli sviluppatori web per via della latenza intrinseca nell'architettura client/server basata su HTTP. Ogni volta che comunichiamo con il server per ottenere delle informazioni, non possiamo fare assunzioni sui tempi di risposta e quindi, se non vogliamo bloccare completamente l'esecuzione dell'applicazione, è buona norma tenere conto di queste limitazioni e progettarla di conseguenza.

Il modo "tradizionale" di gestire la programmazione asincrona in JavaScript (e non solo) era basato sul meccanismo delle callback. Una funzione che doveva eseguire operazioni potenzialmente molto lunghe prevedeva tra i propri parametri una funzione da chiamare al termine delle operazioni. In questo modo era possibile ritornare immediatamente il controllo al chiamante ed eseguire poi il codice in risposta al completamento nella funzione di callback.

Non mi dilungherò sui tanti problemi che questo comporta. Trovate tonnellate di documentazione in rete (tra cui ad esempio le slide della mia sessione a .NET Campus). In ogni caso per superare tali limitazioni già da diversi anni si è diffuso l'utilizzo delle promise come alternativa alle callback.

Anche in questo caso infatti, come per le classi e i moduli, ES2015 arriva a standardizzare un approccio già in voga tra gli sviluppatori, con diverse librerie che fornivano un'implementazione delle specifiche Promises/A+ come ad esempio Q.

Una promise è in definitiva (e con un'innocente semplificazione) un oggetto che rappresenta un valore che al momento non è noto ma che lo sarà prima o poi nel futuro. Come interagiamo con tale oggetto? Fondamentalmente per mezzo del suo metodo then – che da specifiche ogni promise deve obbligatoriamente implementare– che accetta come parametri le due funzioni che saranno invocate rispettivamente quando la promise viene risolta positivamente o quando viene viceversa rigettata (ad esempio per un timeout).

Molto spesso del corso dei prossimi post ci imbatteremo quindi in codice come il seguente:

Due cose interessanti vanno aggiunte sul funzionamento di questo meccanismo: intanto il metodo then ritorna a sua volta una promise usando il valore di ritorno della nostra funzione per risolvere la stessa o intercettando un'eventuale eccezione lanciata dal nostro codice per rigettarla. Questo vuol dire che possiamo concatenare chiamate al metodo then con una sintassi molto più chiara e interpretabile della "pyramid of doom" caratteristica delle callback. In secondo luogo, essendo oggetti, le promise possono essere sia memorizzate in una variabile che passate da una funzione all'altra, aprendo la porta a scenari molto interessanti che avremo modo di scoprire nei prossimi post:

E TypeScript in tutto ciò?

Aurelia supporta non solo ES5 e ES2015/6 ma anche TypeScript. Ciò vuol dire che possiamo scrivere la nostra applicazione nel modo che preferiamo, anche se il consiglio è ovviamente di evitare ES5 e orientarsi per una delle due opzioni più "moderne".

Nel corso di questa serie userò principalmente ES2015, ma non escludo di fare qualche esperimento anche con TypeScript più avanti. La differenza in realtà non è più così rilevante come era tra ES5 e TypeScript.

Le aggiunte più interessanti di quest'ultimo rispetto a ES2015 sono a mio parere la type safety (cioè la possibilità di definire quale sia il tipo di variabili, parametri e valori di ritorno), i generic (praticamente uguali anche nella sintassi a quelli di C# e molto utili in diverse situazioni) e la possibilità di definire interfacce. Tutte cose utili ma non indispensabili per il proseguo del nostro progetto.

Happy coding!

«novembre»
domlunmarmergiovensab
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567