domenica 3 dicembre 2023

Appunti su Blazor prima parte : definizioni ,direttive ,componenti

Cosa significa Blazor : è la “contrazione” di Browser + Razor , browser sono il tipo di applicazione che usiamo per navigare su internet ,Razor fa riferimento a Razor Pages https://learn.microsoft.com/it-it/aspnet/core/razor-pages/?view=aspnetcore-8.0&tabs=visual-studio

Tipo di progetti Blazor :

Blazor Server :

in questo caso l'applicazione è eseguita lato server all'interno di un applicazione Asp .Net Core, i dati e gli eventi vengono gestiti tramite una comunicazione bidirezionale tra il server ed il client utilizzando SignalR (https://learn.microsoft.com/it-it/aspnet/signalr/overview/getting-started/introduction-to-signalr).

I vantaggi del modello Blazor Server sono :

  1. la dimensioni inferiore dei file da scaricare anche la prima volte che si esegue l'applicazione da parte del client infatti vengono solo scaricati i javascript , html e css

  2. l'applicazione può essere eseguita anche su browser che non supportano webassembly

svantaggi :

    1. ogni azione dell'utente potrebbe comportare una comunicazione tra server e client

    2. è ovviamente indispensabile un web server che esegue l'applicazione Asp .Net Core la quale ospiti l'applicazione Blazor Server

Blazor WebAssembly :

in questo caso si distribuisce l'applicazione e l'esecuzione lato client all'interno del browser che supporta WebAssembly, WebAssembly (detto anche in forma abbreviata Wasm) è (https://webassembly.org/ ) :

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

Possiamo distinguere tra due tipi di applicazione webassembly ovvero “hosted” e “standalone” ,nel primo caso l'applicazione è ospitata in un applicazione Asp .Net Core nel secondo caso è possibile non avere un server asp .net core.

Vantaggi di webassembly :

  1. scaricata l'applicazione dal server l'applicazione può funzionare anche offline

  2. non è necessario,nel caso di applicazione webassembly standalone , avere un web server

Svantaggi di webassembly:

  1. la prima esecuzione prevede un download più pesante rispetto a blazor server

  2. risorse limitate del browser potrebbero impedire un funzionamento ottimale dell'applicazione ,inoltre il browser deve essere in grado di gestire il formato WebAssembly

Creiamo un progetto di tipo Blazor Web App e di nome Blazor_Web_App_Test , possiamo esplorare i due progetti creati :

  1. Blazor_Web_App_Test

  2. Blazor_Web_App_Test.Client

entrambi i progetti hanno un file Program.cs dove vengono configurati i due progetti , nel progetto 2 nella dir Pages troviamo un file Counter.razor,

il codice contiene nelle prime due righe due direttive :

@page "/counter"

@rendermode InteractiveWebAssembly 

la direttiva page definisce il percorso(la route) a cui sarà disponibile il componente (la “pagina” in questo caso che ha un estensione .razor ma un componente può essere anche una piccola ( o meno) unità indipendente composta sia da codice che da markup) , la direttiva rendermode definisce quale sarà il suo modello di hosting ,qui una pagina che contiene l'elenco (e non solo) dei valori che può assumere rendermode https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0 .
In pratica se nella pagina Counter modifichiamo la direttiva page da “/counter” a “counter2” ed eseguiamo l'applicazione per chiamare Counter la route dovrà essere :

se proviamo ad utilizzare nel menu l'elemento NavLink con valore counter che tenta il routing su “/counter” otterremo:

ovvero pagina non trovata.

NavLink è un componente ,per vederne le caratteristiche potete andare e vedere https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-8.0 e 

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.routing.navlink?view=aspnetcore-7.0

Per vedere il menu è necessario andare nel primo progetto (quello che non finisce per “.Client”) ,andare nella cartella Components e selezionare la dir Layout ,ora aprite la pagina NavMenu.razor e potrete osservare come è costituito il menu,il secondo NavLink ha valore "counter" nell'attributo href 

<div class="nav-item px-3">

   <NavLink class="nav-link" href="counter">
      <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
   </NavLink>
</div>

Le applicazioni Blazor sono costituite principalmente da componenti razor,questi devono seguire qualche regola formale per essere utilizzabili , una di queste riguarda il nome il quale deve iniziare con una maiuscola ,esempio TestListino.razor è un nome valido mentre testListino.razor non è un nome valido . Possiamo ora analizzare il componente Counter.razor ,segue il codice :

@page "/counter"
@rendermode InteractiveWebAssembly
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
    }
}

