Abhängigkeitsinjektion in ASP.NET Core Blazor

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Von Rainer Stropek und Mike Rousos

In diesem Artikel wird erläutert, wie Blazor-Apps Dienste in Komponenten einbinden können.

Die Abhängigkeitsinjektion, die sogenannte Dependency Injection, ist ein Verfahren für den Zugriff auf Dienste, die an einer zentralen Stelle konfiguriert wurde:

  • Dienste, die beim Framework registriert wurden, können direkt in Razor-Komponenten injiziert werden.
  • Blazor-Apps definieren und registrieren benutzerdefinierte Dienste und machen sie über die Abhängigkeitsinjektion in der gesamten App verfügbar.

Hinweis

Lesen Sie zunächst Dependency Injection in ASP.NET Core, ehe Sie dieses Thema lesen.

Standarddienste

Die in der folgenden Tabelle aufgeführten Dienste werden häufig in Blazor-Apps verwendet.

Dienst Lebensdauer Beschreibung
HttpClient Bereichsbezogen

Stellt Methoden zum Senden von HTTP-Anforderungen und Empfangen von HTTP-Antworten aus einer Ressource bereit, die von einem URI identifiziert wird.

Clientseitig wird eine Instanz von HttpClient durch die App in der Program-Datei registriert und verwendet den Browser zur Abwicklung des HTTP-Datenverkehrs im Hintergrund.

Serverseitig ist nicht standardmäßig ein HttpClient als Dienst konfiguriert. Geben Sie im serverseitigen Code einen HttpClient an.

Weitere Informationen finden Sie unter Aufrufen einer Web-API über eine ASP.NET Core Blazor-App.

HttpClient wird als bereichsbezogener Dienst, nicht als Singleton registriert: Weitere Informationen finden Sie im Abschnitt zur Dienstlebensdauer.

IJSRuntime

Clientseitig: Singleton

Serverseitig: Bereichsbezogen

Das Blazor Framework registriert IJSRuntime im Dienstcontainer der App.

Stellt eine Instanz einer JavaScript-Laufzeit dar, in der JavaScript-Aufrufe verteilt werden. Weitere Informationen finden Sie unter Aufrufen von JavaScript-Funktionen über .NET-Methoden in Blazor in ASP.NET Core.

Wenn Sie den Dienst in einen Singletondienst auf dem Server einbinden möchten, wählen Sie einen der folgenden Ansätze:

  • Ändern Sie die Dienstregistrierung so, dass sie mit der Registrierung von IJSRuntime übereinstimmt. Dies ist sinnvoll, wenn der Dienst mit benutzerspezifischen Zuständen umgehen soll.
  • Übergeben Sie IJSRuntime an die Implementierung des Singletondiensts als Argument seiner Methodenaufrufe, anstatt das Element in das Singleton einzufügen.
NavigationManager

Clientseitig: Singleton

Serverseitig: Bereichsbezogen

Das Blazor Framework registriert NavigationManager im Dienstcontainer der App.

Enthält Hilfsprogramme für die Arbeit mit URIs und dem Navigationszustand. Weitere Informationen finden Sie unter Hilfsprogramme für URI und Navigationszustand.

Zusätzliche vom Blazor Framework registrierte Dienste werden in der Dokumentation beschrieben, in der sie verwendet werden, um Blazor Features zu beschreiben, z. B. Konfiguration und Protokollierung.

Ein benutzerdefinierter Dienstanbieter stellt nicht automatisch die in der Tabelle aufgeführten Standarddienste bereit. Wenn Sie einen benutzerdefinierten Dienstanbieter verwenden und einen der Dienste benötigen, die in der Tabelle angezeigt werden, fügen Sie dem neuen Dienstanbieter die erforderlichen Dienste hinzu.

Hinzufügen clientseitiger Dienste

Konfigurieren Sie Dienste für die Dienstsammlung der App in der Program-Datei. Im folgenden Beispiel ist die ExampleDependency-Implementierung für IExampleDependency registriert:

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

await builder.Build().RunAsync();

