Delen via


ASP.NET Kernafhankelijkheidsinjectie Blazor

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor 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 artikel voor de huidige release.

Door Rainer Stropek en Mike Rousos

In dit artikel wordt uitgelegd hoe Blazor apps services in onderdelen kunnen injecteren.

Afhankelijkheidsinjectie (DI) is een techniek voor toegang tot services die zijn geconfigureerd op een centrale locatie:

  • Framework-geregistreerde services kunnen rechtstreeks in Razor onderdelen worden geïnjecteerd.
  • Blazor apps definiëren en registreren aangepaste services en maken ze beschikbaar in de hele app via DI.

Opmerking

Het is raadzaam om afhankelijkheidsinjectie in ASP.NET Core te lezen voordat u dit onderwerp leest.

Standaardservices

De services die in de volgende tabel worden weergegeven, worden vaak gebruikt in Blazor apps.

Dienst Levensduur Beschrijving
HttpClient Afgebakend

Biedt methoden voor het verzenden van HTTP-aanvragen en het ontvangen van HTTP-antwoorden van een resource die is geïdentificeerd door een URI.

Aan de clientzijde registreert de app een exemplaar van HttpClient in het Program bestand en gebruikt de browser voor het afhandelen van HTTP-verkeer op de achtergrond.

Aan de serverzijde is een HttpClient service niet standaard geconfigureerd. Geef in de server-side code een HttpClient.

Zie Een web-API aanroepen vanuit een ASP.NET Core-app Blazorvoor meer informatie.

Een HttpClient is geregistreerd als een scoped service en niet als een singleton-service. Zie de sectie Levensduur van de service voor meer informatie.

IJSRuntime

Clientzijde: Singleton

Serverzijde: Scoped

Het Blazor-framework registreert IJSRuntime in de servicecontainer van de app.

Vertegenwoordigt een exemplaar van een JavaScript-runtime waarbij JavaScript-aanroepen worden verzonden. Zie JavaScript-functies aanroepen vanuit .NET-methoden in ASP.NET Core Blazorvoor meer informatie.

Wanneer u de service wilt injecteren in een singleton-service op de server, moet u een van de volgende benaderingen volgen:

  • Wijzig de serviceregistratie naar 'als scoped' zodat deze overeenkomt met de registratie van IJSRuntime, wat geschikt is als de service te maken heeft met een gebruikersspecifieke status.
  • Geef de IJSRuntime door aan de implementatie van de singleton-service als een argument van zijn methodeaanroepen in plaats van deze in de singleton te injecteren.
NavigationManager

Clientzijde: Singleton

Serverzijde: Scoped

Het Blazor-framework registreert NavigationManager in de servicecontainer van de app.

Bevat helpers voor het werken met URI's en navigatiestatus. Zie URI- en navigatiestatushelpers voor meer informatie.

Aanvullende services die door het Blazor framework zijn geregistreerd, worden beschreven in de documentatie waarin ze worden gebruikt om functies te beschrijven Blazor , zoals configuratie en logboekregistratie.

Een aangepaste serviceprovider biedt niet automatisch de standaardservices die in de tabel worden vermeld. Als u een aangepaste serviceprovider gebruikt en een van de services in de tabel nodig hebt, voegt u de vereiste services toe aan de nieuwe serviceprovider.

Services aan clientzijde toevoegen

Configureer services voor de serviceverzameling van de app in het Program bestand. In het volgende voorbeeld is de ExampleDependency implementatie geregistreerd voor IExampleDependency:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...

await builder.Build().RunAsync();

Nadat de host is gebouwd, zijn services beschikbaar vanuit het hoofd-DI-bereik voordat onderdelen worden weergegeven. Dit kan handig zijn voor het uitvoeren van initialisatielogica voordat inhoud wordt weergegeven:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...

var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync();

await host.RunAsync();

De host biedt een centraal configuratie-exemplaar voor de app. Op basis van het voorgaande voorbeeld wordt de URL van de weerservice doorgegeven vanuit een standaardconfiguratiebron (bijvoorbeeld appsettings.json) naar InitializeWeatherAsync:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...

var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync(
    host.Configuration["WeatherServiceUrl"]);

