domenica 25 febbraio 2024

Scegliere password complesse ma facili da "indovinare" ? Come evitarlo..

 La lunghezza di una password non ne implica sempre la sicurezza , la password "Passw0rd!" è composta da 9 caratteri  di cui uno maiuscolo ,un numero ed un carattere speciale quindi formalmente è una password forte, in realtà non è così nella pratica se le credenziali sono sottoposte ad un attacco a dizionario usando le password più comuni ( https://it.wikipedia.org/wiki/Attacco_a_dizionario )  . Probabilmente un dizionario usato nell'attacco potrebbe contenere anche la password di cui sopra perchè è molto comune , per mitigare questo tipo di problemi possiamo utilizzare delle liste che contengono le password più utilizzate per verificare in fase di registrazione dell'utente che la password scelta dall'utente non sia tra queste . A quest'indirizzo   https://github.com/danielmiessler/SecLists/tree/master/Passwords/Common-Credentials potete trovare delle liste di password comuni (1000000 di password e c'è anche 'Passw0rd!' , :-) ) .

Ovviamente un attacco dizionario non è comunque facile da portare a segno se abbiamo configurato la nostra applicazione adeguatamente, ad esempio in asp.net core possiamo settare così la nostra applicazione :

builder.Services.AddDefaultIdentity<IdentityUser>
    (options =>
    {
        options.SignIn.RequireConfirmedAccount = true;
        options.Password.RequireDigit = true;
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = true;
        options.Lockout.MaxFailedAccessAttempts = 3;
        options.Lockout.AllowedForNewUsers = true;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
    });

impostare il lockout con la valorizzazione dei tentativi falliti dopo i quali l'account è locked ed il periodo per cui esso sarà lockout è quindi relativamente semplice .

E' disponibile qui  https://info.microsoft.com/rs/157-GQE-382/images/EN-CNTNT-eBook-MicrosoftPasswordGuidance.pdf  un documento di Microsoft  che  è  una "Microsoft Password Guidance" , in questo paper è presente anche un link al sito di "Schneier on Security" ( https://www.schneier.com/ ) ,in particolare al post https://www.schneier.com/blog/archives/2014/03/choosing_secure_1.html , chi fosse interessato a tematiche di sicurezza e crittografia potrà trovare interessante la newsletter mensile del sito di Schneier

Per verificare se la password inserita dall'utente appartiene a quelle da ritenere troppo comuni per prima cosa carichiamo le "password comuni" in una tabella 'Passwords' creata nel db "GestioneUtenti" ,un pò di "pseudocodice" :

 private void button1_Click(object sender, EventArgs e)
        {
            IEnumerable<string> lines = File.ReadLines(@"C:\Users\gianm\Desktop\10-million-password-list-top-1000000.txt");
            long l = lines.LongCount<string>(); //999998

            SqlConnection conn = new SqlConnection("Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GestioneUtenti;Integrated Security=True");
            conn.Open();

            foreach (string line in lines) {
                SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
                par.Value = line;

                SqlCommand cmd = new SqlCommand("insert into Passwords(password) values (" + par + ")",conn);
                cmd.Parameters.Add(par);
                cmd.ExecuteNonQuery();
                par = null;
                cmd = null;
            }
            conn.Close();
        }

il codice impiegherà da qualche decina di secondi ad alcuni minuti per caricare le passwords nella tabella 'Passwords' 

segue un metodo per verificare se la password inserita dall'utente esiste nella tabella Passwords:

 private bool CheckPasswordExists(string s)
        {
            bool b = false;
            SqlConnection conn = new SqlConnection("Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GestioneUtenti;Integrated Security=True");
            conn.Open();
            SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
            par.Value = s;
            SqlCommand cmd = new SqlCommand();
            cmd.Parameters.Add(par);
            cmd.Connection = conn;
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = "select * from Passwords where Password like " + par;
            SqlDataReader reader = cmd.ExecuteReader();
            bool read = reader.Read();
            b = read;
            reader.Close();
            conn.Close();       
            return b;
        }

se il metodo torna true significa che la password esiste nella lista delle password comuni e quindi bisogna indicare all'utente di scegliere un' altra password ,
se il metodo torna false la password non è tra quelle della nostra lista e possiamo lasciar procedere l'utente nella registrazione .

Ora vedremo come utilizzare ulteriori funzionalità proprie di Asp .Net Core per verificare che la password immessa dall'utente non sia troppo comune :

nel file Program.cs ,di un progetto AspNetCore, possiamo aggiungere alla configurazione un AddPasswordValidator con cui costruire una nostra logica di validazione della password ( https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identitybuilder.addpasswordvalidator?view=aspnetcore-7.0 ) :

builder.Services.AddDefaultIdentity<IdentityUser>
            (options =>
            {
                options.SignIn.RequireConfirmedAccount = true;
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 8;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.RequireLowercase = true;
                options.Lockout.MaxFailedAccessAttempts = 3;
                options.Lockout.AllowedForNewUsers = true;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
            }) .AddPasswordValidator<TestPasswordValidator<IdentityUser>>()               
               .AddEntityFrameworkStores<AppDbContext>();

creiamo una classe TestPasswordValidator :

public class TestPasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
    {
        
public TestPasswordValidator()
{
}
    public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user, string password)
    {
        SearchIntoDb s = new SearchIntoDb();
        bool res = s.CheckPasswordExists(password);        
        IdentityResult result;
        if (res)
        {
            result = IdentityResult.Failed(new IdentityError { Description = "Password troppo facile da indovinare" });
        } else {
            result = IdentityResult.Success;
        }
        s = null;
        return Task.FromResult(result);
    }
}