Nachdem der Host erstellt wurde, kann auf die Dienste vom Stammbereich der Abhängigkeitsinjektion aus zugegriffen werden, bevor Komponenten bereitgestellt werden. Dies kann für die Ausführung der Initialisierungslogik vor dem Rendern von Inhalten nützlich sein:

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();

Der Host stellt eine zentrale Konfigurationsinstanz für die App zur Verfügung. Ausgehend vom vorhergehenden Beispiel wird die URL des Wetterdiensts von einer Standardkonfigurationsquelle (z. B. appsettings.json) an InitializeWeatherAsync übergeben:

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();

Hinzufügen serverseitiger Dienste

Nachdem Sie eine neue App erstellt haben, untersuchen Sie einen Teil der Program-Datei:

var builder = WebApplication.CreateBuilder(args);

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

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

Die builder-Variable stellt einen WebApplicationBuilder mit einer IServiceCollection dar. Dabei handelt es sich um eine Liste von Dienstdeskriptorobjekten. Dienste werden durch die Bereitstellung von Dienstdeskriptoren in die Dienstsammlung aufgenommen. Das folgende Beispiel veranschaulicht das Konzept mit der IDataAccess-Schnittstelle und seiner konkreten Implementierung DataAccess:

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

Nachdem Sie eine neue App erstellt haben, sehen Sie sich die Startup.ConfigureServices-Methode in Startup.cs an:

using Microsoft.Extensions.DependencyInjection;

...

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

Der Methode ConfigureServices wird ein IServiceCollection-Objekt übergeben, das eine Liste von Dienstdeskriptorobjekten ist. Dienste werden in der ConfigureServices-Methode durch Bereitstellung von Dienstdeskriptoren in der Dienstsammlung hinzugefügt. Das folgende Beispiel veranschaulicht das Konzept mit der IDataAccess-Schnittstelle und seiner konkreten Implementierung DataAccess:

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

Registrieren allgemeiner Dienste

Wenn einer oder mehrere allgemeine Dienste client- und serverseitig benötigt werden, können Sie die allgemeinen Registrierungen der Dienste in einer clientseitigen Methode platzieren und die Methode aufrufen, um die Dienste in beiden Projekten zu registrieren.

Integrieren Sie zunächst die gemeinsamen Dienstregistrierungen in einer separaten Methode. Erstellen Sie zum Beispiel clientseitig eine ConfigureCommonServices-Methode:

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

Rufen Sie für die clientseitige Program-Datei ConfigureCommonServices auf, um die allgemeinen Dienste zu registrieren:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

Rufen Sie in der serverseitigen Program-Datei ConfigureCommonServices auf, um die allgemeinen Dienste zu registrieren:

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Ein Beispiel für diesen Ansatz finden Sie unter Zusätzliche Sicherheitsszenarien für ASP.NET Core Blazor WebAssembly.

Clientseitige Dienste, bei denen beim Prerendering ein Fehler auftritt

Dieser Abschnitt gilt nur für WebAssembly-Komponenten in Blazor-Web-Apps.

Blazor-Web-Apps führen in der Regel das Prerendering für clientseitige WebAssembly-Komponenten durch. Die Ausführung einer App mit einem erforderlichen Dienst, der nur im .Client-Projekt registriert ist, kann zu einem Laufzeitfehler ähnlich dem folgenden führen, wenn eine Komponente versucht, den erforderlichen Dienst während des Prerenderingvorgangs zu verwenden:

InvalidOperationException: Es kann kein Wert für {EIGENSCHAFT} für den Typ '{ASSEMBLY}}.Client.Pages.{KOMPONENTENNAME}' bereitgestellt werden. Es gibt keinen registrierten Dienst vom Typ '{DIENST}'.

Verwenden Sie einen der folgenden Ansätze, um das Problem zu beheben:

  • Registrieren Sie den Dienst im Hauptprojekt, um ihn während des Prerenderingvorgangs für die Komponente verfügbar zu machen.
  • Wenn für die Komponente kein Prerendering erforderlich ist, deaktivieren Sie das Prerendering, indem Sie die Anleitung im Artikel ASP.NET Core – Blazor-Rendermodi ausführen. Wenn Sie diesen Ansatz übernehmen, müssen Sie den Dienst nicht im Hauptprojekt registrieren.

