Probabilmente il concetto più importante dei moderni framework di front-end è quello di componente, che nonostante la sua semplicità è spesso causa di fraintendimenti sugli scopi e gli utilizzi. Cerchiamo di capire in questo articolo di cosa si tratta e come definire componenti in Blazor.

Che cos’è un componente

Immaginiamo come sarà fatta l’interfaccia della nostra applicazione, analizziamola e proviamo a vederla invece che come un blocco unico, come un insieme di tanti pezzi, come in un puzzle. Ogni elemento di questo puzzle è un componente, quindi, molto semplicemente, un componente è un pezzo della nostra interfaccia. Probabilmente ci sarà un’area con il menu, un footer, un header, un’area in cui visualizziamo i nostri dati: tutte queste aree sono potenziali componenti.

Suddivisione in componenti dell'interfaccia utente

Detta così sembra semplicissimo, e lo è, ma un componente deve anche avere alcune fondamentali caratteristiche:

  • avere l’unica responsabilità di gestire la sua parte di interazione con l’utente e reagire ad essa
  • poter contenere altri componenti, al fine di formare un albero di componenti
  • essere correttamente dimensionato
  • essere potenzialmente riutilizzabile e non accoppiato con altri componenti

Una questione di responsabilità

Un componente conterrà la definizione del pezzo di interfaccia che lo interessa, l’HTML nel caso di applicazioni Web, e definirà il codice per gestire questo pezzo di interfaccia, limitandosi a visualizzare i dati richiesti e catturare l’input dell’utente. Questo significa che in un componente non ci dovrebbe mai essere logica applicativa, ma solo logica di interfaccia, demandando tutti il resto ad altri elementi dell’applicazione. Questo punto è importante, perchè ci permette di rispondere a una domanda che tutti noi ci facciamo quando scegliamo un framework di front-end:

Chi mi garantisce che tra 6 mesi questo framework sia ancora la scelta giusta per me?

La risposta è nessuno! Ma se ho messo il grosso della logica della mia applicazione in un layer separato, sostituire la tecnologie di front-end smette di diventare un bagno di sangue. Quante volte in Windows Form abbiamo messo i nostri algoritmi e le nostre query direttamente nel code-behind, spiattellandoli in un event-handler? Quante volte nel passaggio a Web Forms abbiamo fatto la stessa cosa? E’ vero che questi framework portavano in maniera naturale a scrivere il codice in questo modo, ma nessuno ci ha mai obbligati a farlo ed è soprattutto questa la ragione per la quale siamo rimasti su quella tecnologia per 10 anni, lamentandoci di non riuscire a stare dietro alle continue evoluzioni.

Nel mondo front-end questa cosa è particolarmente vera, ma rassegniamoci al fatto di avere continuamente framework nuovi, scriviamo il nostro codice perchè sia facile migrarlo, e decidiamo se per i nostri requisiti, funzionali e non, ha ancora senso restare su quella tecnologia.

In .NET Core è particolarmente semplice creare quello che in Ingegneria del Software viene detto basso accoppiamento, perchè abbiamo un motore di Dependency Injection già integrato ed estendibile: applichiamo il principio di separazione delle responsabiità, creiamo il giusto numero di classi e layer per gestire la complessità della nostra applicazione e iniettiamo le dipendenze senza creare accoppiamento. Se non avete mai sentito parlare di Dependency Injection, Inversion of Controls e gestione delle dipendenze, date un occhio qui. In ogni caso vedremo degli esempi pratici costruendo la nostra applicazione.

Il nostro albero di componenti

Come abbiamo visto negli articoli precedenti, sia in Blazor Server che Blazor WebAssembly, il rendering della UI parte da una singola pagina HTML in cui c’è un primo componente. Quel componente è detto solitamente componente radice, perchè è la radice dell’albero di componenti che costituirà la nostra interfaccia.

Albero di elementi HTML

Di solito si parla di legami di parentela tra i componenti: il componente contenitore è il padre, i componenti contenuti i figli. Questo significa che un componente può contenere altri componenti, ma non ci possono essere cicli: non posso cioè contenere un componente che è mio parente nella ramificazione.

