Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 10 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 10 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 circuit. Un esempio di stato utente è costituito dai dati contenuti nelle istanze di servizio di dependency injection (DI) 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 Blazor lato 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 Blazor Web App.
Queste indicazioni si applicano al progetto Server 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 BlazorSicurezza e Identity nodo.
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
Tutorial per creare un'app Blazor 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 Core si basa su un DbContext come 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 scoped. 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 tra tutti gli utenti dell'app e porta a un uso simultaneo inappropriato.
- Scoped (impostazione predefinita) pone un problema simile tra i componenti per lo stesso utente.
- Transient comporta 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 Blazor lato server.
Prendere in considerazione l'uso di un contesto per ogni operazione. Il contesto è progettato per la creazione rapida di istanze con basso overhead:
using var context = new ProductsDatabaseContext(); return await context.Products.ToListAsync();Usare un flag per impedire più operazioni concorrenti:
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, iniettare una factory e creare una nuova istanza per ogni operazione. In caso contrario, iniettare e usare il contesto sono in genere sufficienti.
Per le operazioni di lunga durata che sfruttano il rilevamento delle modifiche di EF Core o il controllo della concorrenza di EF Core, 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:
- Utilizzo di
DbContextOptionsper configurare il contesto. - Uso di una stringa di connessione per DbContext, ad esempio quando si usa il modello 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/staging 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, il Secret Manager tool è consigliato per proteggere i dati sensibili. Per altre informazioni, vedere Gestire in modo sicuro dati sensibili e credenziali.
L'approccio consigliato per creare un nuovo DbContext con le dipendenze consiste nell'usare una factory. EF Core in .NET 5 o versione successiva offre una factory predefinita per la creazione di nuovi contesti.
Nelle versioni di .NET precedenti a .NET 5 usare quanto segue DbContextFactory:
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 fabbrica precedente:
- ActivatorUtilities.CreateInstance soddisfa eventuali dipendenze tramite il provider di servizi.
- IDbContextFactory<TContext> è disponibile in EF Core .NET 5 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 la DI 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
Potresti voler 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 dei conflitti di 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 locali:
if (builder.Environment.IsDevelopment())
{
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"));
}