Weitere Informationen finden Sie unter Clientseitige Dienste können während des Prerendering nicht aufgelöst werden.

Lebensdauer von Diensten

Die Dienste können mit den in der folgenden Tabelle angegebenen Lebensdauern konfiguriert werden.

Lebensdauer Beschreibung
Scoped

Auf der Clientseite gibt es derzeit kein Konzept für Bereiche für die Abhängigkeitsinjektion. Scoped-registrierte Dienste verhalten sich wie Singleton-Dienste.

Die serverseitige Entwicklung unterstützt die Scoped-Lebensdauer übergreifend für HTTP-Anforderungen, nicht jedoch übergreifend für SignalR-Verbindungsnachrichten zwischen Komponenten, die auf dem Client geladen werden. Beim Navigieren zwischen Seiten oder Ansichten oder von einer Seite oder Ansicht zu einer Komponente verarbeitet der Razor Pages- bzw. MVC-Teil der App bereichsbezogene Dienste normal und erstellt die Dienste für jede HTTP-Anforderung neu. Beim Navigieren zwischen Komponenten auf dem Client werden bereichsbezogene Dienste nicht neu erstellt. In diesem Fall erfolgt die Kommunikation mit dem Server über die SignalR-Verbindung der Benutzerleitung, nicht über HTTP-Anforderungen. In den folgenden Komponentenszenarien auf dem Client werden bereichsbezogene Dienste neu erstellt, weil für den Benutzer eine neue Leitung erstellt wird:

  • Der Benutzer schließt das Browserfenster. Der Benutzer öffnet ein neues Fenster und navigiert zur App zurück.
  • Der*die Benutzer*in schließt eine Registerkarte der App in einem Browserfenster. Der Benutzer öffnet eine neue Registerkarte und navigiert zur App zurück.
  • Der Benutzer wählt die Schaltfläche des Browsers zum erneuten Laden oder Aktualisieren aus.

Weitere Informationen zur Beibehaltung des Benutzerzustands in serverseitigen Apps finden Sie unter ASP.NET Core Blazor-Zustandsverwaltung.

Singleton Die Abhängigkeitsinjektion erstellt eine Einzelinstanz des Diensts. Alle Komponenten, die einen Singleton-Dienst erfordern, erhalten dieselbe Instanz des Diensts.
Transient Immer wenn eine Komponente eine Instanz eines Transient-Diensts aus dem Dienstcontainer erhält, erhält sie eine neue Instanz des Diensts.

Das Abhängigkeitsinjektionssystem basiert auf dem Abhängigkeitsinjektionssystem in ASP.NET Core. Weitere Informationen finden Sie unter Abhängigkeitsinjektion in ASP.NET Core.

Anfordern eines Diensts in einer Komponente

Zum Einfügen von Diensten in Komponenten unterstützt BlazorKonstrukturinjektion und Eigenschafteninjektion.

Constructor Injection

Nachdem die Dienste zur Dienstsammlung hinzugefügt wurden, können Sie einen oder mehrere Dienste in Komponenten mit Konstruktorinjektion einfügen. Im folgenden Beispiel wird der Dienst NavigationManager eingefügt.

ConstructorInjection.razor:

@page "/constructor-injection"

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

ConstructorInjection.razor.cs:

using Microsoft.AspNetCore.Components;

public partial class ConstructorInjection(NavigationManager navigation)
{
    protected NavigationManager Navigation { get; } = navigation;
}

Eigenschaftsinjektion

Nachdem die Dienste der Dienstsammlung hinzugefügt wurden, fügen Sie einen oder mehrere Dienste mit der @injectRazor-Anweisung ein, die zwei Parameter hat:

  • Typ: Der Typ des einzufügenden Diensts.
  • Eigenschaft: Der Name der Eigenschaft, die den eingefügten App-Dienst erhält. Die Eigenschaft erfordert keine manuelle Erstellung. Der Compiler erstellt die Eigenschaft.