la classe deve implementare l'interfaccia IPasswordValidatorhttps://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.ipasswordvalidator-1?view=aspnetcore-7.0 ) e quindi essa deve avere un' implementazione del metodo ValidateAsync previsto dall'interfaccia , sarà questo metodo a ritornare un IdentityResult . Creando l'IdentityError possiamo valorizzare la Description dell'errore , nel caso la password non sia presente nell'elenco la variabile "res" varrà false ed il metodo ritornerà un IdentityResult.Success.

Il metodo ValidateAsync utilizza una classe SearchIntoDb ed un metodo CheckPasswordExists (già riportato sopra ) per effettuare la select nel db al fine di verificare se la password appartiene alla tabella Passwords oppure no.

public class SearchIntoDb
{

    private string connectionString = null;    
    public SearchIntoDb()
    {
        var config = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json").Build();
        string t=config.GetSection("ConnectionStrings").GetSection("UtentiConnection").Value;
        connectionString = t;
    }
   
    public bool CheckPasswordExists(string password)
    {
        bool t = false;        
        SqlConnection conn = new SqlConnection(connectionString);
        conn.Open();
        SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
        par.Value = password;
        SqlCommand cmd = new SqlCommand();
        cmd.Parameters.Add(par);
        cmd.Connection = conn;
        cmd.CommandType = System.Data.CommandType.Text;
        cmd.CommandText = "select * from Passwords where Password like " + par;
        SqlDataReader reader = cmd.ExecuteReader();
        bool b = reader.Read();
        t = b;
        reader.Close();
        conn.Close();       
        return t;
    }

}

proviamo il codice in un applicazione asp .net core , inseriamo la famigerata password "Passw0rd!" 

                              

ed otteniamo quanto ci aspettavamo :



    


Il post ha solo scopo di esempio e la lista di password utilizzata non rappresenta un elenco esaustivo delle password "comuni" .


domenica 18 febbraio 2024

Appunti su Blazor seconda parte : i componenti dei template di esempio

 I componenti :

i componenti possono essere costruiti con 3 modalità differenti :

  1. usando la sintassi Razor si può scrivere in c# ed in html nello stesso file

  2. si può utilizzare un file c# di code behind insieme ad un .razor file

  3. si può utilizzare il solo file c#

Creiamo un applicazione di tipo App WebAssembly Blazor su .Net 7.0

osserviamo il progetto:

la soluzione è composta da 3 progetti :

1) AppWebAssemblyRazor.Client

2)AppWebAssemblyRazor.Server

3)AppWebAssemblyRazor.Shared

il primo progetto ha tra le dipendenze anche il progetto Shared

il secondo ha tra le dipendenze sia il progetto Client che il progetto Shared.