abbiamo già visto a cosa servono le due direttiva in cima alla “pagina” ,osserviamo il simbolo @ prima di currentCount , questo comporta che qui sarà visualizzato il valore della variabile currentCount la quale è definita nel blocco di codice c# definita nel blocco che inizia con @code , la variabile è inizializzata a zero ed il suo valore è incrementato di 1 ogni volta che viene eseguito il metodo IncrementCount , il metodo è eseguito ogni volta che verrà cliccato il bottone “Click me” il quale determina che all' onclick si esegua IncrementCount ,il codice è @onclick=”IncrementCount” .

Possiamo modificare leggermente l'esempio aggiungendo un altro metodo che decrementa il valore :

@page "/counter"

@rendermode InteractiveWebAssembly

<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<button class="btn btn-primary" @onclick="DecrementCount">Decrementa</button>

@code {

  private int currentCount = 0;

  private void IncrementCount() {

    currentCount++;

  }

  private void DecrementCount() {

    currentCount--;

  }

}

Sempre nella directory Components sotto Pages troviamo un file Weather.razor ,le prima due righe sono:

@page "/weather"
@attribute [StreamRendering]

@page ci indica a quale route sarà disponibile la risorsa , StreamRendering comporta che sia visualizzato comunque un messaggio alternativo mentre si aspetta un risultato ,se proviamo a togliere l'attributo non vedremo “Loading..” ,
segue il codice di Weather.razor ,per vedere meglio questa differerenza di comportamento possiamo impostare a 5000 l'intervallo in await Task.Delay(5000); settato nell'area @code {….}

@page "/weather"
@attribute [StreamRendering]

<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
    <thead>
    <tr>
    <th>Date</th>
    <th>Temp. (C)</th>
    <th>Temp. (F)</th>
    <th>Summary</th>
    </tr>
    </thead>
    <tbody>
    @foreach (var forecast in forecasts)
    {
        <tr>
        <td>@forecast.Date.ToShortDateString()</td>
        <td>@forecast.TemperatureC</td>
        <td>@forecast.TemperatureF</td>
        <td>@forecast.Summary</td>
        </tr>

    }
    </tbody>
    </table>

 }

