iniekcja zależności ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

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ą, zapoznaj się z wersją tego artykułu platformy .NET 8.

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 Program pliku i używa przeglądarki do obsługi ruchu HTTP w tle.

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:

  • Zmień rejestrację usługi na zakres, aby odpowiadała IJSRuntimerejestracji, która jest odpowiednia, jeśli usługa zajmuje się stanem specyficznym dla użytkownika.
  • Przekaż element IJSRuntime do implementacji pojedynczej usługi jako argument wywołania metody zamiast wstrzykiwania jej do pojedynczego elementu.
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ą IDataAccessDataAccess:

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

Po utworzeniu nowej aplikacji sprawdź metodę w pliku Startup.ConfigureServicesStartup.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ą IDataAccessDataAccess:

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 usłudze Blazor Web Apps.

Blazor Web Apps zwykle prerender składniki 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. Scoped-zarejestrowane usługi zachowują się jak Singleton usługi.

Programowanie po stronie serwera obsługuje Scoped okres istnienia żądań HTTP, ale nie między SignalR komunikatami połączenia/obwodu między składnikami ładowanym na kliencie. Część Razor Pages lub MVC aplikacji traktuje usługi o określonym zakresie normalnie i odtwarza usługi w każdym żądaniu HTTP podczas nawigowania między stronami lub widokami albo z poziomu strony lub widoku do składnika. Usługi o określonym zakresie nie są odtwarzane podczas nawigowania między składnikami na kliencie, gdzie komunikacja z serwerem odbywa się za SignalR pośrednictwem połączenia obwodu użytkownika, a nie za pośrednictwem żądań HTTP. W następujących scenariuszach składników na kliencie usługi o określonym zakresie są odtwarzane, ponieważ dla użytkownika jest tworzony nowy obwód:

  • Użytkownik zamyka okno przeglądarki. Użytkownik otwiera nowe okno i przechodzi z powrotem do aplikacji.
  • Użytkownik zamyka kartę aplikacji w oknie przeglądarki. Użytkownik otwiera nową kartę i przechodzi z powrotem do aplikacji.
  • Użytkownik wybiera przycisk ponownego ładowania/odświeżania przeglądarki.

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

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 @injectRazor 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 Navigationskł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)
    {
        ...
    }
}

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 ScopedServicesGetRequiredService 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 jako TimeTravel1.
  • 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 TimeTravel1TimeTravel2 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 nowe ITimeTravel wystąpienie usługi z TimeTravel2 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 elementu builder 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();).
  • Aplikacja rejestruje usługę TransientDisposableService bez zgłaszania wyjątku. Jednak próba rozwiązania problemu z usługą InvalidOperationException zgłasza TransientService.razor błąd, gdy struktura próbuje skonstruować wystąpienie klasy TransientDisposableService.

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 dla ITransitiveTransientDisposableDependency elementu (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • Aplikacja rejestruje usługę TransientDependency bez zgłaszania wyjątku. Jednak próba rozwiązania problemu z usługą InvalidOperationException zgłasza TransientService.razor błąd, gdy struktura próbuje skonstruować wystąpienie klasy TransientDependency.

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żliwia TransientService 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 : 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;
    }
}

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 Server-side ASP.NET Core Blazor dodatkowe scenariusze zabezpieczeń.

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 AsyncLocalelement BlazorServiceAccessor , który przechowuje BlazorIServiceProvider element dla bieżącego kontekstu asynchronicznego. Wystąpienie BlazorServiceAcccessor 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.csdodaj Startup.ConfigureServices element BlazorServiceAccessor jako usługę o określonym zakresie:

services.AddScoped<BlazorServiceAccessor>();

Dodatkowe zasoby