Per ultimare la nostra prima CRUD ci siamo concentrati sul front-end, utilizzando dei dati in memoria per verificare il corretto funzionamento delle operazioni. In un’applicazione reale i dati sono gestiti dal back-end, che può ad esempio leggerli e scriverli su un database. Vediamo come sia semplice in Blazor integrare un back-end .NET, sia nel caso di Blazor Server che con Blazor WebAssembly.

Blazor Server vs Blazor WebAssembly vs JavaScript

L’integrazione con il back-end è uno dei fattori discriminanti tra le due versioni di Blazor, dato che nella versione server ci troviamo già sul back-end, o quanto meno in un ambiente relativamente sicuro dove abbiamo accesso diretto a risorse che sul front-end non abbiamo. In Blazor WebAssembly invece ci troviamo sul client dell’utente, nel browser in particolare, con la limitazione di non poter accedere ne alla macchina dell’utente, ne direttamente alle risorse server.

Se decidiamo ad esempio di utilizzar Entity Framework per l’accesso ai dati, in Blazor Server potremmo iniettare il DbContext direttamente nella pagina e utilizzarlo senza limitazioni. Nel caso invece di Blazor WebAssembly, restando sempre nel mondo .NET, avremmo bisogno di un Controller ASP.NET Core, che ci esponga delle API REST da poter invocare dal browser, proprio come faremmo con qualsiasi framework di front-end. A quel punto nel controller possiamo utilizzare il DbContext per implementare le operazioni sui dati.

Rispetto però a framework come Angular, Blazor, grazie a .NET Core, ci da notevoli vantaggi. Possiamo ad esempio condividere con il back-end, utilizzando una DLL .NET Standard 2.1, gli oggetti di scambio che sono parte del contratto di comunicazione. Questo significa che se uno di questi oggetti cambia, sia il back-end che il front-end sono allineati, e il compilatore può indicarci gli errori dovuti al cambiamento. Quante volte ci sarà capitato di aggiungere o togliere una proprietà o modificarne il nome: adesso possiamo non riporare le modifiche su entrambi i lati.

Inoltre, come abbiamo visto nell’articolo prededente, la validazione in Blazor è basata su Data Annotations, che è lo stesso meccanismo su cui è basata la validazione per ASP.NET Core. Questo significa che con un solo oggetto riusciamo ad implementare gratis la validazione sia lato client che lato server e lasciare che siano i due framework a farla per noi.

Ultima, ma non per importanza, è la disponibilità di un HTTPClient già pronto che si occuperà in automatico della serializzazione da e verso JSON, esponendoci i metodi asincroni per tutti i verbi HTTP, che possiamo utilizzare con la consuenta notazione async/await.

Aggiungiamo la persistenza dei dati a Blazor Server

Per ottimizzare il lavoro che faremo sulle due versioni di Blazor, andiamo ad aggiungere al nostro progetto una libreria che si occuperà della persistenza dei dati, alla quale aggiungeremo un DbContext EntityFramework e una classe che rappresenterà l’entità da persistere sul database. Dal nostro terminale possiamo utilizzare i seguenti comandi:

dotnet new classlib -o event-manager-data
dotnet sln add event-manager-data/event-manager-data.csproj 
cd event-manager-data
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package System.ComponentModel.Annotations

In alternativa possiamo usare il wizard di Visual Studio per aggiungere un progetto libreria di classi alla solution, e da NuGet aggiungere EntityFramework core e la librerie delle DataAnnotations. Eliminiamo il file Class1.cs creato dal template e creiamo invece una classe DatiEvento:

public class DatiEvento
{
    public int Id { get; set; }

    [Required]
    public string Nome { get; set; }

    [Required]
    [StringLength(50)]
    public string Localita { get; set; }
    public DateTime Data { get; set; }
    public string Descrizione { get; set; }
    public string Note { get; set; }
}

In questo caso semplice la classe è identica alla classe Evento già realizzata precedentemente, in casi reali molto probabilmente le due entità avranno proprietà differenti, che non riguardano il front-end o un particolare caso d’uso. Possiamo adesso creare il nostro EventManagerDbContext a cui aggiungiamo il DbSet degli eventi:

public class EventManagerDbContext : DbContext
{
    public DbSet<DatiEvento> Eventi { get; set; }
    
