ASP.NET Core Blazor met Entity Framework Core (EF Core)
Notitie
Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.
Waarschuwing
Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.
Belangrijk
Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.
Zie de .NET 9-versie van dit artikelvoor de huidige release.
In dit artikel wordt uitgelegd hoe u Entity Framework Core (EF Core) gebruikt in Blazor-apps aan de serverzijde.
Blazor aan de serverzijde is een stateful app-framework. De app onderhoudt een doorlopende verbinding met de server en de status van de gebruiker wordt bewaard in het geheugen van de server in een circuit. Een voorbeeld van de gebruikersstatus zijn gegevens die zijn opgeslagen in afhankelijkheidsinjectie (DI) service-exemplaren die binnen het bereik van het circuit vallen. Het unieke toepassingsmodel dat Blazor biedt, vereist een speciale benadering voor het gebruik van Entity Framework Core.
Notitie
In dit artikel wordt EF Core in Blazor-apps aan de serverzijde behandeld. Blazor WebAssembly apps worden uitgevoerd in een WebAssembly-sandbox die de meeste directe databaseverbindingen voorkomt. Het uitvoeren van EF Core in Blazor WebAssembly valt buiten het bereik van dit artikel.
Deze richtlijnen zijn van toepassing op onderdelen die interactieve server-side rendering (interactieve SSR) gebruiken in een Blazor Web App.
Deze richtlijnen zijn van toepassing op het Server
project van een gehoste Blazor WebAssembly-oplossing of een Blazor Server-app.
Beveiligde verificatiestroom vereist voor productie-apps
Dit artikel heeft betrekking op het gebruik van een lokale database waarvoor geen gebruikersverificatie is vereist. Productie-apps moeten gebruikmaken van de veiligste verificatiestroom die beschikbaar is. Zie de artikelen in de BlazorIdentity nodevoor meer informatie over verificatie voor geïmplementeerde test- en productie--apps.
Voor Microsoft Azure-services raden we u aan beheerde identiteitente gebruiken. Beheerde identiteiten worden veilig geverifieerd bij Azure-services zonder referenties op te slaan in app-code. Zie de volgende bronnen voor meer informatie:
- Wat zijn beheerde identiteiten voor Azure-resources? (Documentatie voor Microsoft Entra)
- Documentatie voor Azure-services
Zelfstudie over het bouwen van een Blazor filmdatabase-app
Zie Een Blazor filmdatabase-app maken (overzicht) voor een zelfstudie over het bouwen van een app die gebruikmaakt van EF Core voor databasebewerkingen. De handleiding laat zien hoe je een Blazor Web App maakt waarmee films in een filmdatabase kunnen worden getoond en beheerd.
Databasetoegang
EF Core is afhankelijk van een DbContext als de manier om databasetoegang te configureren en treedt op als een werkeenheid. EF Core biedt de AddDbContext-extensie voor ASP.NET Core-apps die de context registreren als een scoped-service. In server-side Blazor-apps blijken scoped service-registraties problematisch te zijn omdat de instantie wordt gedeeld tussen componenten binnen het circuit van de gebruiker. DbContext is niet threadveilig en is niet ontworpen voor gelijktijdig gebruik. De bestaande levensduur is om deze redenen ongepast:
- Singleton deelt de toestand tussen alle gebruikers van de app en leidt tot onjuist gelijktijdig gebruik.
- Scoped (de standaardinstelling) vormt een vergelijkbaar probleem tussen componenten voor dezelfde gebruiker.
- tijdelijke resulteert in een nieuw exemplaar per aanvraag; maar omdat onderdelen lang kunnen duren, resulteert dit in een context die langer duurt dan mogelijk is bedoeld.
De volgende aanbevelingen zijn ontworpen om een consistente benadering te bieden voor het gebruik van EF Core in Blazor-apps aan de serverzijde.
Overweeg om één context per bewerking te gebruiken. De context is ontworpen voor snelle, lage overhead-instantiëring:
using var context = new ProductsDatabaseContext(); return await context.Products.ToListAsync();
Gebruik een vlag om meerdere gelijktijdige bewerkingen te voorkomen:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Plaats databasebewerkingen na de
Loading = true;
regel in hettry
blok.Threadveiligheid is geen probleem, dus het laden van logica vereist geen databaserecords vergrendelen. De laadlogica wordt gebruikt om UI-besturingselementen uit te schakelen, zodat gebruikers niet per ongeluk knoppen selecteren of velden bijwerken terwijl gegevens worden opgehaald.
Als er een kans is dat meerdere threads toegang hebben tot hetzelfde codeblok, een factory- injecteren en per bewerking een nieuw exemplaar maken. Anders is het injecteren en gebruiken van de context in principe voldoende.
Voor bewerkingen met een langere levensduur die gebruikmaken van EF Corewijzigingen bijhouden of gelijktijdigheidsbeheer, de context beperken tot de levensduur van het onderdeel.
Nieuwe DbContext
-exemplaren
De snelste manier om een nieuw DbContext exemplaar te maken, is door new
te gebruiken om een nieuw exemplaar te maken. Er zijn echter scenario's waarvoor aanvullende afhankelijkheden moeten worden opgelost:
- Gebruik
DbContextOptions
om de context te configureren. - Een verbindingsreeks per DbContextgebruiken, bijvoorbeeld wanneer u ASP.NET Core-Identity-modelgebruikt. Zie multitenancy (EF Core documentatie)voor meer informatie.
Waarschuwing
Sla geen app-geheimen, verbindingsreeksen, referenties, wachtwoorden, persoonlijke identificatienummers (PINCODE's), persoonlijke C#/.NET-code of persoonlijke sleutels/tokens op in code aan de clientzijde. Dit is altijd onveilig. In test- en stagingomgevingen en productieomgevingen moeten aan de serverzijde Blazor-code en web-API's veilige authenticatiestromen gebruiken die vermijden dat referenties in projectcode of configuratiebestanden worden opgeslagen. Buiten het testen van lokale ontwikkeling raden we u aan het gebruik van omgevingsvariabelen voor het opslaan van gevoelige gegevens te vermijden, omdat omgevingsvariabelen niet de veiligste benadering zijn. Voor het testen van lokale ontwikkeling wordt het hulpprogramma Secret Manager aanbevolen voor het beveiligen van gevoelige gegevens. Zie Gevoelige gegevens en referenties veilig onderhoudenvoor meer informatie.
De aanbevolen methode om een nieuwe DbContext met afhankelijkheden te maken, is door gebruik te maken van een factory. EF Core 5.0 of hoger biedt een ingebouwde factory voor het creëren van nieuwe contexten.
Gebruik in versies van .NET vóór 5.0 de volgende 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);
}
}
In de voorgaande fabriek:
- ActivatorUtilities.CreateInstance voldoet aan eventuele afhankelijkheden via de serviceprovider.
- IDbContextFactory<TContext> is beschikbaar in EF Core ASP.NET Core 5.0 of hoger, dus de voorgaande interface is alleen vereist voor ASP.NET Core 3.x.
In het volgende voorbeeld wordt SQLite- geconfigureerd en wordt logboekregistratie van gegevens ingeschakeld in een app waarmee contactpersonen worden beheerd. De code maakt gebruik van een extensiemethode (AddDbContextFactory) om de databasefactory voor DI te configureren en standaardopties te bieden:
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"));
De factory wordt geïnjecteerd in componenten om nieuwe DbContext exemplaren te creëren.
Een databasecontext beperken tot een componentmethode
De fabriek wordt geïntegreerd in het onderdeel.
@inject IDbContextFactory<ContactContext> DbFactory
Maak een DbContext voor een methode met behulp van de 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();
}
}
}
Een databasecontext beperken tot de levensduur van het onderdeel
U kunt een DbContext maken die bestaat voor de levensduur van een onderdeel. Hierdoor kunt u deze gebruiken als een werkeenheid en profiteren van ingebouwde functies, zoals het bijhouden van wijzigingen en gelijktijdigheidsresolutie.
Implementeer IDisposable en injecteer de fabriek in het onderdeel:
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Stel een eigenschap voor de DbContextin:
private ContactContext? Context { get; set; }
OnInitializedAsync
wordt overschreven om de DbContextte maken:
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
}
De DbContext wordt verwijderd wanneer het onderdeel wordt verwijderd:
public void Dispose() => Context?.Dispose();
Logboekregistratie van gevoelige gegevens inschakelen
EnableSensitiveDataLogging bevat toepassingsgegevens in uitzonderingsberichten en frameworklogging. De vastgelegde gegevens kunnen de waarden bevatten die zijn toegewezen aan eigenschappen van entiteitsexemplaren en parameterwaarden voor opdrachten die naar de database worden verzonden. Logboekregistratiegegevens met EnableSensitiveDataLogging is een beveiligingsrisico, omdat wachtwoorden en andere PII- (Personally Identifiable Information) kunnen worden weergegeven wanneer SQL-instructies op de database worden uitgevoerd.
U wordt aangeraden alleen EnableSensitiveDataLogging in te schakelen voor ontwikkeling en testen:
#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