Se già lavorate nel mondo WEB è lo stesso concetto su cui è basato HTML, se avete lavorato con WPF o similari, è lo stesso concetto su cui è basato XAML. In realtà è lo stesso concetto che lega tutti i linguaggi di markup, che derivando dal metalinguaggio XML, ne ereditano le regole.

Trovare la giusta dimensione

Probabilmente la parte più difficile nella definizione di un componente è capire quanto deve essere grande o piccolo. Possiamo passare da un componente che contiene tutta l’inferfaccia a definire un componente che rappresenta la singola etichetta di testo. Qual è la giusta dimensione per un componente?

Non c’è una regola generale, perchè dipende molto dai requisiti dell’applicazione, e con l’esperienza diventeremo sempre più bravi a trovare il giusto equilibrio nel dimensionare i componenti. Ci sono però un po’ di indicatori che possono aiutarci a capire se stiamo andando nella giusta direzione.

Se il vostro componente svolge più di una funzione, ha cioè più di una responsabilità, è potenzialmente un componente troppo grande. Quindi un indicatore può essere proprio il principio di singola responsabilità. Se avete un componente in cui ad esempio gestite una griglia e una form di dettaglio, potenzialmente potreste dividerlo in 3 componenti: la griglia, il dettaglio e il componente che li contiene e li mette in comunicazione.

Un indicatore più preciso è invece la caratteristica di riutilizzabilità di un componente: se dobbiamo usare quel pezzo di interfaccia in più punti della nostra applicazione, allora è sicuramente un componente indipendente.

Un altro indicatore è l’assenza di logica applicativa, quei componenti che cioè hanno solo il markup: è uno spreco di risorse, specie se siete nel browser, perchè tecnicamente stiamo creando un oggetto per ogni componente, che occupa memoria ed entra a far parte degli elementi che il framework tiene sotto controllo per reagire quando interagite con essi. Per la stessa ragione è potenzialmente dannoso fare componenti piccolissimi, come una etichetta o un pulsante.

Una buona strategia, in caso non si sia sicuri di come dimensionare un componente, è quella di partire dal crearne uno e poi rifattorizzarlo in più componenti man mano che cresce.

Usateli insieme ma non accoppiateli!

E’ possibile quindi mettere un componente all’interno di un altro componente, ma come li faccio comunicare tra loro senza creare accoppiamento? E perchè non dovrei accoppiarli?

Nello sviluppo software è sempre un male accoppiare elementi applicativi, perchè questo, tra le altre cose, ne ostacola il riutilizzo. Ed è esattamente questa la ragione per la quale non dobbiamo farlo con i componenti. Se creiamo un componente che è accoppiato con suo padre, l’unico modo in cui potremo riutilizzarlo è usare il padre. Se invece il legame con il padre non è diretto, ma basato su una buona parametrizzazione, potrò riutilizzare direttamente il componente dove voglio.

Tutti i framework di front-end, compreso Blazor, ci permettono di creare componenti parametrizzabili, in modo da non dover avere nessun riferimento esplicito al componente che lo ospita, ma solo ricevere da esso dati in input ed emettere eventi in output a cui potrà eventualmente sottoscriversi.

Componenti in Blazor

Passiamo alla pratica e vediamo come creare un componente in Blazor. Il template base da cui siamo partiti ha già creato dei componenti per noi, alcuni dei quali hanno un ruolo molto specifico e si trovano nella cartella Pages. Vedremo che cosa sono le pagine in Blazor nel seguito, perchè abbiamo bisogno di introdurre dei concetti preliminari per comprenderne il ruolo, ma per il momento immaginiamoli come degli speciali componenti contenitori che rappresentano una pagina della nostra applicazione.

Tecnicamente in Blazor un componente è un file con estensione .razor, che contiene il markup, le direttive Razor per la gestione del markup e il binding dei dati, e, opzionalmente, il codice. Il nome del file definisce anche il selettore con cui possiamo chiedere al framework di istanziare per noi il componente e renderizzarlo nel punto in cui abbiamo utilizzato il selettore.