Portiamo la nostra attenzione al file Program.cs del progetto AppWebAssemblyRazor.Client :

public class Program

{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("#app");
        builder.RootComponents.Add<HeadOutlet>("head::after");
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new             Uri(builder.HostEnvironment.BaseAddress) });
    await builder.Build().RunAsync();
    }
}

per prima cosa viene creato un builder ,vengono poi aggiunti due RootComponents ,qui la collezione Rootcomponentmappingcollection https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.rootcomponentmappingcollection.add?view=aspnetcore-7.0 , i due rootcomponents aggiunti sono App ed HeadOutlet ,per vedere come è fatto HeadOutlet selezionate HeadOutlet dopo Add ovvero il tipo di componente aggiunto ,cliccate due volte sul nome della classe :


e vedrete il sorgente ,la classe è sealed (quindi in ogni caso niente ereditarietà da questa classe ) e deriva da ComponentBase :


dopo aver aggiunto i due RootComponents viene aggiunto alla collezione dei servizi un HttpClient in modalità AddScoped ovvero la durata della sua vita sarà quella di una singola request , il suo BaseAddress sarà: BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) ovvero la radice dell'host . Per imparare le differenze tra Scoped , Transient e Singleton un buon inizio è quest'articolo che tratta anche delle dependency injection https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0

In Blazor possiamo “mischiare” codice html e codice c# in vari modi ,per aggiungere il codice c# possiamo usare varie modalità :

  1. usare i Razor code blocks

  2. usare le espressioni implicite

  3. usare le espressioni esplicite

  4. le direttive

  1. per dichiarare un code block possiamo usare due sintassi differenti:

    a) possiamo usare “@code” , in questo modo

    @code {

    //qui codice 

    }

    b) oppure possiamo non utilizzare la keyword “code” e scrivere direttamente così :

    @{

    //qui codice 

    }

un esempio basico di c# ed html può essere il seguente :

@{
        void StampaUnaStringa (string stringaDaStampare) {
            <p>stringa: <bold> @stringaDaStampare </bold> </p>
        }
        //chiamiamo la funzione StampaUnaStringa
        StampaUnaStringa("Il mio nome è nessuno");
        StampaUnaStringa("rispose Ulisse al ciclope");
}

aggiungiamo lo snippet alla pagina counter ed otteniamo :



  1. le blazor implicit expression si hanno quando aggiungiamo del codice “dentro” dei tag :

    <td>@stringaDaStampare</td>

  2. le blazor expression esplicite si utilizzano quando il nostro codice contiene degli spazi , in questo caso il nostro codice sarà preceduto da '@' e contornato da una coppia si parentesi tonde ,ad esempio aggiungiamo questo snippet nel component Counter :  

      <div>
      <table>
      <tr>
      <td>tempo</td>
      <td>UTC:@DateTime.UtcNow</td>
      </tr>
      <tr>
      <td>tempo UTC </td>
      <td>@DateTime.UtcNow</td>
      </tr>
      <tr>
         <td>tempo UTC - 1minuto</td><td>@(DateTime.UtcNow - TimeSpan.FromSeconds(60))</td>
      </tr>
      </table>
      </div>

andando alla “pagina” Counter visualizzeremo : 

per la descrizione dell'acronimo UTC potete andare qui .

  1. se vogliamo aggiungere un attributo alla nostra pagina ,ad esempio [Authorize] possiamo fare così :

    @attribute [Authorize]

    se vogliamo aggiungere lo stesso attributo in un file di code behind basterà [Authorize] .

    Se vogliamo aggiungere un interfaccia sarà sufficiente aggiungere

    @implements NomeInterfaccia se siamo in una pagina oppure se siamo in un code behind

    public class ClasseDiTest : NomeInterfaccia { …..................... } .

    Se vogliamo ereditare da un altra classe possiamo usare la direttiva inherits ,se siamo nel contesto di una pagina :

    @inherits ClasseDaCuiEreditare

    se siamo in un contesto code-behind :

    public class ClasseDiProva: ClasseDaCuiEreditare { …............... }

    Se vogliamo applicare un particolare layout ad una pagina invece di utilizzare il layout di default definito nel file App.razor possiamo usare la direttiva layout :

    @layout UnLayoutSpecifico

    Se vogliamo che il nostro componente non sia nel namespace di default ma in un namespace da noi scelto possiamo usare la direttiva namespace :

    @namespace NamespaceNonDiDefault

    Per aggiungere un namespace al nostro componente possiamo usare la direttiva @using :

    @using System.Net , ovviamente possiamo aggiungere più namespace :
    @using System.CodeDom
    @using System.Net
    @using System.IO
        ............. etc etc

                                        Ritorniamo sui componenti di base:

Il componente App è contenuto dal file App.razor e contiene il seguente codice :


<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

il componente App utilizza il componente Router ,se la route è trovata la RouteView riceve routeData e visualizza lo specificato componente utilizzando il DefaultLayout , se la route non è trovata viene eseguito quanto indicato nel tag NotFound .

Il tag Found contiene anche il componente FocusOnNavigate del quale vengono valorizzate due proprietà : RouteData al quale viene passato il valore @routeData e Selector ,qui è possibile trovare la documentazione della classe FocusOnNavigate :  https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.routing.focusonnavigate?view=aspnetcore-7.0 .

Nella cartella Pages troviamo la “pagine” chiamabili tramite route , la route alla quale corrisponde la pagina è ormai chiaro come sia definito nella variabile @page valorizzata all'interno della pagina.

Il folder Client contiene il file Program.cs :

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("#app");
        builder.RootComponents.Add<HeadOutlet>("head::after");
        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new             Uri(builder.HostEnvironment.BaseAddress) });
        await builder.Build().RunAsync();

    }
}

questo è l'entry point dell'applicazione, vengono aggiunti due componenti alla collezione RootComponents e viene aggiunto un servizio HttpClient ,questo potrà essere iniettato in un componente tramite l'attributo @inject .

La cartella Shared contiene di default tre componenti :

1) MainLayout

2) NavMenu
3) SurveyPrompt

1) segue il codice di MainLayout ,osserviamo il “tag” <NavMenu /> che inserisce il componente NavMenu :


@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>
    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>
        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

ereditando da LayoutComponentBase il componente ha una sola proprietà ovvero Body (https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.layoutcomponentbase?view=aspnetcore-8.0)

la proprietà è di tipo RenderFragment ovvero rappresenta un segmento di contenuto dell'interfaccia utente (qui la doc )  


  1. il componente NavMenu : segue il codice :

    <div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
    <a class="navbar-brand" href="">Demo1</a>
    <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
    <span class="navbar-toggler-icon"></span>
    </button>
    </div>
    </div>
    <div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
    <div class="nav-item px-3">
    <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
    <span class="oi oi-home" aria-hidden="true"></span> Home
    </NavLink>
    </div>
    <div class="nav-item px-3">
    <NavLink class="nav-link" href="counter">
    <span class="oi oi-plus" aria-hidden="true"></span> Counter
    </NavLink>
    </div>
    <div class="nav-item px-3">
    <NavLink class="nav-link" href="fetchdata">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
    </NavLink>
    </div>
    </nav>
    </div>
    @code {
       private bool collapseNavMenu = true;
       private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
       private void ToggleNavMenu()
       {
           collapseNavMenu = !collapseNavMenu;
       }
    }

il componente utilizza la classe NavLink (qui la doc della classe ) , NavLink crea un tag “a” con l'attributo href ed alcune caratteristiche in più come l'attributo Match il quale può assumere un valore tra i due previsti dall' enumerato NavLinkMatch ( qui l'enum ) .

Nell'area @code troviamo un metodo “toggle” (attiva/disattiva) che assegna valore “collapse” o null alla variabile stringa NavMenuCssClass che valorizza l'attributo class del div che contiene il tag “nav” all'interno del quale sono definiti i singoli link (NavLink) .


  1. passiamo al componente SurveyPrompt : anche questo non ha un attributo @page e quindi non è richiamabile tramite route ,la pagina Index.razor lo chiama tramite “tag” e passando un valore per il parametro Title ,segue SurveyPrompt.razor :

    <div class="alert alert-secondary mt-4"><span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>
    <span class="text-nowrap">
    Please take our
    <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
         </span>
         and tell us what you think.
    </div>
    @code {
    // Dimostriamo come un componente parent può fornire dei parametri (Title)
    [Parameter]
    public string? Title { get; set; }
    }