@code {
    private WeatherForecast[]? forecasts;
    protected override async Task OnInitializedAsync(){
        //segue simulazione di caricamento asincrono
        await Task.Delay(5000);
        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToArray();
    }
    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Qui la documentazione relativa allo StreamRendering :

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-8.0#streaming-rendering

I componenti accettano ovviamente dei parametri ,creiamo un componente che permetta di inserire un parametro stringa , chiamiamolo AggiungiTesto e creiamo un componente di nome VisualizzaTitolo ,AggiungiTesto contiene il seguente codice


<h3>AggiungiTesto</h3>
il titolo scelto è: <h3>@TitoloLibro</h3>
@code {
    [Parameter]
    public string? TitoloLibro { get; set; }
}

VisualizzaTitolo contiene invece il seguente testo :


@page "/visualizzatitolo"
<h3>VisualizzaTitolo</h3>
    <WebAssemblyBlazor.Components.Pages.AggiungiTesto TitoloLibro="Angeli e demoni">              </WebAssemblyBlazor.Components.Pages.AggiungiTesto>
  @code {
  }

aggiungiamo un link nel menu laterale definito in NavMenu :

<div class="nav-item px-3">

    <NavLink class="nav-link" href="visualizzatitolo">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Visualizza TItolo
    </NavLink>
</div>



rispetto a chiamare un componente page passando l'intero namespace WebAssemblyBlazor.Components.Pages è preferibile di gran lunga usare uno using che eviti di scrivere il percorso di classi , a questo punto possiamo aggiungere uno using in testa alla pagina razor così :


@using WebAssemblyBlazor.Components.Pages

    oppure possiamo aggiungere lo using nel file _Imports.razor in questo modo :

@using WebAssemblyBlazor.Components.Pages
@using System.Net.Http
etc.etc.
.
.
ora possiamo scrivere: <AggiungiTesto TitoloLibro="Angeli e demoni"></AggiungiTesto>

Per utilizzare con consapevolezza i componenti Blazor e comprendere il loro ciclo di vita è utile leggere la documentazione microsoft presente a questi due indirizzi https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-8.0

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-8.0&preserve-view=true

I componenti derivano da ComponentBase ed andando proprio alla documentazione di questa classe possiamo vedere di quali metodi possiamo fare l'override nei nostri   componenti https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase?view=aspnetcore-7.0 , facendo l'override di tali metodi possiamo aggiungere comportamenti al nostro componente e vedere il susseguirsi degli eventi relativi al componente.

Eventi nel ciclo di vita di un ComponentBase :

  1. SetParametersAsync : valorizza i componenti forniti dal componente padre,questo metodo prende come parametro un ParameterView ,questo rappresenta una collezione di Parametri forniti dal componente padre ,il metodo ritorna un Task quando il componente ha terminato di aggiornarsi e renderizzarsi

  2. OnInitialized : questo metodo è invocato quando il metodo ha ricevuto i parametri dal componente padre ed è pronto all'esecuzione ,questo può essere il momento adeguato a caricare eventuali dati che popoleranno l'interfaccia utente

  3. OnInitializedAsync : ha lo stesso scopo dell'equivalente sincrono ma è consigliabile nel caso i dati siano recuperati da un servizio remoto, dove abbiamo sia la latenza della rete sia i tempi di esecuzione sulla macchina remota rispetto ai quali potremmo non poter intervenire direttamente , oppure da un db un po' sovraccarico

  4. OnAfterRender(firstRenderization: True) : questo metodo viene invocato quando è terminata la renderizzazione del componente ,sia che si tratti della prima renderizzazione sia che sia un aggiornamento della stessa

  5. OnAfterRenderAsync(firstRenderization: True) questo metodo viene invocato ogni volta che c'è una renderizzazione del componente sia che si tratti della prima visualizzazione sia che si tratti di un aggiornamento successivo , questa versione è indicata quando ci connettiamo ad un servizio od ad un db rispetto ai quali non abbiamo certezza di una risposta rapida

    i metodi ai punti 4) e 5) ricevono un booleano che vale true nel caso sia la prima visualizzazione e false in caso contrario

  6. e 7) OnParameterSet e OnParametersSetAsync sono chiamati ogni volta che il componente ha ricevuto dei parametri dal suo componente padre,questo potrebbe essere il posto adatto per gestire l'aggiornamento dei dati quando essi dipendono da un parametro che diventa parte di una query

    1. StateHasChanged : questo metodo notifica al componente che il suo stato è cambiato , questo metodo può essere utilizzato per ottenere la re-renderizzazione de componente semplicemente invocandolo

    1. ShouldRender : questo metodo viene chiamato ogni volta che il componente è renderizzato ,se l'override del metodo ritorna true l'interfaccia viene aggiornata

      nell'esempio che trovate qua https://learn.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-8.0#suppress-ui-refreshing-shouldrender viene fatto il bind di una checkbox con la variabile privata che viene ritornata da ShouldRender


                                                         Direttive :

le abbiamo già incontrate ma riprendiamo l'argomento , si tratta di parole chiave precedute dal simbolo @ che permettono di aggiungere funzionalità al componente,vediamone alcune :