Weitere Informationen finden Sie unter Abhängigkeitsinjektion in Ansichten in ASP.NET Core.

Verwenden Sie mehrere @inject-Anweisungen, um verschiedene Dienste einzufügen.

Im folgenden Beispiel wird die Verwendung der @inject-Anweisung erläutert. Der Dienst, der Services.NavigationManager implementiert, wird in die Eigenschaft Navigation der Komponente eingefügt. Beachten Sie, dass der Code nur die Abstraktion NavigationManager verwendet.

PropertyInjection.razor:

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

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

Intern verwendet die generierte Eigenschaft (Navigation) das Attribut [Inject]. In der Regel wird dieses Attribut nicht direkt verwendet. Wenn eine Basisklasse für Komponenten erforderlich ist und die injizierten Eigenschaften auch für die Basisklasse erforderlich sind, fügen Sie das Attribut [Inject] manuell hinzu:

using Microsoft.AspNetCore.Components;

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

    ...
}

Hinweis

Da von injizierten Diensten erwartet wird, dass sie verfügbar sind, wird das Standardliteral in .NET 6 oder höher mit dem NULL-toleranten Operator (default!) zugewiesen. Weitere Informationen finden Sie unter Nullable-Verweistypen (NRTs) und statische Analyse des .NET-Compilers mit NULL-Status.

In Komponenten, die von einer Basisklasse abgeleitet sind, ist die @inject-Anweisung nicht erforderlich. Das InjectAttribute der Basisklasse ist ausreichend. Die Komponente erfordert nur die @inherits-Anweisung. Im folgenden Beispiel sind alle eingefügten Dienste der CustomComponentBase für die Demo-Komponente verfügbar:

@page "/demo"
@inherits CustomComponentBase

Verwenden der Abhängigkeitsinjektion in Diensten

Komplexe Dienste können zusätzliche Dienste erfordern. Im folgenden Beispiel ist für DataAccess der HttpClient-Standarddienst erforderlich. @inject (oder das Attribut [Inject]) ist nicht für die Verwendung in Diensten verfügbar. Stattdessen muss die Constructor Injection verwendet werden. Erforderliche Dienste werden durch Hinzufügen von Parametern zum Konstruktor des Diensts hinzugefügt. Wenn die Abhängigkeitsinjektion den Dienst erstellt, erkennt sie die erforderlichen Dienste im Konstruktor und stellt sie entsprechend zur Verfügung. Im folgenden Beispiel empfängt der Konstruktor einen HttpClient über die Abhängigkeitsinjektion. HttpClient ist ein Standarddienst.

using System.Net.Http;

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

Voraussetzungen für die Constructor Injection:

  • Es muss einen Konstruktor geben, dessen Argumente alle von der Abhängigkeitsinjektion erfüllt werden können. Zusätzliche, nicht durch die Abhängigkeitsinjektion abgedeckte Parameter sind zulässig, wenn sie Standardwerte angeben.
  • Der anwendbare Konstruktor muss public sein.
  • Es muss ein anwendbarer Konstruktor vorhanden sein. Im Falle einer Mehrdeutigkeit löst die Abhängigkeitsinjektion eine Ausnahme aus.

Einfügen von schlüsselbasierten Diensten in Komponenten

Blazor unterstützt das Einfügen schlüsselbasierter Dienste mithilfe des [Inject]-Attributs. Schlüssel ermöglichen es, Bereiche für die Registrierung und für den Verbrauch von Diensten bei Verwendung der Abhängigkeitsinjektion festzulegen. Verwenden Sie die InjectAttribute.Key-Eigenschaft, um den Schlüssel für den Dienst anzugeben, der eingefügt werden soll:

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

Hilfsprogramm-Basiskomponentenklassen zur Verwaltung eines Bereichs für die Abhängigkeitsinjektion

In ASP.NET Core-Apps ohne Blazor werden bereichsbezogene und vorübergehende Dienste in der Regel der aktuellen Anforderung zugeordnet. Nachdem die Anforderung abgeschlossen ist, werden bereichsbezogene und vorübergehende Dienste vom Abhängigkeitsinjektionssystem entsorgt.