await host.RunAsync();

Services aan serverzijde toevoegen

Nadat u een nieuwe app hebt gemaakt, bekijkt u een deel van het Program bestand:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();

De builder variabele vertegenwoordigt een WebApplicationBuilder met een IServiceCollection, een lijst met servicedescriptorobjecten . Services worden toegevoegd door servicedescriptoren aan de serviceverzameling te verstrekken. In het volgende voorbeeld ziet u het concept met de IDataAccess interface en de concrete implementatie DataAccess:

builder.Services.AddSingleton<IDataAccess, DataAccess>();

Nadat u een nieuwe app hebt gemaakt, bekijkt u de Startup.ConfigureServices methode in Startup.cs:

using Microsoft.Extensions.DependencyInjection;

...

public void ConfigureServices(IServiceCollection services)
{
    ...
}

De ConfigureServices-methode krijgt een IServiceCollection doorgegeven, wat een lijst is van servicedescriptor-objecten. Services worden toegevoegd in de ConfigureServices methode door servicebeschrijvingen aan de servicecollectie te verstrekken. In het volgende voorbeeld ziet u het concept met de IDataAccess interface en de concrete implementatie DataAccess:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataAccess, DataAccess>();
}

Algemene services registreren

Als een of meer algemene services aan client- en serverzijde zijn vereist, kunt u de algemene serviceregistraties in een methode aan de clientzijde plaatsen en de methode aanroepen zodat de services in beide projecten worden geregistreerd.

Factor eerst algemene serviceregistraties in een afzonderlijke methode. Maak bijvoorbeeld een ConfigureCommonServices methode aan de clientzijde:

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

Voor het bestand aan de clientzijde Program roept u ConfigureCommonServices aan om de algemene services te registreren:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

In het serverzijdebestand Program, roep ConfigureCommonServices aan om de algemene services te registreren.

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Zie ASP.NET Core Blazor WebAssembly aanvullende beveiligingsscenario's voor een voorbeeld van deze benadering.

Services aan de clientzijde die mislukken tijdens het prerendering

Deze sectie is alleen van toepassing op WebAssembly-onderdelen in Blazor Web Apps.

Blazor Web Appnormaliter worden WebAssembly-componenten aan de clientkant vooraf gerenderd. Als een app wordt uitgevoerd met een vereiste service die alleen is geregistreerd in het .Client project, resulteert het uitvoeren van de app in een runtimefout die vergelijkbaar is met het volgende wanneer een onderdeel de vereiste service probeert te gebruiken tijdens het voorbereiden:

InvalidOperationException: Kan geen waarde opgeven voor {PROPERTY} voor het type {ASSEMBLY}}. Client.Pages. {COMPONENT NAME}'. Er is geen geregistreerde service van het type {SERVICE}.

Gebruik een van de volgende methoden om dit probleem op te lossen:

  • Registreer de service in het hoofdproject om deze beschikbaar te maken tijdens het voorbereiden van onderdelen.
  • Als prerendering niet vereist is voor het onderdeel, schakelt u prerendering uit door de richtlijnen in ASP.NET Core-rendermodi Blazorte volgen. Als u deze aanpak gebruikt, hoeft u de service niet te registreren in het hoofdproject.

Zie Services aan de clientzijde kunnen niet worden omgezet tijdens het prerendering voor meer informatie.

Levensduur van service

Services kunnen worden geconfigureerd met de levensduur die wordt weergegeven in de volgende tabel.

Levensduur Beschrijving
Scoped

Clientzijde heeft momenteel geen concept van DI-bereiken. Scoped-geregistreerde services gedragen zich als Singleton services.