segue Index.razor in cui viene passato il valore di title a SurveyPrompt :


@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />

Analisi dei componenti nel folder "Pages" :

Esaminiamo i file sotto la directory Pages :

1)Index
2)Counter
3)FetchData

tutte e tre i componenti sono accomunati dall'avere una direttiva @page che consenti di chiamare i componenti tramite routing

1) Index.razor ,segue il codice :


@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />

osserviamo la presenza di PageTitle , si tratta di un componente già pronto e disponibile che fornisce le funzionalità di Title , qui la PageTitle class ,per il componente SurveyPrompt vale quanto già detto sopra

2) Counter.razor è un componente un po' più articolato di Index ,segue il codice:

@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>
@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
    }
}
come per Index abbiamo la direttiva page , la quale nel caso specifico vale “/counter” ,segue PageTitle , in questo caso abbiamo un area @code la quale definisce una variabile currentCount di tipo int inizializzandola a zero , il valore della variabile è visualizzato dopo “count :” utilizzando @currentCount , nell'area code abbiamo anche un metodo che incrementa di 1 il valore di currentCount , il metodo è chiamato ogni volta che si clicca sul bottone “Click me” .

  1. FetchData ,è il componente più complesso tra quelli del progetto di esempio ,segue il codice :

@page "/fetchdata"
@inject HttpClient Http
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</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()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }
    public 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);
    }
}

le prime due righe contengono due direttive ,la direttiva page che definisce la route e la direttiva inject che consente di utilizzare l'HttpClient nella pagina,HttpClient è aggiunto ai servizi nel file program.cs con la seguente istruzione :

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

segue un “PageTitle” nell 'area @code troviamo definita una variabile forecasts che può essere null ed è un array di oggetti WeatherForecast , nell'override del metodo OnInitializedAsync viene caricata la variabile forecast , per fare ciò viene chiamato il metodo GetFromJsonAsync di HttpClient il quale punta al file “sample-data/weather.json” ,da notare che la classe WeatherForecast contiene 4 proprietà ,le prime 3 sono valorizzate dalle proprietà con lo stesso nome presenti nel json ,la quarta è una proprietà calcolata ovvero la temperatura in gradi Fahrenheit ( TemperatureF ) partendo dalla temperatura in gradi Celsius ( TemperatureC ) .

                                                                        Logging:

di default i progetti WebAssembly hanno abilitato un provider di logging che scrive nella Console ,se dobbiamo far persistere i risultati dobbiamo utilizzare un altro provider per il logging , segue l'esempio del file Counter.razor che scrive nella Console, potete vederla selezionando la finestra di output di visual studio :


@page "/counter"
@inject ILogger<Counter> logger

<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()
    {
        logger.LogInformation("incremento di 1");
        currentCount++;
    }
}

la direttiva inject inietta il logger , dentro il metodo IncrementCount viene aggiungiamo la riga             logger.LogInformation(“incremento di 1”);

Abbiamo 6 livelli diversi di Logging (per il logging sotto Microsoft.Extensions.Logging ) qui li troviamo descritti https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-8.0

E' possibile usare altri provider di logging ,ad esempio Serilog , qui come utilizzarlo in Blazor .

                                                                        Il binding

    Il binding dei dati nei componenti può essere di due tipi :
         1)One way binding
         2)Two way binding

      1)Un esempio di one way binding è il componente Counter.razor (vedi sopra) ,il metodo IncrementCount aumenta tramite l'operatore ++ il valore di 1 e possiamo vedere aggiornato il valore di @currentCount

      2) quello che segue è un esempio di two way binding in cui abbiamo dei campi di input che vengono valorizzati dall'utente ed un risultato finale che viene “calcolato” dall'applicazione :

creiamo un componente BindTwoWay.razor con il seguente codice contenuto :

@page "/BindTwoWay"

<h3>Bind two ways </h3>
Titolo del libro: <input type="text" @bind="TitoloLibro"/>
<br />
Letto tutto :
<input type="checkbox" @bind="LettoTutto" />
<br />
Autore :
<input type="text" @bind="Autore" />
<br />
<button @onclick="Salva">Salva</button>
<p>@Messaggio</p>


