iniekcja zależności ASP.NET Core Blazor
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Przez Rainer Stropek i Mike Rousos
W tym artykule wyjaśniono, jak Blazor aplikacje mogą wprowadzać usługi do składników.
Wstrzykiwanie zależności (DI) to technika uzyskiwania dostępu do usług skonfigurowanych w centralnej lokalizacji:
- Usługi zarejestrowane w strukturze można wprowadzać bezpośrednio do Razor składników.
- Blazor aplikacje definiują i rejestrują usługi niestandardowe i udostępniają je w całej aplikacji za pośrednictwem di.
Uwaga
Zalecamy przeczytanie iniekcji zależności w ASP.NET Core przed przeczytaniem tego tematu.
Usługi domyślne
Usługi przedstawione w poniższej tabeli są często używane w Blazor aplikacjach.
Usługa | Okres istnienia | opis |
---|---|---|
HttpClient | Zakresu | Udostępnia metody wysyłania żądań HTTP i odbierania odpowiedzi HTTP z zasobu zidentyfikowanego przez identyfikator URI. Po stronie klienta wystąpienie HttpClient jest rejestrowane przez aplikację w Po stronie HttpClient serwera ustawienie nie jest domyślnie skonfigurowane jako usługa. W kodzie po stronie serwera podaj element HttpClient. Aby uzyskać więcej informacji, zobacz Wywoływanie internetowego interfejsu API z aplikacji ASP.NET CoreBlazor. Element HttpClient jest zarejestrowany jako usługa o określonym zakresie, a nie pojedyncza. Aby uzyskać więcej informacji, zobacz sekcję Okres istnienia usługi. |
IJSRuntime | Po stronie klienta: pojedynczy Po stronie serwera: zakres Platforma Blazor rejestruje IJSRuntime się w kontenerze usługi aplikacji. |
Reprezentuje wystąpienie środowiska uruchomieniowego języka JavaScript, w którym są wysyłane wywołania języka JavaScript. Aby uzyskać więcej informacji, zobacz Wywoływanie funkcji języka JavaScript z metod platformy .NET na platformie ASP.NET Core Blazor. Jeśli chcesz wstrzyknąć usługę do pojedynczej usługi na serwerze, wykonaj jedną z następujących metod:
|
NavigationManager | Po stronie klienta: pojedynczy Po stronie serwera: zakres Platforma Blazor rejestruje NavigationManager się w kontenerze usługi aplikacji. |
Zawiera pomocników do pracy z identyfikatorami URI i stanem nawigacji. Aby uzyskać więcej informacji, zobacz Pomocnicy identyfikatora URI i stanu nawigacji. |
Dodatkowe usługi zarejestrowane przez platformę Blazor są opisane w dokumentacji, w której są używane do opisywania Blazor funkcji, takich jak konfiguracja i rejestrowanie.
Niestandardowy dostawca usług nie udostępnia automatycznie domyślnych usług wymienionych w tabeli. Jeśli używasz niestandardowego dostawcy usług i potrzebujesz dowolnej z usług pokazanych w tabeli, dodaj wymagane usługi do nowego dostawcy usług.
Dodawanie usług po stronie klienta
Konfigurowanie usług dla kolekcji usług aplikacji w Program
pliku. W poniższym przykładzie implementacja jest zarejestrowana ExampleDependency
dla elementu IExampleDependency
:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...
await builder.Build().RunAsync();
Po skompilowania hosta usługi są dostępne z zakresu głównego di przed renderowaniem wszystkich składników. Może to być przydatne w przypadku uruchamiania logiki inicjowania przed renderowaniem zawartości:
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();
Host udostępnia centralne wystąpienie konfiguracji dla aplikacji. Korzystając z powyższego przykładu, adres URL usługi pogodowej jest przekazywany z domyślnego źródła konfiguracji (na przykład appsettings.json
) do 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();
Dodawanie usług po stronie serwera
Po utworzeniu nowej aplikacji sprawdź część Program
pliku:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
Zmienna builder
reprezentuje WebApplicationBuilder obiekt z elementem IServiceCollection, który jest listą obiektów deskryptora usługi. Usługi są dodawane przez dostarczanie deskryptorów usług do kolekcji usług. W poniższym przykładzie przedstawiono koncepcję z interfejsem i jego konkretną implementacją IDataAccess
DataAccess
:
builder.Services.AddSingleton<IDataAccess, DataAccess>();
Po utworzeniu nowej aplikacji sprawdź metodę w pliku Startup.ConfigureServices
Startup.cs
:
using Microsoft.Extensions.DependencyInjection;
...
public void ConfigureServices(IServiceCollection services)
{
...
}
Metoda ConfigureServices jest przekazywana jako IServiceCollectionlista obiektów deskryptora usługi. Usługi są dodawane w metodzie ConfigureServices
przez dostarczanie deskryptorów usług do kolekcji usług. W poniższym przykładzie przedstawiono koncepcję z interfejsem i jego konkretną implementacją IDataAccess
DataAccess
:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataAccess, DataAccess>();
}
Rejestrowanie typowych usług
Jeśli co najmniej jedna wspólna usługa jest wymagana po stronie klienta i serwera, możesz umieścić wspólne rejestracje usług po stronie klienta i wywołać metodę w celu zarejestrowania usług w obu projektach.
Najpierw należy uwzględnić typowe rejestracje usług w oddzielnej metodzie. Na przykład utwórz metodę ConfigureCommonServices
po stronie klienta:
public static void ConfigureCommonServices(IServiceCollection services)
{
services.Add...;
}
W przypadku pliku po stronie Program
klienta wywołaj metodę ConfigureCommonServices
rejestrowania wspólnych usług:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
ConfigureCommonServices(builder.Services);
W pliku po stronie Program
serwera wywołaj metodę ConfigureCommonServices
rejestrowania typowych usług:
var builder = WebApplication.CreateBuilder(args);
...
Client.Program.ConfigureCommonServices(builder.Services);
Aby zapoznać się z przykładem tego podejścia, zobacz ASP.NET Core Blazor WebAssembly dodatkowe scenariusze zabezpieczeń.
Usługi po stronie klienta, które kończą się niepowodzeniem podczas prerenderingu
Ta sekcja dotyczy tylko składników zestawu WebAssembly w programie Blazor Web Apps.
Blazor Web Apps zwykle prerender składników WebAssembly po stronie klienta. Jeśli aplikacja jest uruchamiana z wymaganą usługą zarejestrowaną tylko w .Client
projekcie, wykonanie aplikacji powoduje błąd środowiska uruchomieniowego podobny do następującego, gdy składnik próbuje użyć wymaganej usługi podczas prerenderingu:
InvalidOperationException: Nie można podać wartości {PROPERTY} dla typu "{ASSEMBLY}}". Client.Pages. {NAZWA SKŁADNIKA}". Nie ma zarejestrowanej usługi typu "{SERVICE}".
Aby rozwiązać ten problem, użyj jednej z następujących metod:
- Zarejestruj usługę w głównym projekcie, aby udostępnić ją podczas prerenderingu składników.
- Jeśli prerendering nie jest wymagany dla składnika, wyłącz prerendering, postępując zgodnie ze wskazówkami w trybach renderowania ASP.NET CoreBlazor. Jeśli zastosujesz to podejście, nie musisz rejestrować usługi w głównym projekcie.
Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z usługami po stronie klienta podczas prerenderingu.
Okres istnienia usługi
Usługi można skonfigurować przy użyciu okresów istnienia przedstawionych w poniższej tabeli.
Okres istnienia | opis |
---|---|
Scoped | Po stronie klienta nie ma obecnie pojęcia zakresów di. Programowanie po stronie serwera obsługuje
Aby uzyskać więcej informacji na temat zachowania stanu użytkownika w aplikacjach po stronie serwera, zobacz zarządzanie stanem ASP.NET CoreBlazor. |
Singleton | Di tworzy pojedyncze wystąpienie usługi. Wszystkie składniki wymagające Singleton usługi otrzymują to samo wystąpienie usługi. |
Transient | Za każdym razem, gdy składnik uzyskuje wystąpienie Transient usługi z kontenera usługi, otrzymuje nowe wystąpienie usługi. |
System DI jest oparty na systemie DI w ASP.NET Core. Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności na platformie ASP.NET Core.
Żądanie usługi w składniku
W przypadku wstrzykiwania usług do składników Blazor obsługuje iniekcję konstruktora i iniekcję właściwości.
Wstrzykiwanie konstruktora
Po dodaniu usług do kolekcji usług należy wprowadzić co najmniej jedną usługę do składników z wstrzyknięciem konstruktora. Poniższy przykład wprowadza usługę NavigationManager
.
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");
}
}
Wstrzykiwanie właściwości
Po dodaniu usług do kolekcji usług należy wprowadzić co najmniej jedną usługę do składników @inject
Razor dyrektywy, która ma dwa parametry:
- Typ: typ usługi do wstrzykiwania.
- Właściwość: nazwa właściwości odbieranej przez wstrzykniętą usługę app Service. Właściwość nie wymaga ręcznego tworzenia. Kompilator tworzy właściwość .
Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności do widoków w programie ASP.NET Core.
Użyj wielu @inject
instrukcji, aby wstrzyknąć różne usługi.
W poniższym przykładzie pokazano, jak używać @inject
dyrektywy. Implementacja usługi Services.NavigationManager
jest wstrzykiwana do właściwości Navigation
składnika . Zwróć uwagę, że kod używa NavigationManager
tylko abstrakcji.
PropertyInjection.razor
:
@page "/property-injection"
@inject NavigationManager Navigation
<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
Take me to the Counter component
</button>
Wewnętrznie wygenerowana właściwość (Navigation
) używa atrybutu[Inject]
. Zazwyczaj ten atrybut nie jest używany bezpośrednio. Jeśli klasa bazowa jest wymagana dla składników i właściwości wstrzykiwanych są również wymagane dla klasy bazowej, ręcznie dodaj [Inject]
atrybut:
using Microsoft.AspNetCore.Components;
public class ComponentBase : IComponent
{
[Inject]
protected NavigationManager Navigation { get; set; } = default!;
...
}
Uwaga
Ponieważ oczekuje się, że wprowadzone usługi będą dostępne, domyślny literał z operatorem forgiving o wartości null (default!
) jest przypisywany na platformie .NET 6 lub nowszym. Aby uzyskać więcej informacji, zobacz Nullable reference types (NRTs) and .NET compiler null-state static analysis (Typy referencyjne dopuszczane do wartości null) i Analiza statyczna kompilatora platformy .NET o stanie null.
W składnikach pochodzących z klasy @inject
bazowej dyrektywa nie jest wymagana. Klasa InjectAttribute bazowa jest wystarczająca. Składnik wymaga @inherits
tylko dyrektywy . W poniższym przykładzie wszystkie wprowadzone usługi CustomComponentBase
są dostępne dla Demo
składnika:
@page "/demo"
@inherits CustomComponentBase
Korzystanie z di w usługach
Złożone usługi mogą wymagać dodatkowych usług. W poniższym przykładzie DataAccess
wymagana jest usługa domyślna HttpClient . @inject
(lub [Inject]
atrybut) nie jest dostępny do użycia w usługach. Zamiast tego należy użyć iniekcji konstruktora. Wymagane usługi są dodawane przez dodanie parametrów do konstruktora usługi. Podczas tworzenia usługi di rozpoznaje usługi wymagane w konstruktorze i udostępnia je odpowiednio. W poniższym przykładzie konstruktor otrzymuje wartość HttpClient za pośrednictwem di. HttpClient jest usługą domyślną.
using System.Net.Http;
public class DataAccess : IDataAccess
{
public DataAccess(HttpClient http)
{
...
}
...
}
Wstrzykiwanie konstruktora jest obsługiwane w przypadku konstruktorów podstawowych w języku C# 12 (.NET 8) lub nowszym:
using System.Net.Http;
public class DataAccess(HttpClient http) : IDataAccess
{
...
}
Wymagania wstępne dotyczące wstrzykiwania konstruktora:
- Jeden konstruktor musi istnieć, którego argumenty mogą być spełnione przez di. Dodatkowe parametry, które nie są objęte di, są dozwolone, jeśli określają wartości domyślne.
- Odpowiedni konstruktor musi mieć wartość
public
. - Musi istnieć jeden odpowiedni konstruktor. W przypadku niejednoznaczności di zgłasza wyjątek.
Wstrzykiwanie kluczowych usług do składników
Blazor obsługuje wstrzykiwanie usług kluczy przy użyciu atrybutu [Inject]
. Klucze umożliwiają określenie zakresu rejestracji i użycia usług podczas korzystania z iniekcji zależności. InjectAttribute.Key Użyj właściwości , aby określić klucz, który ma być wstrzykiwany przez usługę:
[Inject(Key = "my-service")]
public IMyService MyService { get; set; }
Klasy składników podstawowych narzędzi do zarządzania zakresem di
Blazor W aplikacjach innych niż ASP.NET Core zakres i usługi przejściowe są zwykle ograniczone do bieżącego żądania. Po zakończeniu żądania zakres i przejściowe usługi są usuwane przez system DI.
W interaktywnych aplikacjach po stronie Blazor serwera zakres di trwa przez czas trwania obwodu ( SignalR połączenie między klientem a serwerem), co może spowodować, że zakres i jednorazowe usługi przejściowe będą żyć znacznie dłużej niż okres istnienia pojedynczego składnika. W związku z tym nie należy bezpośrednio wprowadzać usługi o określonym zakresie do składnika, jeśli okres istnienia usługi będzie zgodny z okresem istnienia składnika. Przejściowe usługi wprowadzone do składnika, który nie implementuje IDisposable , są wyrzucane śmieci po usunięciu składnika. Jednak wstrzyknięte usługi przejściowe, które implementują IDisposable , są obsługiwane przez kontener DI przez okres istnienia obwodu, co uniemożliwia odzyskiwanie pamięci usługi, gdy składnik jest usuwany i powoduje wyciek pamięci. Alternatywne podejście dla usług o określonym zakresie na OwningComponentBase podstawie typu opisano w dalszej części tej sekcji, a jednorazowe usługi przejściowe nie powinny być używane w ogóle. Aby uzyskać więcej informacji, zobacz Design for solving transient disposables on Blazor Server (dotnet/aspnetcore
#26676).
Nawet w aplikacjach po stronie Blazor klienta, które nie działają w obwodzie, usługi zarejestrowane w okresie istnienia o określonym zakresie są traktowane jako pojedynczetony, więc działają dłużej niż usługi o określonym zakresie w typowych aplikacjach ASP.NET Core. Jednorazowe usługi przejściowe po stronie klienta również działają dłużej niż składniki, w których są wstrzykiwane, ponieważ kontener DI, który przechowuje odwołania do jednorazowych usług, utrzymuje się przez cały okres istnienia aplikacji, uniemożliwiając odzyskiwanie pamięci w usługach. Chociaż długotrwałe jednorazowe usługi przejściowe są bardziej niepokojące na serwerze, należy ich unikać, ponieważ również rejestracje usług klienta. OwningComponentBase Użycie typu jest również zalecane w przypadku usług w zakresie klienta w celu kontrolowania okresu istnienia usługi, a jednorazowe usługi przejściowe nie powinny być używane w ogóle.
Podejście, które ogranicza okres istnienia usługi, to użycie OwningComponentBase typu . OwningComponentBase jest typem abstrakcyjnym pochodzącym z ComponentBase tego, który tworzy zakres di odpowiadający okresowi istnienia składnika. Korzystając z tego zakresu, składnik może wstrzyknąć usługi z okresem istnienia o określonym zakresie i mieć je na żywo tak długo, jak składnik. Gdy składnik zostanie zniszczony, usługi od dostawcy usług o określonym zakresie składnika również zostaną usunięte. Może to być przydatne w przypadku usług ponownie używanych w składniku, ale nie współużytkowanych między składnikami.
Dostępne są dwie wersje OwningComponentBase typu i opisano je w dwóch następnych sekcjach:
OwningComponentBase
OwningComponentBase jest abstrakcyjnym, jednorazowym elementem podrzędnym ComponentBase typu z chronioną ScopedServices właściwością typu IServiceProvider. Dostawca może służyć do rozpoznawania usług, które są ograniczone do okresu istnienia składnika.
Usługi DI wprowadzone do składnika przy użyciu @inject
lub [Inject]
atrybut nie są tworzone w zakresie składnika. Aby użyć zakresu składnika, usługi muszą zostać rozwiązane przy użyciu polecenia ScopedServices GetRequiredService lub GetService. Wszystkie usługi rozwiązane przy użyciu ScopedServices dostawcy mają swoje zależności podane w zakresie składnika.
W poniższym przykładzie pokazano różnicę między bezpośrednim wstrzykiwaniem usługi o określonym zakresie i rozpoznawaniem usługi przy użyciu ScopedServices na serwerze. Poniższy interfejs i implementacja klasy podróży czasowych obejmują DT
właściwość do przechowywania DateTime wartości. Implementacja wywołuje metodę DateTime.Now ustawiania DT
wystąpienia TimeTravel
klasy.
ITimeTravel.cs
:
public interface ITimeTravel
{
public DateTime DT { get; set; }
}
TimeTravel.cs
:
public class TimeTravel : ITimeTravel
{
public DateTime DT { get; set; } = DateTime.Now;
}
Usługa jest zarejestrowana jako o określonym zakresie w pliku po stronie Program
serwera. Usługi w zakresie serwera mają okres istnienia równy czasowi trwania obwodu.
W pliku Program
:
builder.Services.AddScoped<ITimeTravel, TimeTravel>();
W poniższym składniku TimeTravel
:
- Usługa podróży czasowej jest bezpośrednio wstrzykiwana
@inject
jakoTimeTravel1
. - Usługa jest również rozpoznawana oddzielnie z elementami ScopedServices i GetRequiredService jako
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>();
}
}
Początkowo przechodząc do składnika, usługa podróży czasowej jest tworzone dwukrotnie po załadowaniu TimeTravel
składnika i TimeTravel1
TimeTravel2
ma tę samą wartość początkową:
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM
Podczas przechodzenia TimeTravel
z dala od składnika do innego składnika i z powrotem do TimeTravel
składnika:
TimeTravel1
jest dostarczane to samo wystąpienie usługi, które zostało utworzone podczas pierwszego załadowania składnika, więc wartośćDT
pozostaje taka sama.TimeTravel2
uzyskuje noweITimeTravel
wystąpienie usługi zTimeTravel2
nową wartością DT.
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:48 PM
TimeTravel1
jest powiązany z obwodem użytkownika, który pozostaje nienaruszony i nie jest usuwany, dopóki podstawowy obwód nie zostanie zdekonstrukturowany. Na przykład usługa jest usuwana, jeśli obwód jest odłączony dla odłączonego okresu przechowywania obwodu.
Pomimo rejestracji usługi w zakresie w Program
pliku i długowieczności obwodu użytkownika, otrzymuje nowe ITimeTravel
wystąpienie usługi za każdym razem, TimeTravel2
gdy składnik jest inicjowany.
OwningComponentBase<TService>
OwningComponentBase<TService> element pochodzi z OwningComponentBase i dodaje Service właściwość, która zwraca wystąpienie T
z dostawcy di o określonym zakresie. Ten typ to wygodny sposób uzyskiwania dostępu do usług o określonym zakresie bez użycia wystąpienia IServiceProvider , gdy istnieje jedna usługa podstawowa, której aplikacja wymaga z kontenera DI przy użyciu zakresu składnika. Właściwość ScopedServices jest dostępna, więc aplikacja może w razie potrzeby pobierać usługi innych typów.
@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>
Wykrywanie przejściowych jednorazowych zrazów po stronie klienta
Kod niestandardowy można dodać do aplikacji po stronie Blazor klienta w celu wykrywania jednorazowych usług przejściowych w aplikacji, która powinna używać polecenia OwningComponentBase. Takie podejście jest przydatne, jeśli obawiasz się, że kod dodany do aplikacji w przyszłości korzysta z co najmniej jednej przejściowej jednorazowej usługi, w tym usług dodanych przez biblioteki. Kod demonstracyjny jest dostępny w Blazor repozytorium GitHub przykładów (jak pobrać).
Sprawdź następujące elementy na platformie .NET 6 lub nowszych wersjach przykładu BlazorSample_WebAssembly
:
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransientDisposableService.cs
- W pliku
Program.cs
:- Przestrzeń nazw aplikacji
Services
jest udostępniana w górnej części pliku (using BlazorSample.Services;
). DetectIncorrectUsageOfTransients
jest wywoływana natychmiast po przypisaniu elementubuilder
z WebAssemblyHostBuilder.CreateDefault.- Element
TransientDisposableService
jest zarejestrowany (builder.Services.AddTransient<TransientDisposableService>();
). EnableTransientDisposableDetection
jest wywoływany na wbudowanym hoście w potoku przetwarzania aplikacji (host.EnableTransientDisposableDetection();
).
- Przestrzeń nazw aplikacji
- Aplikacja rejestruje usługę
TransientDisposableService
bez zgłaszania wyjątku. Jednak próba rozwiązania problemu z usługą InvalidOperationException zgłaszaTransientService.razor
błąd, gdy struktura próbuje skonstruować wystąpienie klasyTransientDisposableService
.
Wykrywanie przejściowych jednorazowego użytku po stronie serwera
Kod niestandardowy można dodać do aplikacji po stronie serwera w celu wykrywania jednorazowych usług przejściowych po stronie Blazor serwera w aplikacji, które powinny używać polecenia OwningComponentBase. Takie podejście jest przydatne, jeśli obawiasz się, że kod dodany do aplikacji w przyszłości korzysta z co najmniej jednej przejściowej jednorazowej usługi, w tym usług dodanych przez biblioteki. Kod demonstracyjny jest dostępny w Blazor repozytorium GitHub przykładów (jak pobrać).
Sprawdź następujące elementy na platformie .NET 8 lub nowszych wersjach przykładu BlazorSample_BlazorWebApp
:
Sprawdź następujące elementy na platformie .NET 6 lub .NET 7 w wersji przykładu BlazorSample_Server
:
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransitiveTransientDisposableDependency.cs
:- W pliku
Program.cs
:- Przestrzeń nazw aplikacji
Services
jest udostępniana w górnej części pliku (using BlazorSample.Services;
). DetectIncorrectUsageOfTransients
program jest wywoływany w konstruktorze hosta (builder.DetectIncorrectUsageOfTransients();
).- Usługa jest zarejestrowana
TransientDependency
(builder.Services.AddTransient<TransientDependency>();
). - Element
TransitiveTransientDisposableDependency
jest zarejestrowany dlaITransitiveTransientDisposableDependency
elementu (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();
).
- Przestrzeń nazw aplikacji
- Aplikacja rejestruje usługę
TransientDependency
bez zgłaszania wyjątku. Jednak próba rozwiązania problemu z usługą InvalidOperationException zgłaszaTransientService.razor
błąd, gdy struktura próbuje skonstruować wystąpienie klasyTransientDependency
.
Przejściowe rejestracje usług dla IHttpClientFactory
/HttpClient
programów obsługi
Zalecane są przejściowe rejestracje usług dla IHttpClientFactory/HttpClient procedur obsługi. Jeśli aplikacja zawiera IHttpClientFactory/HttpClient programy obsługi i używa IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> elementu w celu dodania obsługi uwierzytelniania, wykryto również następujące przejściowe jednorazowe operacje uwierzytelniania po stronie klienta, które są oczekiwane i można je zignorować:
Odnaleziono również inne wystąpienia programu IHttpClientFactory/HttpClient . Te wystąpienia można również zignorować.
Przykładowe Blazor aplikacje w Blazor repozytorium GitHub z przykładami (jak pobrać) przedstawiają kod do wykrywania przejściowych jednorazowych operacji. Jednak kod jest dezaktywowany, ponieważ przykładowe aplikacje obejmują IHttpClientFactory/HttpClient programy obsługi.
Aby aktywować kod demonstracyjny i sprawdzić jego działanie:
Usuń komentarz z przejściowych linii jednorazowych w pliku
Program.cs
.Usuń sprawdzanie
NavLink.razor
warunkowe, które uniemożliwiaTransientService
wyświetlanie składnika na pasku bocznym nawigacji aplikacji:- else if (name != "TransientService") + else
Uruchom przykładową aplikację i przejdź do
TransientService
składnika pod adresem/transient-service
.
Używanie obiektu Entity Framework Core (EF Core) DbContext z di
Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor z programem Entity Framework Core (EF Core).
Uzyskiwanie dostępu do usług po stronie Blazor serwera z innego zakresu di
Procedury obsługi działań obwodu zapewniają podejście do uzyskiwania dostępu do usług o określonym Blazor zakresie z innychBlazor zakresów iniekcji zależności (DI), takich jak zakresy utworzone przy użyciu polecenia IHttpClientFactory.
Przed wydaniem programu ASP.NET Core na platformie .NET 8 uzyskiwanie dostępu do usług o zakresie obwodu z innych zakresów wstrzykiwania zależności wymaganych przy użyciu niestandardowego typu składnika podstawowego. W przypadku procedur obsługi działań obwodu niestandardowy typ podstawowego składnika nie jest wymagany, jak pokazano w poniższym przykładzie:
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;
}
}
Uzyskaj dostęp do usług o zakresie obwodu, wstrzykiwając CircuitServicesAccessor
tam, gdzie jest to potrzebne.
Przykład pokazujący, jak uzyskać dostęp do AuthenticationStateProvider elementu z DelegatingHandler konfiguracji przy użyciu programu IHttpClientFactory, zobacz ASP.NET Core server-side and additional security scenarios (Scenariusze zabezpieczeń po stronie serwera ASP.NET Core).Blazor Web App
Czasami Razor składnik wywołuje metody asynchroniczne, które wykonują kod w innym zakresie di. Bez poprawnego podejścia te zakresy di nie mają dostępu do Blazorusług, takich jak IJSRuntime i Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.
Na przykład HttpClient wystąpienia utworzone przy użyciu mają IHttpClientFactory własny zakres usługi DI. W związku z tym HttpMessageHandler wystąpienia skonfigurowane na serwerze HttpClient nie mogą bezpośrednio wprowadzać Blazor usług.
Utwórz klasę, która definiuje AsyncLocal
element BlazorServiceAccessor
, który przechowuje BlazorIServiceProvider element dla bieżącego kontekstu asynchronicznego. Wystąpienie BlazorServiceAccessor
można uzyskać z innego zakresu usługi DI w celu uzyskania dostępu do Blazor usług.
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; }
}
}
Aby ustawić wartość BlazorServiceAccessor.Services
automatycznie po async
wywołaniu metody składnika, utwórz niestandardowy składnik podstawowy, który ponownie implementuje trzy podstawowe punkty wejścia asynchronicznego do Razor kodu składnika:
Poniższa klasa demonstruje implementację składnika podstawowego.
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;
}
}
}
Wszystkie składniki rozszerzające CustomComponentBase
się automatycznie mają BlazorServiceAccessor.Services
ustawioną IServiceProvider wartość w bieżącym Blazor zakresie di.
Na koniec w Program
pliku dodaj element BlazorServiceAccessor
jako usługę o określonym zakresie:
builder.Services.AddScoped<BlazorServiceAccessor>();
Na koniec w pliku Startup.cs
dodaj Startup.ConfigureServices
element BlazorServiceAccessor
jako usługę o określonym zakresie:
services.AddScoped<BlazorServiceAccessor>();
Dodatkowe zasoby
- Iniekcja usługi za pośrednictwem pliku importu najwyższego poziomu (
_Imports.razor
) w s Blazor Web App - Dependency injection in ASP.NET Core (Wstrzykiwanie zależności na platformie ASP.NET Core)
IDisposable
wskazówki dotyczące wystąpień przejściowych i udostępnionych- Wstrzykiwanie zależności do widoków w programie ASP.NET Core
- Konstruktory podstawowe (Przewodnik języka C#)