Ontwikkeling aan de serverzijde ondersteunt de Scoped levensduur van HTTP-aanvragen, maar niet tussen SignalR verbindings- en circuitberichten tussen onderdelen die op de client worden geladen. Het Razor Pages-gedeelte of het MVC-gedeelte van de app behandelt de scoped services zoals gewoonlijk en maakt de services opnieuw aan bij elke HTTP-aanvraag wanneer je navigeert tussen pagina's of weergaven, of van een pagina of weergave naar een component. Scoped services worden niet gereconstrueerd bij het navigeren tussen onderdelen op de client, waarbij de communicatie met de server plaatsvindt via de SignalR verbinding van het circuit van de gebruiker, niet via HTTP-aanvragen. In de volgende onderdeelscenario's op de client worden scoped services gereconstrueerd omdat er een nieuw circuit wordt gemaakt voor de gebruiker:

  • De gebruiker sluit het venster van de browser. De gebruiker opent een nieuw venster en gaat terug naar de app.
  • De gebruiker sluit een tabblad van de app in een browservenster. De gebruiker opent een nieuw tabblad en gaat terug naar de app.
  • De gebruiker selecteert de knop voor opnieuw laden/vernieuwen van de browser.

Zie ASP.NET Core-statusbeheer Blazorvoor meer informatie over het behouden van de gebruikersstatus in apps aan de serverzijde.

Singleton DI maakt één exemplaar van de service. Alle onderdelen die een Singleton service vereisen, ontvangen hetzelfde exemplaar van de service.
Transient Wanneer een onderdeel een exemplaar van een Transient service van de servicecontainer verkrijgt, ontvangt het een nieuw exemplaar van de service.

Het DI-systeem is gebaseerd op het DI-systeem in ASP.NET Core. Zie Afhankelijkheidsinjectie in ASP.NET Corevoor meer informatie.

Een service in een onderdeel aanvragen

Voor het injecteren van services in onderdelen ondersteunt Blazorconstructorinjectie en eigenschapsinjectie.

Constructorinjectie

Nadat de services aan de serviceverzameling zijn toegevoegd, kunt u een of meer services in de componenten injecteren met behulp van constructorinjectie. In het volgende voorbeeld wordt de NavigationManager service geïnjecteerd.

ConstructorInjection.razor:

@page "/constructor-injection"

<button @onclick="HandleClick">
    Take me to the Counter component
</button>

ConstructorInjection.razor.cs:

using Microsoft.AspNetCore.Components;

public partial class ConstructorInjection(NavigationManager navigation)
{
    private void HandleClick()
    {
        navigation.NavigateTo("/counter");
    }
}

Eigenschapsinjectie

Nadat services aan de serviceverzameling zijn toegevoegd, injecteert u een of meer services in onderdelen met de @injectRazor richtlijn, die twee parameters heeft:

  • Type: Het type service dat moet worden geïnjecteerd.
  • Eigenschap: De naam van de eigenschap die de geïnjecteerde app-service ontvangt. De eigenschap vereist geen handmatige creatie. De compiler maakt de eigenschap.

Zie Afhankelijkheidsinjectie in weergaven in ASP.NET Core voor meer informatie.

Gebruik meerdere @inject instructies om verschillende services te injecteren.

In het volgende voorbeeld ziet u hoe u de @inject instructie gebruikt. De service die Services.NavigationManager wordt geïmplementeerd, wordt geïnjecteerd in de eigenschap Navigationvan het onderdeel. Let op hoe de code alleen gebruikmaakt van de NavigationManager abstractie.

PropertyInjection.razor:

@page "/property-injection"
@inject NavigationManager Navigation

<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
    Take me to the Counter component
</button>

Intern gebruikt de gegenereerde eigenschap (Navigation) het [Inject] kenmerk. Dit kenmerk wordt doorgaans niet rechtstreeks gebruikt. Als een basisklasse vereist is voor onderdelen en geïnjecteerde eigenschappen ook vereist zijn voor de basisklasse, voegt u het [Inject] kenmerk handmatig toe:

using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent
{
    [Inject]
    protected NavigationManager Navigation { get; set; } = default!;

    ...
}

Opmerking

