ASP.NET Core Blazor con Entity Framework Core (EF Core)
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Questo articolo illustra come usare Entity Framework Core (EF Core) nelle app lato Blazor server.
Il lato Blazor server è un framework di app con stato. L'app mantiene una connessione continua al server e lo stato dell'utente viene mantenuto nella memoria del server in un circuito. Un esempio di stato utente è costituito dai dati contenuti nelle istanze del servizio di inserimento delle dipendenze che hanno come ambito il circuito. Il modello di applicazione univoco che Blazor fornisce richiede un approccio speciale per l'uso di Entity Framework Core.
Nota
Questo articolo riguarda le EF Core app sul lato Blazor server. Blazor WebAssembly le app vengono eseguite in una sandbox WebAssembly che impedisce la maggior parte delle connessioni di database dirette. L'esecuzione EF Core in Blazor WebAssembly non rientra nell'ambito di questo articolo.
Queste linee guida si applicano ai componenti che adottano il rendering lato server interattivo (SSR interattivo) in un oggetto Blazor Web App.
Queste indicazioni si applicano al Server
progetto di una soluzione ospitata Blazor WebAssembly o di un'app Blazor Server .
Flusso di autenticazione sicuro necessario per le app di produzione
Questo articolo riguarda l'uso di un database locale che non richiede l'autenticazione utente. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app di test e produzione Blazor distribuite, vedere gli articoli nel Blazornodo Sicurezza e Identity distribuzione.
Per i servizi di Microsoft Azure, è consigliabile usare le identità gestite. Le identità gestite eseguono l'autenticazione sicura ai servizi di Azure senza archiviare le credenziali nel codice dell'app. Per ulteriori informazioni, vedi le seguenti risorse:
- Cosa sono le identità gestite per le risorse di Azure? (Documentazione di Microsoft Entra)
- Documentazione dei servizi di Azure
Esercitazione sulla creazione di un'app Blazor di database di film
Per un'esperienza di esercitazione sulla creazione di un'app che utilizza EF Core per le operazioni di database, consultare Creare un'app di database di film Blazor (Panoramica). L'esercitazione illustra come creare un oggetto Blazor Web App in grado di visualizzare e gestire film in un database di film.
Accesso al database
EF Coresi basa su come DbContext mezzo per configurare l'accesso al database e fungere da unità di lavoro. EF Core fornisce l'estensione AddDbContext per le app ASP.NET Core che registra il contesto come servizio con ambito. Nelle app lato Blazor server, le registrazioni del servizio con ambito possono essere problematiche perché l'istanza viene condivisa tra i componenti all'interno del circuito dell'utente. DbContext non è thread-safe e non è progettato per l'uso simultaneo. Le durate esistenti non sono appropriate per questi motivi:
- Singleton condivide lo stato in tutti gli utenti dell'app e porta a un uso simultaneo inappropriato.
- L'ambito (impostazione predefinita) pone un problema simile tra i componenti per lo stesso utente.
- I risultati temporanei in una nuova istanza per ogni richiesta, ma poiché i componenti possono essere di lunga durata, ciò comporta un contesto di durata superiore a quello previsto.
Le raccomandazioni seguenti sono progettate per offrire un approccio coerente all'uso EF Core nelle app lato Blazor server.
Prendere in considerazione l'uso di un contesto per ogni operazione. Il contesto è progettato per la creazione di istanze a sovraccarico rapido e basso:
using var context = new ProductsDatabaseContext(); return await context.Products.ToListAsync();
Usare un flag per impedire più operazioni simultanee:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Posizionare le operazioni di database dopo la riga
Loading = true;
nel bloccotry
.Thread safety non è un problema, quindi la logica di caricamento non richiede il blocco dei record del database. La logica di caricamento viene usata per disabilitare i controlli dell'interfaccia utente in modo che gli utenti non selezionino inavvertitamente pulsanti o aggiornino i campi durante il recupero dei dati.
Se è possibile che più thread possano accedere allo stesso blocco di codice, inserire una factory e creare una nuova istanza per ogni operazione. In caso contrario, l'inserimento e l'uso del contesto sono in genere sufficienti.
Per le operazioni di lunga durata che sfruttano EF Coreil rilevamento delle modifiche o il controllo della concorrenza, definire l'ambito del contesto per la durata del componente.
Nuove DbContext
istanze
Il modo più rapido per creare una nuova DbContext istanza consiste nell'usare new
per creare una nuova istanza. Esistono tuttavia scenari che richiedono la risoluzione di dipendenze aggiuntive:
- Uso
DbContextOptions
di per configurare il contesto. - Uso di un stringa di connessione per DbContext, ad esempio quando si usa Identity di ASP.NET Core. Per altre informazioni, vedere Multi-tenancy (EF Coredocumentazione).
Avviso
Non archiviare segreti dell'app, stringa di connessione, credenziali, password, numeri di identificazione personale (PIN), codice C#/.NET privato o chiavi/token privati nel codice lato client, che è sempre non sicuro. Negli ambienti di test/gestione temporanea e produzione, il codice lato Blazor server e le API Web devono usare flussi di autenticazione sicuri che evitano di mantenere le credenziali all'interno del codice del progetto o dei file di configurazione. Al di fuori dei test di sviluppo locali, è consigliabile evitare l'uso di variabili di ambiente per archiviare i dati sensibili, perché le variabili di ambiente non sono l'approccio più sicuro. Per i test di sviluppo locali, lo strumento Secret Manager è consigliato per proteggere i dati sensibili. Per altre informazioni, vedere Gestire in modo sicuro dati e credenziali sensibili.
L'approccio consigliato per creare un nuovo DbContext con le dipendenze consiste nell'usare una factory. EF Core 5.0 o versione successiva offre una factory predefinita per la creazione di nuovi contesti.
Nelle versioni di .NET precedenti alla 5.0 usare il DbContextFactory
seguente:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace BlazorServerDbContextExample.Data
{
public class DbContextFactory<TContext>
: IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public DbContextFactory(IServiceProvider provider)
{
this.provider = provider ?? throw new ArgumentNullException(
$"{nameof(provider)}: You must configure an instance of " +
"IServiceProvider");
}
public TContext CreateDbContext() =>
ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
Nella factory precedente:
- ActivatorUtilities.CreateInstance soddisfa eventuali dipendenze tramite il provider di servizi.
- IDbContextFactory<TContext> è disponibile in EF Core ASP.NET Core 5.0 o versione successiva, quindi l'interfaccia precedente è necessaria solo per ASP.NET Core 3.x.
L'esempio seguente configura SQLite e abilita la registrazione dei dati in un'app che gestisce i contatti. Il codice usa un metodo di estensione (AddDbContextFactory) per configurare la factory di database per l'inserimento delle dipendenze e fornire le opzioni predefinite:
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
La factory viene iniettata nei componenti per creare nuove istanze di DbContext.
Definire l'ambito di un contesto di database in un metodo componente
La factory viene inserita nel componente:
@inject IDbContextFactory<ContactContext> DbFactory
Creare un DbContext per un metodo usando la factory (DbFactory
):
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
if (context.Contacts is not null)
{
var contact = await context.Contacts.FirstAsync(...);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
}
Definire l'ambito di un contesto di database in base alla durata del componente
È possibile creare un oggetto DbContext esistente per la durata di un componente. In questo modo è possibile usarlo come unità di lavoro e sfruttare le funzionalità predefinite, ad esempio il rilevamento delle modifiche e la risoluzione della concorrenza.
Implementare IDisposable e inserire la factory nel componente:
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Definire una proprietà per il DbContext:
private ContactContext? Context { get; set; }
OnInitializedAsync
viene sovrascritto per creare il DbContext.
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
}
Il DbContext viene eliminato quando il componente viene eliminato:
public void Dispose() => Context?.Dispose();
Abilitare la registrazione dei dati sensibili
EnableSensitiveDataLogging include i dati dell'applicazione nei messaggi di eccezione e nella registrazione del framework. I dati registrati possono includere i valori assegnati alle proprietà delle istanze di entità e i valori dei parametri per i comandi inviati al database. La registrazione dei dati con EnableSensitiveDataLogging è un rischio per la sicurezza, in quanto può esporre password e altre informazioni personali (PII) quando registra le istruzioni SQL eseguite sul database.
È consigliabile abilitare EnableSensitiveDataLogging solo per lo sviluppo e il test:
#if DEBUG
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
#else
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
#endif