In interaktiven serverseitigen Blazor-Apps ist der DI-Bereich für die Dauer der Leitung (die SignalR-Verbindung zwischen Client und Server) gültig. Dies kann dazu führen, dass die Lebensdauer bereichsbezogener und verwerfbarer vorübergehender Dienste viel länger ist als die für eine einzelne Komponente erwartete Lebensdauer. Fügen Sie daher einen bereichsbezogenen Dienst nicht direkt in eine Komponente ein, wenn Sie beabsichtigen, die Dienstlebensdauer auf die Lebensdauer der Komponente abzustimmen. Für vorübergehende Dienste, die in eine Komponente eingefügt werden, die nicht IDisposable implementieren, wird beim Verwerfen der Komponente eine Garbage Collection ausgeführt. Eingefügte vorübergehende Dienste, die IDisposable implementieren, werden jedoch für die Lebensdauer der Leitung vom DI-Container beibehalten. Dadurch wird die Garbage Collection für den Dienst beim Verwerfen der Komponente verhindert, und es kommt zu einem Arbeitsspeicherverlust. Ein alternativer Ansatz für bereichsbezogene Dienste, die auf dem Typ OwningComponentBase basieren, wird weiter unten in diesem Abschnitt beschrieben, und verfügbare vorübergehende Dienste sollten überhaupt nicht verwendet werden. Weitere Informationen finden Sie unter Design for solving transient disposables on Blazor Server (dotnet/aspnetcore #26676).

Auch bei clientseitigen Blazor-Apps, die keine Leitung verwenden, werden Dienste, die mit einer bereichsbezogenen Lebensdauer registriert wurden, als Singletons behandelt, deshalb ist ihre Lebensdauer länger als die von bereichsbezogenen Diensten in typischen ASP.NET Core-Apps. Die Lebensdauer clientseitiger verwerfbarer vorübergehender Dienste ist außerdem länger als die der Komponenten, in die sie eingefügt werden, da der DI-Container, der Verweise auf verwerfbare Dienste enthält, für die Lebensdauer der App beibehalten wird. Dadurch wird die Garbage Collection für die Dienste verhindert. Obwohl langlebige verwerfbare vorübergehende Dienste auf dem Server von größerer Bedeutung sind, sollten sie auch als Clientdienstregistrierungen vermieden werden. Die Verwendung des Typs OwningComponentBase wird auch für clientseitige bereichsbezogene Dienste empfohlen, um die Lebensdauer des Diensts zu steuern. Verwerfbare vorübergehende Dienste sollten überhaupt nicht verwendet werden.

Ein Ansatz, der die Lebensdauer eines Dienstes begrenzt, ist die Verwendung des OwningComponentBase-Typs. OwningComponentBase ist ein abstrakter, von ComponentBase abgeleiteter Typ, der einen der Lebensdauer der Komponente entsprechenden Bereich für die Abhängigkeitsinjektion erstellt. Mit diesem Bereich kann eine Komponente Dienste mit einer bereichsbezogenen Lebensdauer einfügen und sie so lange wie die Komponente nutzen. Wenn die Komponente zerstört wird, werden auch die Dienste des bereichsbezogenen Dienstanbieters der Komponente entsorgt. Dies kann für Dienste nützlich sein, die in einer Komponente wiederverwendet werden, aber nicht komponentenübergreifend genutzt werden.

Zwei Versionen des Typs OwningComponentBase sind verfügbar und werden in den nächsten beiden Abschnitten beschrieben:

OwningComponentBase

OwningComponentBase ist ein abstraktes, verwerfbares, untergeordnetes Element vom Typ ComponentBase mit einer geschützten ScopedServices-Eigenschaft vom Typ IServiceProvider. Der Anbieter kann zur Auflösung von Diensten verwendet werden, die der Lebensdauer der Komponente zugeordnet sind.

DI-Dienste, die mit @inject oder dem Attribut [Inject] in die Komponente injiziert werden, werden nicht im Geltungsbereich der Komponente erstellt. Um den Bereich der Komponente zu verwenden, müssen die Dienste mit ScopedServices entweder mit GetRequiredService oder mit GetService aufgelöst werden. Alle Diensten, die unter Verwendung des ScopedServices-Anbieters aufgelöst werden, haben ihre Abhängigkeiten im Bereich der Komponente.

Das folgende Beispiel veranschaulicht den Unterschied zwischen der direkten Injektion eines bereichsbezogenen Diensts und der Dienstauflösung mit ScopedServices auf dem Server. Die folgende Schnittstelle und Implementierung für eine Zeitreiseklasse enthalten eine DT-Eigenschaft zur Aufnahme eines DateTime-Werts. Die Implementierung ruft DateTime.Now auf, um DT festzulegen, wenn die TimeTravel-Klasse instanziiert wird.

ITimeTravel.cs:

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

TimeTravel.cs:

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

Der Dienst wird in der serverseitigen Program-Datei als bereichsbezogen registriert. Serverseitige, bereichsbezogene Dienste haben eine Lebensdauer, die der Dauer der Leitung entspricht.

In der Program-Datei:

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

In der folgenden TimeTravel-Komponente:

  • Der Zeitreisedienst wird direkt mit @inject als TimeTravel1 injiziert.
  • Der Dienst wird auch separat mit ScopedServices und GetRequiredService als TimeTravel2 aufgelöst.

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>();
    }
}