@page : questa direttiva definisce a quale route sarà disponibile il componente ,ad esempio nel template di visual studio WebAssembly sotto la cartella Pages trovate un componente Razor che si chiama Counter.razor ,la prima riga di questo file è @page "/counter" il quale ci dice che il componente sarà richiamabile all'indirizzo https://localhost:7252/counter (la prima parte dell'indirizzo dipende ovviamente dal valore presente in lauchsettings.json ,in particolare dal valore della chiave "applicationUrl" ) , possiamo avere più direttive @page in un singolo componente ad esempio per passare un parametro con un valore , restando all'esempio di Counter.razor possiamo aggiungere una direttiva @page "/counter/{param1:int?}" vediamo uno snippet di esempio :

@page "/counter/"

@page "/counter/{param1:int?}"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Incrementa (di @Param1)</button>
@code {
    //segue la riga che inizializza ad 1 il valore del parametro
    private int _param1=1;
    //segue il codice che valorizza inizialmente @currentCount ad 1
    private int currentCount = 0;
    private void IncrementCount()
    {
        //il conteggio viene aggiornato aumentando currentCount del valore di param1
        currentCount += _param1;
    }
    [Parameter]
    public int Param1{
    get
    {
        return _param1;
    }
    set
    {
        if(value>0)
            _param1=value;
        else _param1=1;
    }
}

se “chiamiamo” il componente con la route definita dalla prima direttiva page ovvero https://localhost:7252/counter otteniamo


se passiamo un valore per ricadere nella seconda route '@page "/counter/{param1:int?}"' otteniamo :

se passiamo un numero negativo come valore del parametro questo verrà riportato ad 1 nel set di Param1 ricadendo nell' else   

un'altra importante direttiva è @code ,come abbiamo già visto ci permette di aggiungere membri alla classe (variabili , parametri , metodi etc.etc. ) ,@code permette di creare un'area compresa tra una coppia di graffe dove possiamo scrivere in c# liberamente ,

@attribute è un importante direttiva che ci permette ad esempio di sottoporre ad autorizzazione una pagina razor usando @attribute [Authorize] , segue il link alla documentazione ufficiale https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-8.0#authorize-attribute , comunque più avanti torneremo su questo tema ,

la direttiva @inject è usata per la dependency injection , ad esempio se ci serve un client http possiamo aggiungere la direttiva “@inject HttpClient Http” ,per utilizzare questa inject dobbiamo prima aver aggiunto l'HttpClient all'elenco dei nostri services , qui un esempio https://learn.microsoft.com/it-it/aspnet/core/blazor/call-web-api?view=aspnetcore-7.0&pivots=webassembly#add-the-httpclient-service , come si vede nel codice il servizio viene aggiunto “AddScoped” ovvero per ogni richiesta client , per vedere la differenza tra Transient ,Scoped,Singleton potete leggere il seguente articolo https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes

abbiamo utilizzato poco sopra la direttiva @bind (ASP.NET Core Razor component rendering | Microsoft Learnquesta è la direttiva utilizzata per il data binding ,al seguente link troverete la descrizione della direttiva e più in generale il data binding per Asp .Net Core Blazor https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-8.0 ,altre direttive verranno commentate più avanti nel tutorial

Routing:

in Blazor (in Asp .Net Core) troviamo un namespace Microsoft.AspNetCore.Components.Routing dedicato al routing ed alla navigazione ,esso contiene classi , interfacce ed un enumerato , per il momento vedremo brevemente solo le classi ovvero Router e NavLink , qui la pagina dedicata al namespace .

Nel file Routes.razor troviamo un Router :

<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] {     typeof(Client._Imports).Assembly }">
 <Found Context="routeData">
   <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
   <FocusOnNavigate RouteData="@routeData" Selector="h1" />
 </Found>
</Router>

il codice soprastante è contenuto nel file Routes.razor ,il componente Routes è chiamato nel componente iniziale dell'applicazione ovvero nel componente App di cui segue il codice,notate il riferimento a <Routes /> :

<!DOCTYPE html>

<html lang="en">
   <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="BlazorApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
   </head>
   <body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
   </body>
</html>

in pratica in Blazor WebAssembly il routing è gestito dal client ovvero quando si naviga Blazor cattura l'url a cui stiamo puntando ed esegue il componente che ha una direttiva @page che corrisponde alla route .

Possiamo catturare tutte le routes usando il simbolo * ,segue esempio :


@page "/cattura/{*path}"
<h1>Cattura tutto: </h1>
Route: @Path
@code {
    [Parameter] public string? Path { get; set; }
}

aggiungiamo una pagina razor “CatturaParametri.razor” al progetto e sovrascrivete il contenuto di default con lo snippet soprastante , eseguite e chiamate https://localhost:7152/cattura, ovvero aggiungete “/cattura” all'indirizzo del server ed otterrete

A Route non segue niente niente coerentemente al fatto che “*path” è nullo , il codice non dà errore poiché nullable era uno dei valori possibili previsti grazie al '?' dopo il tipo string ,inseriamo un valore per path : https://localhost:7152/cattura/tigre :

aggiungiamo un altro parametro , https://localhost:7152/cattura/tigre/elefante ed otteniamo :

appare chiaro che nella maggior parte dei casi questa non può essere la strada giusta per ottenere del codice robusto nel caso si abbiamo più parametri ,una soluzione migliore potrebbe essere questa :

@page "/parametri/{par1}/{par2}"

<h1>Cattura singoli parametri: </h1>
Route: par1= @Par1 ,par2= @Par2
@code {
    [Parameter] public string? Par1 { get; set; }
    [Parameter] public string? Par2 { get; set; }
}




possiamo anche impostare un tipo di dato del parametro nella direttiva page creando delle routes più significative ,ad esempio

cambiamo lo snippet sopra con questo :


@page "/parametri/{par1}/{par2:int}"
<h1>Cattura singoli parametri: </h1>
Route: par1= @Par1 ,par2= @Par2
@code {
    [Parameter] public string? Par1 { get; set; }
    [Parameter] public int? Par2 { get; set; }
}

possiamo definire il tipo del parametro aggiungendo i doppi punti dopo il nome del parametro e prima del tipo di dato da noi richiesto , in questo ho aggiunto int ovvero per “chiamare” questo componente occorre che il secondo parametro sia un intero , in caso contrario semplicemente non istanzieremo il componente e non otterremo una risposta ,per aggiungere una risposta nel caso si stia chiamando una route non esistente leggete il paragrafo a questo link .

Possiamo passare dei tipi nullable ,modifichiamo l'esempio sopra ed aggiungiamo il punto di domanda dopo int così :


@page "/parametri/{par1}/{par2:int?}"
<h1>Cattura singoli parametri: </h1>
Route: par1= @Par1 ,par2= @Par2
@code {
    [Parameter] public string? Par1 { get; set; }
    [Parameter] public int? Par2 { get; set; }
}

passiamo il seguente path https://localhost:7152/parametri/leone ed otteniamo :


ovvero abbiamo ottenuto una risposta poiché il caso in cui il secondo parametro è nullo è un caso valido nella definizione della route dopo che abbiamo reso nullable il secondo parametro.

Se il parametro è definito senza specificarne il tipo è un tipo stringa ,se provate il seguente snippet :


@page "/parametri/{par1}"
<h1>Cattura singoli parametri: </h1>
Route: par1= @Par1
@code {
    [Parameter] public int Par1 { get; set; }
}

e puntiamo alla route https://localhost:7152/parametri/7 otteniamo:


Per suggerimenti ,critiche o segnalazione errori potete inviare un email a gianmarco.castagna@gmail.com

Crittografia e WCF per passare una password ( od una qualsiasi altra stringa (xml,json, etc.etc.) ) da un applicazione ad un' altra in relativa sicurezza

 Il codice che segue è da considerarsi in alpha e da non utilizzare in un ambiente di produzione , qui potete trovare il  "progetto&quo...