Il file NavMenu.razor, che abbiamo già visto nell’articolo precedente, è un ottimo esempio: contiene il markup (HTML e classi CSS per la definizione della classica Navbar di Bootstrap), le direttiva e le istruzioni Razor (quelle con il simbolo @ per capirci) e il blocco @code dove è definito il codice del componente. Per poter utilizzare questo componente, nel file MainLayout.razor troviamo il selettore <NavMenu />:

...
<div class="sidebar">
    <NavMenu />
</div>
...

Creiamo un nuovo componente da zero, in modo da seguire tutti i passaggi necessari. Creiamo nella root del progetto una cartella Components che useremo per i componenti non condivisi (per quelli condivisi esiste già la cartella Shared): in questo modo sarà più semplice individuarli e spostarli se necessario.

La nostra applicazione ha lo scopo di gestire eventi, quindi potremmo realizzare un componente che visualizza la lista degli eventi in formato tabellare. Una classica griglia a cui siamo sicuramente tutti abituati. Creaimo quindi un file ListaEventi.razor nella cartella Components e creiamo la nostra table:

<h2>Lista Eventi</h2>
<table class="table">
    <tr>
        <th>Id</th>
        <th>Nome</th>
        <th>Località</th>
        <th>Data</th>
    </tr>
    @foreach(var evento in ListaElementi)
    {
    <tr>
        <td>@evento.Id</td>
        <td>@evento.Nome</td>
        <td>@evento.Localita</td>
        <td>@evento.Data</td>
    </tr>
    }
</table>
@code {
    public List<ElementoListaEventi> ListaElementi { get; set; }
        = new List<ElementoListaEventi>
            {
                new ElementoListaEventi() { Id = 1, Nome="DevDay Benevento - Blazor", Localita="Benvento", Data = new DateTime(2020, 2,8)},
                new ElementoListaEventi() { Id = 2, Nome="DotNetSide Bari - Blazor", Localita="Bari", Data = new DateTime(2020, 2, 21)}
            };
}

Un semplice for ci permette di ciclare sugli eventi per creare le nostre righe. La lista degli eventi è una proprietà di tipo List<ElementoListaEventi>(per il momento riempita degli elementi fake), dove ElementoListaEventi è una classe C# creata in una cartella Models nella root del progetto:

using System;

namespace event_manager_server.Models
{
    public class ElementoListaEventi
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Localita { get; set; }
        public DateTime Data { get; set; }
    }
}

Si tratta di una classe con le sole proprietà che voglio mostrare nella griglia, quindi pensata ad uso e consumo di questo caso d’uso. Solitamente queste classi vengono dette ViewModel, perchè rappresentano la parte di modello dati ad uso e consumo di una particolare View.

Per il componente, dietro le quinte sarà creata una classe in un namespace che rispecchia la cartella in cui si trova, dobbiamo quindi aggiungere una using del namespace <nomeprogetto>.Components ogni volta che vogliamo usare il componente. In alternativa possiamo aggiungere la using nel file _Imports.razor, facendo lo stesso anche per il namespace Models della classe creata:

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using event_manager_server
@using event_manager_server.Shared

@using event_manager_server.Models
@using event_manager_server.Components

A questo punto posso richiamare il componente da qualsiasi punto dell’applicazione mediante il selettore <ListaEventi />, facciamolo ad esempio dalla pagina Index.razor:

@page "/"
<h1>Event Manager</h1>
<p>Benvenuti nella Single Page Application scritta in Blazor per la gestione degli eventi.</p>
<p>Selezionare dal menu laterale l'opzione desiderata.</p>

<ListaEventi />

Ecco il risultato:

Componente Blazor

Abbiamo creato e utilizzato il nostro primo Blazor Component! Trovate il codice aggiornato qui, nella branch 02-componenti.

Conclusioni

Per oggi ci fermiamo qui, nel prossimo articolo vedremo come parametrizzare il componente che abbiamo realizzato per renderlo più generico e riutilizzabile, in modo da poterlo poi usare in contesti differenti.