Omdat geïnjecteerde services naar verwachting beschikbaar zijn, wordt de standaard letterlijke waarde met de operator null-forgiving (default!) toegewezen in .NET 6 of hoger. Zie Nullable reference types (NRT's) en .NET compiler null-state statische analyse voor meer informatie.

In onderdelen die zijn afgeleid van een basisklasse, is de @inject instructie niet vereist. De InjectAttribute basisklasse is voldoende. Voor het onderdeel is alleen de @inherits instructie vereist. In het volgende voorbeeld zijn alle geïnjecteerde services CustomComponentBase beschikbaar voor het Demo onderdeel:

@page "/demo"
@inherits CustomComponentBase

DI gebruiken in diensten

Voor complexe services zijn mogelijk extra services vereist. In het volgende voorbeeld DataAccess is de HttpClient standaardservice vereist. @inject (of het [Inject] kenmerk) is niet beschikbaar voor gebruik in services. In plaats daarvan moet constructorinjectie worden gebruikt. Vereiste services worden toegevoegd door parameters toe te voegen aan de constructor van de service. Wanneer DI de service creëert, herkent het de vereiste services in de constructor en levert ze dienovereenkomstig. In het volgende voorbeeld ontvangt de constructor een HttpClient via DI. HttpClient is een standaardservice.

using System.Net.Http;

public class DataAccess : IDataAccess
{
    public DataAccess(HttpClient http)
    {
        ...
    }

    ...
}

Constructorinjectie wordt ondersteund met primaire constructors in C# 12 (.NET 8) of hoger:

using System.Net.Http;

public class DataAccess(HttpClient http) : IDataAccess
{
    ...
}

Vereisten voor constructorinjectie:

  • Eén constructor moet bestaan waarvan alle argumenten door DI kunnen worden vervuld. Aanvullende parameters die niet onder DI vallen, zijn toegestaan als ze standaardwaarden opgeven.
  • De toepasselijke constructor moet zijn public.
  • Er moet één toepasselijke constructor bestaan. In geval van dubbelzinnigheid geeft DI een foutmelding.

Diensten met sleutels in componenten invoegen

Blazor ondersteunt het injecteren van keyed services met behulp van het [Inject] kenmerk. Met sleutels kunt u het bereik van de registratie en het verbruik van services bepalen bij het gebruik van afhankelijkheidsinjectie. Gebruik de InjectAttribute.Key eigenschap om de sleutel voor de service op te geven die moet worden geïnjecteerd:

[Inject(Key = "my-service")]
public IMyService MyService { get; set; }

Basiscomponentklassen voor hulpprogramma's om een DI-scope te beheren

In niet-ASP.NET Core-apps zijn scoped en transient services doorgaans beperkt tot het bereik van het huidige verzoek. Nadat de aanvraag is voltooid, worden scoped en tijdelijke services verwijderd door het DI-systeem.

In interactieve apps aan de serverzijde Blazor duurt het DI-bereik gedurende de volledige duur van het circuit (de SignalR verbinding tussen de cliënt en de server), wat kan leiden tot beperkte en afbreekbare tijdelijke services die veel langer blijven bestaan dan de levensduur van één component. Injecteer daarom niet rechtstreeks een scoped service in een onderdeel als u de levensduur van de service wilt aanpassen aan de levensduur van het onderdeel. Tijdelijke services die worden geïnjecteerd in een component dat IDisposable niet implementeert, worden verzameld door garbagecollection wanneer het component wordt verwijderd. Geïnjecteerde tijdelijke services die worden geïmplementeerd IDisposable worden onderhouden door de DI-container gedurende de levensduur van het circuit, waardoor de garbagecollectie van de service wordt voorkomen wanneer het onderdeel wordt verwijderd, wat resulteert in een geheugenlek. Verderop in deze sectie wordt een alternatieve benadering voor op OwningComponentBase-type gebaseerde scoped services beschreven, en tijdelijke wegwerpservices mogen helemaal niet worden gebruikt. Zie Ontwerpen voor het oplossen van tijdelijke wegwerpartikelen op Blazor Server (dotnet/aspnetcore #26676) voor meer informatie.

Zelfs in clientside-apps Blazor die niet via een circuit werken, worden diensten die zijn geregistreerd met een beperkte levensduur behandeld als singletons, waardoor ze langer meegaan dan scoped services in typische ASP.NET Core-apps. Tijdelijke services aan de clientzijde leven ook langer dan de onderdelen waar ze worden geïnjecteerd, omdat de DI-container, die verwijzingen naar wegwerpservices bevat, gedurende de levensduur van de app blijft bestaan, waardoor garbagecollection op de services wordt voorkomen. Hoewel vergankelijke services die langer meegaan op de server een grotere zorg zijn, moeten ze ook worden vermeden als registraties van clientdiensten. Het gebruik van het OwningComponentBase type wordt ook aanbevolen voor services aan de clientzijde om de levensduur van de service te beheren en tijdelijke wegwerpservices mogen helemaal niet worden gebruikt.

Een benadering die de levensduur van een service beperkt, is het gebruik van het OwningComponentBase type. OwningComponentBase is een abstract type dat is afgeleid van ComponentBase dat een DI-bereik maakt dat overeenkomt met de levensduur van het onderdeel. Met behulp van dit bereik kan een onderdeel services injecteren met een beperkte levensduur en ze laten bestaan zolang het onderdeel. Wanneer het onderdeel wordt vernietigd, worden services van de serviceprovider met een afgebakend bereik van het onderdeel ook verwijderd. Dit kan handig zijn voor services die opnieuw worden gebruikt binnen een onderdeel, maar niet worden gedeeld tussen onderdelen.

Er zijn twee versies van het OwningComponentBase type beschikbaar en beschreven in de volgende twee secties:

OwningComponentBase

OwningComponentBase is een abstract, wegwerpbaar kind van het ComponentBase type met een beschermde ScopedServices eigenschap van het type IServiceProvider. De provider kan worden gebruikt om services op te lossen die gebonden zijn aan de levensduur van het onderdeel.

DI-services die in het onderdeel zijn geïnjecteerd met behulp van @inject of het [Inject] kenmerk worden niet binnen de context van het onderdeel gemaakt. Als u het bereik van het onderdeel wilt gebruiken, moeten services worden omgezet met ScopedServices, ofwel GetRequiredService, of GetService. Alle services die zijn opgelost met behulp van de ScopedServices provider, hebben hun afhankelijkheden beschikbaar gesteld binnen de context van de component.

In het volgende voorbeeld ziet u het verschil tussen het rechtstreeks injecteren van een scoped service en het oplossen van een service met behulp van ScopedServices op de server. De volgende interface en implementatie voor een tijdreisklasse bevatten een DT eigenschap voor het opslaan van een DateTime waarde. De implementatie roept DateTime.Now aan om in te stellen DT wanneer de TimeTravel klasse wordt geïnstantieerd.

ITimeTravel.cs:

public interface ITimeTravel
{
    public DateTime DT { get; set; }
}

TimeTravel.cs:

public class TimeTravel : ITimeTravel
{
    public DateTime DT { get; set; } = DateTime.Now;
}

De service wordt geregistreerd als scoped in het server-side Program bestand. Diensten aan de serverzijde, die geconfigureerd zijn, hebben een levensduur die gelijk is aan de duur van het circuit.

In het bestand Program:

builder.Services.AddScoped<ITimeTravel, TimeTravel>();

In de volgende TimeTravel component:

  • De tijdreis-service wordt rechtstreeks geïnjecteerd @inject als TimeTravel1.
  • De service wordt ook afzonderlijk opgelost met ScopedServices en GetRequiredService als TimeTravel2.

TimeTravel.razor:

@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase

<h1><code>OwningComponentBase</code> Example</h1>

<ul>
    <li>TimeTravel1.DT: @TimeTravel1?.DT</li>
    <li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>

@code {
    private ITimeTravel TimeTravel2 { get; set; } = default!;

    protected override void OnInitialized()
    {
        TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
    }
}
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase

<h1><code>OwningComponentBase</code> Example</h1>

<ul>
    <li>TimeTravel1.DT: @TimeTravel1?.DT</li>
    <li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>

@code {
    private ITimeTravel TimeTravel2 { get; set; } = default!;

    protected override void OnInitialized()
    {
        TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
    }
}

Als u in eerste instantie naar het TimeTravel onderdeel navigeert, wordt de tijdreisservice tweemaal geïnstantieerd wanneer het onderdeel wordt geladen en TimeTravel1TimeTravel2 dezelfde initiële waarde hebben:

TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM

Wanneer u van het TimeTravel onderdeel naar een ander onderdeel navigeert en terug naar het TimeTravel onderdeel gaat:

  • TimeTravel1 krijgt dezelfde service-instantie die is aangemaakt toen de component voor het eerst werd geladen, zodat de waarde van DT gelijk blijft.
  • TimeTravel2 verkrijgt een nieuwe ITimeTravel service-instantie in TimeTravel2 met een nieuwe DT-waarde.

TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:48 PM

TimeTravel1 is gekoppeld aan het circuit van de gebruiker, dat intact blijft en niet wordt verwijderd totdat het onderliggende circuit is gedeconstrueerd. De service wordt bijvoorbeeld verwijderd als de verbinding met het circuit is verbroken voor de retentieperiode van het niet-verbonden circuit.

Ondanks de registratie van de scoped service in het Program bestand en de levensduur van het circuit van de gebruiker, ontvangt TimeTravel2 telkens wanneer het onderdeel wordt geïnitialiseerd een nieuw ITimeTravel service-exemplaar.

OwningComponentBase<TService>

OwningComponentBase<TService> is afgeleid van OwningComponentBase en voegt een Service eigenschap toe die een exemplaar T van de scoped DI-provider retourneert. Dit type biedt een handige manier om toegang te krijgen tot gescopeerde services zonder een instantie van IServiceProvider te gebruiken, wanneer er één primaire service is die de app vanuit de DI-container vereist, gebruikmakend van de scope van de component. De ScopedServices eigenschap is beschikbaar, zodat de app, indien nodig, services van andere typen kan ophalen.

@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>

<h1>Users (@Service.Users.Count())</h1>

<ul>
    @foreach (var user in Service.Users)
    {
        <li>@user.UserName</li>
    }
</ul>

Tijdelijke wegwerpjes aan de clientzijde detecteren

Aangepaste code kan worden toegevoegd aan een app aan de clientzijde Blazor om tijdelijke wegwerpservices te detecteren in een app die moet worden gebruikt OwningComponentBase. Deze aanpak is handig als u zich zorgen maakt dat code die in de toekomst aan de app wordt toegevoegd, een of meer tijdelijke wegwerpservices verbruikt, inclusief services die zijn toegevoegd door bibliotheken. Demonstratiecode is beschikbaar in de Blazor GitHub-voorbeeldopslagplaats (downloaden).

Controleer het volgende in .NET 6 of nieuwere versies van het BlazorSample_WebAssembly voorbeeld:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • In Program.cs:
    • De naamruimte van Services de app wordt boven aan het bestand (using BlazorSample.Services;) opgegeven.
    • DetectIncorrectUsageOfTransients wordt onmiddellijk aangeroepen nadat de opdracht aan builder is toegewezen van WebAssemblyHostBuilder.CreateDefault.
    • De TransientDisposableService is geregistreerd (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection wordt aangeroepen op de ingebouwde host in de verwerkingspijplijn van de app (host.EnableTransientDisposableDetection();).
  • De app registreert de TransientDisposableService service zonder een uitzondering te genereren. Als u echter probeert de service in TransientService.razor te resolved, werpt het framework een InvalidOperationException wanneer het framework probeert een instantie van TransientDisposableService te construeren.

Tijdelijke wegwerpartikelen aan serverzijde detecteren

Aangepaste code kan worden toegevoegd aan een serverzijde app Blazor om wegwerpbare tijdelijke services aan de serverzijde te detecteren in een app die gebruik moet maken van OwningComponentBase. Deze aanpak is handig als u zich zorgen maakt dat code die in de toekomst aan de app wordt toegevoegd, een of meer tijdelijke wegwerpservices verbruikt, inclusief services die zijn toegevoegd door bibliotheken. Demonstratiecode is beschikbaar in de Blazor GitHub-voorbeeldopslagplaats (downloaden).

Controleer het volgende in .NET 8 of nieuwere versies van het BlazorSample_BlazorWebApp voorbeeld:

Controleer het volgende in .NET 6- of .NET 7-versies van het BlazorSample_Server voorbeeld:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • In Program.cs:
    • De naamruimte van Services de app wordt boven aan het bestand (using BlazorSample.Services;) opgegeven.
    • DetectIncorrectUsageOfTransients wordt aangeroepen op de host-builder (builder.DetectIncorrectUsageOfTransients();).
    • De TransientDependency dienst is geregistreerd (builder.Services.AddTransient<TransientDependency>();).
    • De TransitiveTransientDisposableDependency is geregistreerd voor ITransitiveTransientDisposableDependency (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • De app registreert de TransientDependency service zonder een uitzondering te genereren. Als u echter probeert de service in TransientService.razor te resolved, werpt het framework een InvalidOperationException wanneer het framework probeert een instantie van TransientDependency te construeren.

Tijdelijke serviceregistraties voor IHttpClientFactory/HttpClient verwerkers

Tijdelijke serviceregistraties voor IHttpClientFactory/HttpClient handlers worden aanbevolen. Als de app IHttpClientFactory/HttpClient handlers bevat en de IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> ondersteuning voor authenticatie toevoegt, worden ook de volgende tijdelijke elementen voor authenticatie aan de clientzijde gedetecteerd. Dit wordt verwacht en kan worden genegeerd.

Er worden ook andere gevallen IHttpClientFactory/HttpClient ontdekt. Deze gevallen kunnen ook worden genegeerd.

In de Blazor voorbeeld-apps in de Blazor GitHub-opslagplaats met voorbeelden (hoe te downloaden) wordt de code gedemonstreerd om tijdelijke disposables te detecteren. De code wordt echter gedeactiveerd omdat de voorbeeld-apps handlers bevatten IHttpClientFactory/HttpClient .

Om de demonstratiecode te activeren en de werking te bekijken:

  • Verwijder de opmerkingen bij de tijdelijke wegwerplijnen in Program.cs.

  • Verwijder de voorwaardelijke controle in NavLink.razor die voorkomt dat het TransientService component wordt weergegeven in de navigatiezijbalk van de app.

    - else if (name != "TransientService")
    + else
    
  • Voer de voorbeeld-app uit en navigeer naar het TransientService onderdeel op /transient-service.

Gebruik van een Entity Framework Core (EF Core) DbContext van DI

Zie ASP.NET Core Blazor met Entity Framework Core (EF Core)voor meer informatie.

Toegang tot services aan serverzijde Blazor vanuit een ander DI-bereik

Circuitactiviteit-handlers bieden een benadering voor toegang tot scoped Blazor services vanuit andere scopes die geen gebruik maken van afhankelijkheidsinjectie (DI), zoals scopes die zijn gemaakt met behulp van Blazor.

Voordat ASP.NET Core in .NET 8 werd uitgebracht, vereiste het gebruik van een aangepast basisonderdeeltype om toegang te krijgen tot services binnen het groepenbereik van andere afhankelijkheidsinjectiebereiken. Bij circuitactiviteitshandlers is een aangepast basisonderdeeltype niet vereist, zoals in het volgende voorbeeld wordt gedemonstreert:

public class CircuitServicesAccessor
{
    static readonly AsyncLocal<IServiceProvider> blazorServices = new();

    public IServiceProvider? Services
    {
        get => blazorServices.Value;
        set => blazorServices.Value = value!;
    }
}

public class ServicesAccessorCircuitHandler(
    IServiceProvider services, CircuitServicesAccessor servicesAccessor) 
    : CircuitHandler
{
    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next) => 
            async context =>
            {
                servicesAccessor.Services = services;
                await next(context);
                servicesAccessor.Services = null;
            };
}

public static class CircuitServicesServiceCollectionExtensions
{
    public static IServiceCollection AddCircuitServicesAccessor(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitServicesAccessor>();
        services.AddScoped<CircuitHandler, ServicesAccessorCircuitHandler>();

        return services;
    }
}

Roep AddCircuitServicesAccessor aan in het Program bestand van de app.

builder.Services.AddCircuitServicesAccessor();

Krijg toegang tot de diensten met circuit-scope door de CircuitServicesAccessor te injecteren waar dit nodig is.

Zie voor een voorbeeld dat laat zien hoe u toegang krijgt tot de AuthenticationStateProvider vanuit een op DelegatingHandler gebaseerde configuratie met behulp van IHttpClientFactory, ASP.NET Core-server en Blazor Web App aanvullende beveiligingsscenario's.

Het kan voorkomen dat een Razor onderdeel asynchrone methoden aanroept die code uitvoeren in een ander DI-bereik. Zonder de juiste aanpak hebben deze DI-bereiken geen toegang tot Blazor's services, zoals IJSRuntime en Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Bijvoorbeeld, exemplaren die zijn gemaakt met behulp van HttpClient, hebben hun eigen DI-servicebereik IHttpClientFactory. Als gevolg hiervan kunnen HttpMessageHandler-instanties die zijn geconfigureerd op de HttpClient, de Blazor-services niet rechtstreeks injecteren.

Maak een klasse BlazorServiceAccessor die een AsyncLocal definieert, welke de BlazorIServiceProvider voor de huidige asynchrone context opslaat. Een BlazorServiceAccessor instantie kan worden verkregen binnen een ander DI-servicecontext om toegang te krijgen tot Blazor-services.

BlazorServiceAccessor.cs:

internal sealed class BlazorServiceAccessor
{
    private static readonly AsyncLocal<BlazorServiceHolder> s_currentServiceHolder = new();

    public IServiceProvider? Services
    {
        get => s_currentServiceHolder.Value?.Services;
        set
        {
            if (s_currentServiceHolder.Value is { } holder)
            {
                // Clear the current IServiceProvider trapped in the AsyncLocal.
                holder.Services = null;
            }

            if (value is not null)
            {
                // Use object indirection to hold the IServiceProvider in an AsyncLocal
                // so it can be cleared in all ExecutionContexts when it's cleared.
                s_currentServiceHolder.Value = new() { Services = value };
            }
        }
    }

    private sealed class BlazorServiceHolder
    {
        public IServiceProvider? Services { get; set; }
    }
}

Als u de waarde automatisch BlazorServiceAccessor.Services wilt instellen wanneer een async onderdeelmethode wordt aangeroepen, maakt u een aangepast basisonderdeel waarmee de drie primaire asynchrone toegangspunten opnieuw worden geïmplementeerd in Razor onderdeelcode:

In de volgende klasse ziet u de implementatie voor het basisonderdeel.

CustomComponentBase.cs:

using Microsoft.AspNetCore.Components;

public class CustomComponentBase : ComponentBase, IHandleEvent, IHandleAfterRender
{
    private bool hasCalledOnAfterRender;

    [Inject]
    private IServiceProvider Services { get; set; } = default!;

    [Inject]
    private BlazorServiceAccessor BlazorServiceAccessor { get; set; } = default!;

    public override Task SetParametersAsync(ParameterView parameters)
        => InvokeWithBlazorServiceContext(() => base.SetParametersAsync(parameters));

    Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
        => InvokeWithBlazorServiceContext(() =>
        {
            var task = callback.InvokeAsync(arg);
            var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
                task.Status != TaskStatus.Canceled;

            StateHasChanged();

            return shouldAwaitTask ?
                CallStateHasChangedOnAsyncCompletion(task) :
                Task.CompletedTask;
        });

    Task IHandleAfterRender.OnAfterRenderAsync()
        => InvokeWithBlazorServiceContext(() =>
        {
            var firstRender = !hasCalledOnAfterRender;
            hasCalledOnAfterRender |= true;

            OnAfterRender(firstRender);

            return OnAfterRenderAsync(firstRender);
        });

    private async Task CallStateHasChangedOnAsyncCompletion(Task task)
    {
        try
        {
            await task;
        }
        catch
        {
            if (task.IsCanceled)
            {
                return;
            }

            throw;
        }

        StateHasChanged();
    }

    private async Task InvokeWithBlazorServiceContext(Func<Task> func)
    {
        try
        {
            BlazorServiceAccessor.Services = Services;
            await func();
        }
        finally
        {
            BlazorServiceAccessor.Services = null;
        }
    }
}

Alle componenten die CustomComponentBase uitbreiden, hebben BlazorServiceAccessor.Services automatisch ingesteld op IServiceProvider in het huidige Blazor DI-bereik.

Voeg ten slotte in het Program bestand de BlazorServiceAccessor als een scoped service toe:

builder.Services.AddScoped<BlazorServiceAccessor>();

Voeg ten slotte in Startup.ConfigureServices van Startup.cs de BlazorServiceAccessor als een scoped service toe.

services.AddScoped<BlazorServiceAccessor>();

Aanvullende bronnen