@code {
    public string? TitoloLibro { get; set; }
    public bool LettoTutto { get; set; }    
    public string? Autore { get; set; }
    public string? Messaggio { get; set; }
    private void Salva()
    {
        Messaggio = TitoloLibro + ", letto tutto: " + (LettoTutto ? "sì" : "no") + ", Autore:" + Autore;
        TitoloLibro = null;
        Autore = null;
        LettoTutto = false;
    }
}

la prima riga contiene la direttiva @page ovvero per chiamare questo componente dovremmo chiamare /BindTwoWay , alla direttiva @bind vengono assegnate tre variabili ovvero TitoloLibro , LettoTutto , Autore ,queste tre variabili sono dichiarate nell'area @code , grazie a questa dichiarazione verrà assegnato alle variabili il valore dichiarato nei campi di input ,il metodo Salva viene chiamato quando l'utente clicca sul bottone in cui è assegnato ad @onclick il valore Salva , questo metodo compone un messaggio utilizzando i valori che arrivano dall'inputbox ed inoltre “svuota” i campi di input mettendoli a null ed a false il valore relativo al checkbox il quale viene quindi deselezionato qualora lo fosse ,compiliamo il form chiamando 


compiliamo i campi :

clicchiamo su Salva ed otteniamo :


La navigazione in un sito Blazor

Per navigare programmaticamente un sito possiamo utilizzare il componente NavigationManager ,qui la documentazione, segue un breve esempio , modifichiamo la pagina Index così :

@page "/"
@inject NavigationManager navman

<PageTitle>Index</PageTitle>
<h1>Hello from index page!</h1>
<button name="btNav" @onclick="GoTo">Naviga a counter</button>
<br />
<br />
<button name="btNav1" @onclick="UriAttuale">UriAttuale</button>
<button name="btNav2" @onclick="GoToUri">Navigare all'uri nel textbox</button>
<input name="uribox" type="text" @bind="UriValue" />
@code {
    public string? UriValue { get; set; }
    private void GoTo()
        {
            navman.NavigateTo("/counter");
        }
    private void UriAttuale()
    {
        UriValue=navman.Uri;
    }
    private void GoToUri()
    {
        try {
            navman.NavigateTo(UriValue);
        } catch(Exception ex)
        {
            //qui si potrebbe loggare l'errore :-)         
            UriValue = ex.Message.ToString();
        }
    }
}

notiamo l'uso della direttiva inject utilizzando la quale possiamo utilizzare un oggetto di nome navman e di tipo NavigationManager ,quest'oggetto fornisce il metodo NavigateTo che ci permette di navigare verso una pagina programmaticamente,NavigationManager contiene anche la proprietà Uri che restituisce l'indirizzo attuale del browser ,

il metodo UriAttuale viene invocato a seguito del click sul bottone btNav1 , il metodo assegna alla variabile UriValue il valore della proprietà Uri e poiché la variabile è legata tramite direttiva bind all'input box “uribox” il box risulta aggiornato con il valore impostato nell'assegnazione

UriValue=navman.Uri;

il metodo GoToUri viene chiamato quando si effettua il click sul bottone di nome btNav2 , il metodo naviga all'indirizzo contenuto in “uribox” ovvero ad UriValue ,nel caso si chiami GoToUri quando UriValue è nullo il metodo NavigateTo genera un'eccezione che viene catturata e valorizza l'”uribox”.Ripercorriamo il "flusso del programma" :


clicchiamo sul bottone “Naviga a counter” ed otteniamo :

torniamo alla index e clicchiamo sul bottone “UriAttuale” :

                                                            clicca sull'immagine per ingrandirla

clicchiamo su F5 per fare il refresh della pagina , inseriamo /fetchdata del text box

clicchiamo sul bottone “Navigare all'uri nel textbox” ed otteniamo :


clicchiamo su "Navigare all'uri del textbox" con il textbox vuoto:

l'intero testo d'errore è “Value cannot be null. (Parameter 'uri')” ,l'errore si riferisce al parametro passato al metodo NavigateTo di NavigationManager che non può essere nullo.

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...