Bei der ersten Navigation zur TimeTravel-Komponente wird der Zeitreisedienst zweimal instanziiert, wenn die Komponente geladen wird und TimeTravel1 und TimeTravel2 den gleichen Anfangswert haben:

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

Beim Navigieren von der TimeTravel-Komponente zu einer anderen Komponente und zurück zur TimeTravel-Komponente:

  • wird TimeTravel1 die gleiche Dienstinstanz bereitgestellt, die beim ersten Laden der Komponente erstellt wurde, sodass der Wert von DT unverändert bleibt.
  • ruft TimeTravel2 eine neue ITimeTravel-Dienstinstanz in TimeTravel2 mit einem neuen DT-Wert ab.

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

TimeTravel1 ist an die Benutzerleitung gebunden, die intakt bleibt und erst entsorgt wird, wenn die zugrunde liegende Leitung dekonstruiert wird. Der Dienst wird beispielweise entsorgt, wenn die Leitung für den Aufbewahrungszeitraum der unterbrochenen Leitung getrennt wird.

Trotz der bereichsbezogenen Dienstregistrierung in der Program-Datei und der Lebensdauer der Benutzerleitung erhält TimeTravel2 bei jeder Initialisierung der Komponente eine neue ITimeTravel-Dienstinstanz.

OwningComponentBase<TService>

OwningComponentBase<TService> ist abgeleitet von OwningComponentBase und fügt eine Eigenschaft Service hinzu, die eine Instanz von T vom bereichsbezogenen Abhängigkeitsinjektionsanbieter zurückgibt. Dieser Typ ist eine komfortable Methode für den Zugriff auf bereichsbezogene Dienste, ohne eine Instanz von IServiceProvider zu verwenden, wenn es einen primären Dienst gibt, den die Anwendung aus dem Abhängigkeitsinjektionscontainer unter Verwendung des Bereichs der Komponente benötigt. Die Eigenschaft ScopedServices ist verfügbar, sodass die App bei Bedarf auch Dienste mit anderen Typen erhalten kann.

@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>

Erkennung clientseitiger vorübergehender Elemente, die verworfen werden können

Benutzerdefinierter Code kann einer clientseitigen Blazor-App hinzugefügt werden, um verwerfbare vorübergehende Dienste in einer App zu erkennen, die OwningComponentBase verwenden sollte. Dieser Ansatz ist nützlich, wenn Sie befürchten, dass Code, der der App in Zukunft hinzugefügt wird, vorübergehende verwerfbare Dienste nutzt, einschließlich der Dienste, die von Bibliotheken hinzugefügt werden. Democode ist im Blazor-Beispiele-Repository auf GitHub verfügbar (Downloadanleitung).

