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 :
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
l'applicazione può essere eseguita anche su browser che non supportano webassembly
svantaggi :
ogni azione dell'utente potrebbe comportare una comunicazione tra server e client
è 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 :
scaricata l'applicazione dal server l'applicazione può funzionare anche offline
non è necessario,nel caso di applicazione webassembly standalone , avere un web server
Svantaggi di webassembly:
la prima esecuzione prevede un download più pesante rispetto a blazor server
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 :
Blazor_Web_App_Test
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"
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
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">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
@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 :
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
.
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 :
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
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
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
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
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
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
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
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 Learn) questa è 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
@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"
<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--;
}
}
@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 {….}
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
{
<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>
}
</tbody>
</table>
}
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 :
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
il titolo scelto è: <h3>@TitoloLibro</h3>
@code {
[Parameter]
public string? TitoloLibro { get; set; }
}
VisualizzaTitolo contiene invece il seguente testo :
<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">
<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ì :
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 .
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 :
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
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
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
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
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
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
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
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/"
<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
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 :
<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>
<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 :
<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;
}
}
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}"
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 :
<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ì :
<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 :
<h1>Cattura singoli parametri: </h1>
Route: par1= @Par1
@code {
[Parameter] public int Par1 { get; set; }
}