    public EventManagerDbContext(DbContextOptions options)
        : base(options) { }
}

Aggiungiamo al progetto Blazor Server la libreria appena creata e la dipendenza da EntityFramework Core e SqlLite:

cd ../event-manager-server/
dotnet add reference ../event-manager-data/event-manager-data.csproj 
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Aggiungiamo la configurazione di EntityFramework alla class Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    
    services.AddDbContext<EventManagerDbContext>(
        opt => opt.UseSqlite("DataSource=eventmanager.db"));
}

A questo punto non ci resta che creare la migration e applicarla al nostro database, per la quale ci serve il pacchetto Microsoft.EntityFrameworkCore.Design:

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet ef migrations add DatiEvento --project ../event-manager-data
dotnet ef database update

L’approccio migliore adesso sarebbe quello di creare una classe di servizio che ci nasconda le chiamate a Entity Framework, ma affronteremo nel dettaglio la dependency injection e le best practices in un prossimo articolo. In questo caso, ai fini del funzionamento, iniettiamo direttamente il DbContext nella pagina e utilizziamolo per la gestione dei dati. Per comodità aggiungiamo al file _Imports.razor la libreria dati che abbiamo creato (@using event_manager_data), mentre nella pagina Eventi.razor, iniettiamo il DbContext:

@page "/eventi"
@inject EventManagerDbContext context;

I dati finti non ci servono più, andiamo invece a crearci un metodo CaricaEventi(), che richiamiamo appena la pagina è stata inizializzata, utilizzando l’override del metodo OnInitialized():

protected override void OnInitialized()
{
    CaricaEventi();
}

private void CaricaEventi()
{
    this.ListaEventi = this.context.Eventi
        .Select(x => new ElementoListaEventi()
        {
            Id = x.Id,
            Nome = x.Nome,
            Localita = x.Localita,
            Data = x.Data
        }).ToList();
}

A questo punto non ci resta che reimplementare i metodi della pagina usando Linq per interrogare i dati:

public void ModificaEvento(ElementoListaEventi evento)
{
    this.EventoCorrente = this.context.Eventi
        .Where(x => x.Id == evento.Id)
        .Select(x => new Evento()
        {
            Id = x.Id,
            Nome = x.Nome,
            Localita = x.Localita,
            Data = x.Data,
            Descrizione = x.Descrizione,
            Note = x.Note
        }).SingleOrDefault();
}

public void SalvaEvento(Evento evento)
{
    if(evento.Id == 0)
    {
        this.context.Eventi.Add(new DatiEvento()
        {
            Nome = evento.Nome,
            Localita = evento.Localita,
            Data = evento.Data,
            Descrizione = evento.Descrizione,
            Note = evento.Note
        });
    }
    else
    {
        var eventoDaModificare = this.context.Eventi.Single(x => x.Id == evento.Id);
        eventoDaModificare.Nome = evento.Nome;
        eventoDaModificare.Localita = evento.Localita;
        eventoDaModificare.Data = evento.Data;
        eventoDaModificare.Descrizione = evento.Descrizione;
        eventoDaModificare.Note = evento.Note;
    }
    this.context.SaveChanges();
    this.CaricaEventi();
    this.EventoCorrente = null;
}

public void AnnullaOperazione()
{
    this.EventoCorrente = null;
}

public void EliminaEvento(ElementoListaEventi evento) 
{
    var eventoDaEliminare = this.context.Eventi.Single(x => x.Id == evento.Id);
    this.context.Eventi.Remove(eventoDaEliminare);
    this.context.SaveChanges();
    this.CaricaEventi();
}

Ed ecco il nostro risultato:

Blazor Server integrazione persistenza dati

Potete consultare il codice sorgente qui, nella branch 06-integrazione-backend.

Conclusioni

Per oggi ci fermiamo qui. Abbiamo visto come con Blazor Server l’integrazione della persistenza dei dati sia veramente semplice, dato che ci troviamo già sul back-end e possiamo invocare direttamente Entity Framework. Nel prossimo articolo vedremo come aggiungere la stessa funzionalità alla versione WebAssembly, andando a creare una apposita API e utilizzando il client HTTP messo a disposizione dal framework.