Überprüfen Sie Folgendes in .NET 6 oder höheren Versionen des BlazorSample_WebAssembly-Beispiels:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • In: Program.cs
    • Der Services-Namespace der App wird oben in der Datei bereitgestellt (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients wird unmittelbar aufgerufen, nachdem builder von WebAssemblyHostBuilder.CreateDefault zugewiesen wurde.
    • TransientDisposableService ist registriert (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection wird für den integrierten Host in der Verarbeitungspipeline der App aufgerufen (host.EnableTransientDisposableDetection();).
  • Die App registriert den Dienst TransientDisposableService, ohne eine Ausnahme zu auslösen. Beim Versuch, den Dienst in TransientService.razor aufzulösen, wird jedoch InvalidOperationException ausgelöst, wenn das Framework versucht, eine Instanz von TransientDisposableService zu erstellen.

Erkennung serverseitiger vorübergehender Elemente, die verworfen werden können

Benutzerdefinierter Code kann einer serverseitigen Blazor-App hinzugefügt werden, um serverseitige vorübergehende Dienste in einer App zu erkennen, die OwningComponentBase verwenden sollte. Dieser Ansatz ist nützlich, wenn Sie befürchten, dass Code, der der App in Zukunft hinzugefügt wird, vorübergehende verwerfbare Dienste nutzt, einschließlich der Dienste, die von Bibliotheken hinzugefügt werden. Democode ist im Blazor-Beispiele-Repository auf GitHub verfügbar (Downloadanleitung).

Überprüfen Sie Folgendes in .NET 8 oder höheren Versionen des BlazorSample_BlazorWebApp-Beispiels:

Überprüfen Sie Folgendes in .NET 6 oder .NET 7 des BlazorSample_Server-Beispiels:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • In: Program.cs
    • Der Services-Namespace der App wird oben in der Datei bereitgestellt (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients wird für den Host-Generator aufgerufen (builder.DetectIncorrectUsageOfTransients();).
    • Der TransientDependency-Dienst ist registriert (builder.Services.AddTransient<TransientDependency>();).
    • TransitiveTransientDisposableDependency ist für ITransitiveTransientDisposableDependency registriert (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • Die App registriert den Dienst TransientDependency, ohne eine Ausnahme zu auslösen. Beim Versuch, den Dienst in TransientService.razor aufzulösen, wird jedoch InvalidOperationException ausgelöst, wenn das Framework versucht, eine Instanz von TransientDependency zu erstellen.

Vorübergehende Dienstregistrierungen für IHttpClientFactory/HttpClient-Handler

Für IHttpClientFactory/HttpClient-Handler werden vorübergehende Dienstregistrierungen empfohlen. Wenn die App IHttpClientFactory/HttpClient-Handler enthält und zum Hinzufügen der Unterstützung für die Authentifizierung IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> verwendet, werden auch die folgenden vorübergehenden verwerfbaren Elemente für die clientseitige Authentifizierung erkannt. Dies wird erwartet und kann ignoriert werden:

Andere Instanzen von IHttpClientFactory/HttpClient werden ebenfalls erkannt. Diese Instanzen können auch ignoriert werden.

Die Blazor-Beispiel-Apps im Blazor-Beispiele-Repository auf GitHub (Downloadanleitung) veranschaulichen den Code zum Erkennen von vorübergehend verwerfbaren Diensten. Der Code ist jedoch deaktiviert, da die Beispiel-Apps IHttpClientFactory/HttpClient-Handler enthalten.

So können Sie den Democode aktivieren und seine Ausführung sehen:

  • Heben Sie die Auskommentierung der Zeilen für die vorübergehend verwerfbaren Dienste in Program.cs auf.

  • Entfernen Sie die bedingte Überprüfung in NavLink.razor, die verhindert, dass die TransientService-Komponente in der Navigationsseitenleiste der App angezeigt wird:

    - else if (name != "TransientService")
    + else
    
  • Führen Sie die Beispiel-App aus und navigieren Sie zur TransientService-Komponente unter /transient-service.

Verwenden eines Entity Framework Core (EF Core) DbContext von DI

Weitere Informationen finden Sie unter ASP.NET Core Blazor mit Entity Framework Core (EF Core).

Zugreifen auf serverseitige Blazor-Dienste aus einem anderen Abhängigkeitsinjektionsbereich

Leitungsaktivitätshandler bieten auch einen Ansatz für den Zugriff auf bereichsbezogene Blazor-Dienste aus anderen, Blazor-fremden DI-Bereichen (Dependency Injection, Abhängigkeitsinjektion), etwa mithilfe von IHttpClientFactory erstellten Bereichen.

Vor der Veröffentlichung von ASP.NET Core in NET 8 war für den Zugriff auf Dienste im Leitungsbereich aus anderen Abhängigkeitsinjektionsbereichen ein benutzerdefinierter Basiskomponententyp erforderlich. Mit Leitungsaktivitätshandlern ist kein benutzerdefinierter Basiskomponententyp erforderlich, wie im folgenden Beispiel veranschaulicht:

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

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

public class ServicesAccessorCircuitHandler : CircuitHandler
{
    readonly IServiceProvider services;
    readonly CircuitServicesAccessor circuitServicesAccessor;

    public ServicesAccessorCircuitHandler(IServiceProvider services, 
        CircuitServicesAccessor servicesAccessor)
    {
        this.services = services;
        this.circuitServicesAccessor = servicesAccessor;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return async context =>
        {
            circuitServicesAccessor.Services = services;
            await next(context);
            circuitServicesAccessor.Services = null;
        };
    }
}

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

        return services;
    }
}

Greifen Sie auf die Dienste im Leitungsbereich zu, indem Sie CircuitServicesAccessor dort einfügen, wo es erforderlich ist.

Ein Beispiel, das zeigt, wie Sie über einen mithilfe von IHttpClientFactory eingerichteten delegierenden Handler (DelegatingHandler) auf AuthenticationStateProvider zugreifen, finden Sie unterserverseitige zusätzliche Sicherheitsszenarios für ASP.NET Core Blazor.

Es kann vorkommen, dass eine Razor-Komponente asynchrone Methoden aufruft, die Code in einem anderen DI-Bereich (Dependency Injection, Abhängigkeitsinjektion) ausführen. Ohne den richtigen Ansatz haben diese DI-Bereiche keinen Zugriff auf die Dienste von Blazor (z. B IJSRuntime und Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage).

Mit IHttpClientFactory erstellte HttpClient-Instanzen verfügen beispielsweise über einen eigenen DI-Dienstbereich. Daher können HttpMessageHandler-Instanzen, die für den HttpClient konfiguriert sind, Blazor-Dienste nicht direkt injizieren.

Erstellen Sie eine BlazorServiceAccessor-Klasse, die eine AsyncLocal-Instanz zum Speichern des BlazorIServiceProvider für den aktuellen asynchronen Kontext definiert. Eine BlazorServiceAcccessor-Instanz kann innerhalb eines anderen DI-Dienstbereichs abgerufen werden, um auf Blazor-Dienste zuzugreifen.

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; }
    }
}

Um den Wert von BlazorServiceAccessor.Services automatisch festzulegen, wenn eine async-Komponentenmethode aufgerufen wird, erstellen Sie eine benutzerdefinierte Basiskomponente, die die drei primären asynchronen Einstiegspunkte erneut im Razor-Komponentencode implementiert:

Die folgende Klasse veranschaulicht die Implementierung für die Basiskomponente.

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;
        }
    }
}

Für alle Komponenten, die CustomComponentBase erweitern, ist BlazorServiceAccessor.Services automatisch auf den IServiceProvider im aktuellen Blazor-DI-Bereich festgelegt.

Fügen Sie abschließend in der Program-Datei den BlazorServiceAccessor als bereichsbezogenen Dienst hinzu:

builder.Services.AddScoped<BlazorServiceAccessor>();

Fügen Sie zum Schluss in Startup.ConfigureServices von Startup.csBlazorServiceAccessor als bereichsbezogenen Dienst hinzu:

services.AddScoped<BlazorServiceAccessor>();

Zusätzliche Ressourcen