14 maggio 2020 22:50
Geo-Italy: lista comuni e territori italiani con Blazor e Blazorise
In questo post descrivevo e raccontavo di un progetto reale (dati meteo della mia provincia) portato avanti per motivi didattici personali, ovvero imparare una nuova tecnologia chiamata Blazor.
Visto che ho trovato l’approccio divertente e utile ho pensato di replicare quando è stato il momento di testare una libreria di componenti per questo framework chiamata Blazorise.
Tutto è partito da “vediamo come si fanno 2 o più dropdown a cascata dove la successiva si costruisce con il filtro della precedente” e l’esempio che mi è venuto in mente è la classica gerarchia nazione>regione>provincia>comune. La cosa poi mi ha un po’ preso la mano e dalla lista regioni e province e comuni ho ampliato lo spettro con le frazioni (cosa di cui il nostro paese abbonda), e qualche paginetta di “bonus” sempre per provare e sperimentare nuove cose.
Ne è nata quindi questa Single Page Application che ho chiamato Geo-Italy.
Fonte Dati
La prima cosa da procurarsi era ovviamente la lista dei Comuni Italiani, cosa che già sapevo l’ISTAT mette a disposizione in formato excel. Il pacchetto è composto da vari file ma il principale (Elenco-codici-statistici-e-denominazioni-al-01_01_2020) è una tabella flat denormalizzata dove si trovano la maggior parte delle informazioni che servono.
Figura 1 - File Excel ISTAT
Importare questa tabella dividendola in una struttura relazionale (almeno a tre livelli) e soprattutto manutenerla ogni volta che ci fossero gli aggiornamenti diventava però un po’ noioso ed ho quindi cercato una soluzione che mi fornisse sia gli update quadrimestrali che già una struttura a database.
Cercando in internet ho trovato ed acquistato il servizio offerto da DatabaseComuni.it che, per una modica cifra, offre una serie di tabelle (arricchite di molte informazioni come cap, indirizzi email pec e generici, coordinate geografiche, 60.000 frazioni, prefissi, abitanti, ecc.) riguardati i quasi 8.000 comuni italiani. Costantemente aggiornato, viene fornito in tre comodi formati (MySql, Microsoft Excel e Json) e risulta molto utile a chi sviluppa gestionali, siti web, app, anagrafiche clienti e web marketing.
La struttura che viene fuori lanciando gli script forniti e aggiustati per farli girare in Sql Server è questa
Figura 2 - Schema del DB dopo l'importazione dello script per MySql
Primo hint “tennnico”; non sapevo che Ms-Sql avesse un limite al numero di record inseribili con una insert da T-SQL e quindi fare
INSERT INTO table VALUES (‘Comune1’, …), (‘Comune2’,…) ecc per 7904 record NON funziona, il massimo è 1000 !
Pensare di spezzare tutti gli script originali era follia, per fortuna ho trovato questo “trucchetto” per cui facendo
INSERT INTO table
SELECT * FROM (
VALUES (‘Comune1’, …), (‘Comune2’,…) ) --ecc.
il limite non sussiste (swoosh… altrimenti sarei dovuto passare dai file json che vengono forniti insieme a quelli CSV nel bundle del pacchetto).
Ovviamente per mia deformazione professionale non potevo iniziare un progetto con una struttura con i nomi in italiano, ho quindi rimodellato lo schema pensando già ovviamente a come avrei voluto il dominio delle classi delle mie entità ed è venuto fuori questo.
Figura 3 - La mia struttura dopo la ri-modellazione
Se qualche attento lettore, si chiedesse perché non c’è la relazione tra le ProvinceId dei Comuni Rimossi è perché dentro ci sono i valori di Pola, Fiume e Zara che per ovvi motivi non esistono nella tabella delle 107 province attuali. E se qualcun altro si chiedesse perché storicizzare il count(*) dei record delle tabelle parent è perché… c’erano già in origine e per 4 mesi non cambiano di sicuro (!).
Costruzione delle Entità: gli EF Core Power Tools di ErikeJ
Anche se va molto di moda (e ha i suoi innegabili vantaggi) l’approccio Code-First, qui ovviamente avendo già i dati e la struttura ho modellato le mie classi tramite lo scaffolding (il reverse engineering del db e le generazioni di classi POCO) di EF Core 3.1.
Da un tweet del bravissimo dev @Corcav (e ancor più bravo rider scalatore mtb!) ho scoperto dell’esistenza di questa estensione per Visual Studio che ha molteplici vantaggi rispetto all’approccio con EF Core direttamente:
- Permette la selezione delle tabelle da “scaffoldare” tramite una comoda GUI al posto di elencarle una per una nel command da dare in pasto al Package Manager Console
- Non ha bisogno di referenziare l’assembly EntityFrameworkCore.Tools
- Ha una gestione molto migliore della Pluralizzazione (o nel mio caso singolarizzazione) tra tabelle e entità; io infatti amo chiamare le tabelle (e quindi i DbSet) al plurale, ma il nome classe al singolare (Cities la tabella ma City la classe). Basta un click invece che complicate implementazioni di interfacce
- Può omettere di generare la classe che deriva da DbContext se non dovesse servire.
- Gestisce namespaces diversi tra classi e dbcontext e con nomi diversi dal folder di destinazione
Figura 4 - EfCore PowerTools di ErikeJ
Lo strato dei Services
Anche questa volta ho scelto la soluzione Blazor Server Side, quindi teoricamente potrei fare le chiamate al Db direttamente dalla UI (orrore!!). Chiaramente li ho messi in un progetto a parte ma ho comunque preferito per brevità non fare uno strato web-api e quindi usare EF Core per ritornare List<>.
Ho approfittato però per migliorare le mie (scarse) conoscenze del mondo asincrono e mi sono sforzato di usare solo metodi async che ritornino Task<>
Piccola nota sull’uso del FindAsync() che in presenza di .Include (quando vogliamo fare una join con related entities) non può essere usato e che deve essere sostituito dalla FirstOrDefaultAsync().
Per le liste invece il problema non sussiste (ToListAsync() funziona anche con Include<>)
Figura 5 Le Interfacce di uno dei servizi
Template UI
Ancora una volta grossi dubbi e fatica per trovare un template gradevole Bootstrap 4.x (che non sia quello visto e stra-visto di Microsoft) che si potesse adattare facilmente agli stili di Blazorise.
Quello che ho scelto purtroppo è un po’ vecchiotto e utilizza un css bootstrap non originale (anche se dichiara la stessa versione) e quindi è sorta una “incompatibilità” con quello linkato da Blazorise, che mi ha fatto sudare qualche lacrima (idem per il font-awesome).
Come discusso anche in vari threads su Twitter, mi meraviglio ancora molto che qualcuno non abbia subodorato le possibilità di business facendo un template (per siti o per dashboard admin come le migliaia che esistono su Envato o WrapBootstrap) completamente JS-Free e con le varie features (apertura/chiusura sidebar, menu di collapse, ecc). gestite in C# con Blazor.
L'esperienza mi è comunque servita a comprendere meglio tutto il discorso breakpoint di Bootstrap (qui una pagina ghost usata per capire meglio il grid system) e alcune cose sui CSS. Resta il fatto che perdo infinitamente più tempo a centrare una scritta in un DIV che non a scrivere tutto lo strato dei servizi :-(
Per il problema sidebar collassabile, alla fine, dal MainLayout è sufficiente istanziare il <NavMenu> con un @ref e richiamare un metodo che setta una sua proprietà boolean (ricordatevi che bisogna farlo sempre con un metodo e mai direttamente settando la property, come mi ha spiegato in un suo tweet -orgoglio avere un suo reply - Steve Sanderson, il creatore di Blazor!). Nel <NavMenu> ovviamente basta gestire il nome della classe css in base al boolean del parametro.
Figura 6 A sinistra il MainLayout che chiama il NavMenu (a destra)
Btw: il codice sopra dentro il blocco @code è solo per finalità del blog. In tutto il progetto ho usato (con molta più pulizia) il “codebehind” con la classe che deriva da ComponentBase separata in un altro file.
Blazorise
Ma veniamo al main topic da cui è partito tutto. Questa libreria di componenti open-source e free creata da un validissimo dev croato (Mladen Macanovic) è un ottimo toolset di elementi che wrappano non solo i vari componenti di bootstrap ma anche molti di quelli html, arrivando al paradosso che si può scrivere un componente Razor (e quindi una “pagina”) usando solo ed esclusivamente tag Blazorise.
Qui sotto vedete ad esempio un classico esempio di pagina con intestazione, testo, due colonne con una card a sinistra e una tabella a destra. Cose che all’incirca in HTML facciamo dai tempi di asp classic da 20 anni e nell’immagine di sinistra come potrebbe essere scritta in Blazorise.
Sicuramente molto più elegante, compatta e di facile lettura
Figura 7 - HTML vs Components
La domanda è se ne vale veramente la pena e che vantaggio ci sia a usare un <Heading Size="HeadingSize.Is1"> al posto di un più veloce (da scrivere) <h1> ed infatti ogni tanto qualche tag html mi è scappato. Inoltre ho dovuto abbandonare l’uso del componente <Column> che genera un tag tipo <div class="col col-6"> mentre per le incompatibilità col template descritte sopra (che probabilmente non utilizza la modalità flex di Bootstrap) a me serviva essere più preciso e indicare in modo preciso i vari breakpoint tipo <div class="col-sm-6">.
Oltre ai vari tag per gestire una form e ai componenti di layout (che possono essere usati anche con altri CSS Providers come Material, Bulma e AntDesign e anche con Theming custom) molto utili sono venuti extensions più complesse come la DataGrid (usata quasi ovunque) e l’Autocomplete (usata nella pagina delle Frazioni).
Della DataGrid mi è piaciuto molto il fatto che possa essere usata “as-is” per fonti dati di pochi items che possono essere passate tutte in una volta, oppure nel caso di large datasets come era la lista comuni (8000 items) o delle frazioni (60000!) caricate e passate pagina-per-pagina con il classico pattern Take().Skip() di LINQ.
Insomma siamo alla versione 0.9, qualche cosina da sistemare c’è ancora, qualche ottimizzazione anche (es. molti componenti renderizzano un inutile style=”” anche se non ho passato nessuno stile inline) e di codice Js sottostante immagino ve ne sia ancora molto, ma finché è nascosto e incapsulato, non è un grosso problema (e comunque nella long-term roadmap viene indicato come obiettivo quello di rimuoverlo completamente).
Complimenti ancora all’autore per l’enorme lavoro e speriamo che con il contributo della community il progetto venga portato avanti (a mio avviso non ha molto da invidiare alle varie suite di componenti commerciali come DevExpress, Telerik e Syncfusion).
Bonus Tracks
Distanza tra due punti geografici
Avendo a disposizione per ogni comune le coordinate Latitudine e Longitudine, mi è venuto in mente l’idea di calcolare la distanza in linea d’aria utilizzando i dati spaziali di Sql Server, tematica che non avevo mai studiato.
Non è stato neppure necessario creare una colonna di tipo geography (anche se gli EF Core Power Tools avrebbero potuto addirittura mapparli in una entity semplicemente aggiungendo al file efpt.config.json "UseSpatial": true,) ma semplicemente creare una banale stored procedure. E così ho anche ripassato come EF core chiama le sp con parametro di output
ALTER PROCEDURE [dbo].[GetDistanceKm]
@MunicipalityIdFrom INT,
@MunicipalityIdTo INT,
@Distance DECIMAL(9,3) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Geo1 GEOGRAPHY
DECLARE @Geo2 GEOGRAPHY
SELECT @Geo1 =geography::Point(Latitude, Longitude, 4326) FROM dbo.Municipalities WHERE MunicipalityId = @MunicipalityIdFrom
SELECT @Geo2 =geography::Point(Latitude, Longitude, 4326) FROM dbo.Municipalities WHERE MunicipalityId = @MunicipalityIdTo
SET @Distance = (@Geo1.STDistance(@Geo2))/1000
RETURN
END
Figura 8 - Stored Procedure che calcola la distanza tra due punti
Figura 9 - Chiamata EF Core di una SP con parametro di output
Quiz e Contact
Per quanto riguarda i Quiz, visto che avevo perso tempo a creare i 20 png delle regioni italiane, mi è venuto in mente di proporre un quiz adatto a bambini e ragazzi alle prese con lo studio della nostra geografia. Magari in futuro implementerò uno Score salvabile nel localstorage del browser.
Con il solito modulo di Contact invece ho testato i Validators di Blazorise che si comportano in maniera simile a quelli standard di Blazor.
Conclusioni
Altro giro, altro progetto, altro divertimento in un paio di week-end e qualche sera. Sono sempre più convinto che Blazor sia una tremenda figata e visto che manca pochissimo alla RTM della versione WebAssembly (al momento dell’uscita di questo post siamo alla RC) ci sarà sicuramente da divertirsi.