Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zobaczyć bieżącą wersję, zobacz wersję artykułu .NET 9.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zobaczyć bieżącą wersję, zobacz wersję artykułu .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 zobaczyć bieżącą wersję, zobacz wersję artykułu .NET 9.
W tym artykule opisano cykl życia składnika ASP.NET Core Razor oraz sposób używania zdarzeń cyklu życia.
Zdarzenia cyklu życia
Składnik Razor przetwarza Razor zdarzenia cyklu życia składnika w zestawie metod cyklu życia synchronicznego i asynchronicznego. Metody cyklu życia komponentów można nadpisać, aby wykonywać dodatkowe operacje podczas inicjowania i renderowania tych komponentów.
Ten artykuł upraszcza przetwarzanie zdarzeń cyklu życia składników w celu wyjaśnienia złożonej logiki struktury i nie obejmuje każdej zmiany wprowadzonej przez lata. Aby zintegrować niestandardowe przetwarzanie zdarzeń z przetwarzaniem zdarzeń cyklu życia ComponentBase
, może być konieczny dostęp do źródła referencyjnego . Komentarze kodu w źródle referencyjnym zawierają dodatkowe uwagi dotyczące przetwarzania zdarzeń cyklu życia, których nie ma w tym artykule ani w dokumentacji interfejsu API.
Uwaga
Linki dokumentacji do źródła referencyjnego .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżący rozwój kolejnej wersji .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Poniższe uproszczone diagramy ilustrują Razor przetwarzanie zdarzeń cyklu życia składnika. Metody języka C# skojarzone ze zdarzeniami cyklu życia są definiowane przy użyciu przykładów w poniższych sekcjach tego artykułu.
Zdarzenia cyklu życia składnika:
- Jeśli składnik jest renderowany po raz pierwszy na żądanie:
- Utwórz wystąpienie składnika.
- Wykonaj iniekcję właściwości.
- Zadzwoń pod
OnInitialized{Async}
. Jeśli zostanie zwrócona niekompletna Task, oczekuje się na Task, a następnie składnik jest przerysowywany. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
- Zadzwoń pod
OnParametersSet{Async}
. Jeśli zostanie zwrócona niekompletna Task, oczekuje się na Task, a następnie składnik jest przerysowywany. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną. - Wykonaj renderowanie dla wszystkich zadań synchronicznych i ukończ Tasks.
Uwaga
Akcje asynchroniczne wykonywane w zdarzeniach cyklu życia mogą opóźnić renderowanie składników lub wyświetlanie danych. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą obsługi niekompletnych akcji asynchronicznych w części renderowania w dalszej części tego artykułu.
Komponent nadrzędny renderuje przed komponentami potomnymi, ponieważ renderowanie określa, które komponenty potomne są obecne. Jeśli jest używana synchroniczna inicjalizacja komponentu nadrzędnego, gwarantuje się, że inicjalizacja nadrzędna zakończy się jako pierwsza. Jeśli jest używana asynchroniczna inicjalizacja składnika nadrzędnego, nie można określić kolejności ukończenia inicjalizacji składnika nadrzędnego i podrzędnego, ponieważ zależy ona od działania kodu inicjalizacyjnego.
Przetwarzanie zdarzeń DOM:
- Procedura obsługi zdarzeń jest uruchomiona.
- Jeśli zostanie zwrócona niekompletna Task, oczekuje się na Task, a następnie składnik jest przerysowywany.
- Wykonaj renderowanie dla wszystkich zadań synchronicznych i ukończ Tasks.
Cykl Render
życia:
- Unikaj dalszych operacji renderowania w składniku, gdy spełnione są oba następujące warunki:
- Nie jest to pierwszy render.
-
ShouldRender
zwraca wartośćfalse
.
- Zbuduj różnicę drzewa renderowania i renderuj składnik.
- Poczekaj na aktualizację DOM.
- Zadzwoń pod
OnAfterRender{Async}
. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
Wywołania dewelopera do StateHasChanged
powodują ponowne renderowanie. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.
Stan spoczynku podczas wstępnego renderowania.
W aplikacjach Blazor po stronie serwera prerenderowanie czeka na quiescence, co oznacza, że składnik nie jest renderowany, dopóki wszystkie składniki w drzewie renderowania nie zakończą swojego. Quiescence może prowadzić do zauważalnych opóźnień w renderowaniu, gdy składnik wykonuje długotrwałe zadania podczas inicjalizacji i innych metod cyklu życia, co skutkuje niską jakością doświadczenia użytkownika. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą obsługi niekompletnych akcji asynchronicznych w części renderowania w dalszej części tego artykułu.
Gdy parametry są ustawione (SetParametersAsync
)
SetParametersAsync ustawia parametry dostarczane przez rodzica składnika w drzewie renderowania lub na podstawie parametrów trasy.
Parametr metody ParameterView zawiera zestaw wartości parametrów składnika dla składnika za każdym razem, gdy SetParametersAsync jest wywoływany. Przesłaniając metodę SetParametersAsync , kod dewelopera może wchodzić w interakcje bezpośrednio z parametrami ParameterView.
Domyślna implementacja SetParametersAsync ustawia wartość każdej właściwości z atrybutem [Parameter]
lub [CascadingParameter]
, który ma odpowiadającą wartość w obiekcie ParameterView. Parametry, które nie mają odpowiadającej wartości w ParameterView, pozostają niezmienione.
Ogólnie rzecz biorąc, kod powinien wywoływać metodę klasy bazowej (await base.SetParametersAsync(parameters);
) podczas zastępowania metody SetParametersAsync. W zaawansowanych scenariuszach kod dewelopera może interpretować wartości parametrów przychodzących w dowolny sposób, nie wywołując metody klasy bazowej. Na przykład nie ma potrzeby przypisywania parametrów przychodzących do właściwości klasy. Należy jednak odwołać się do źródła ComponentBase
referencyjnego podczas strukturyzacji kodu bez wywoływania metody klasy bazowej, ponieważ wywołuje inne metody cyklu życia i wyzwala renderowanie, co jest skomplikowane.
Uwaga
Linki dokumentacji do źródła referencyjnego .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżący rozwój kolejnej wersji .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Jeśli chcesz polegać na inicjowaniu i logice renderowania ComponentBase.SetParametersAsync, ale nie przetwarzać przychodzących parametrów, możesz przekazać pusty element ParameterView do metody klasy bazowej:
await base.SetParametersAsync(ParameterView.Empty);
Jeśli procedury obsługi zdarzeń są udostępniane w kodzie dewelopera, odłącz je przy usuwaniu. Aby uzyskać więcej informacji, zobacz usuwanie komponentów w ASP.NET Core Razor.
W poniższym przykładzie, ParameterView.TryGetValue przypisuje wartość parametru Param
do value
, jeśli analizowanie parametru trasy dla Param
zakończy się pomyślnie. Gdy value
nie jest równe null
, wartość jest wyświetlana przez składnik.
Mimo że dopasowywanie parametrów trasy jest niezależne od wielkości liter, TryGetValue dopasowuje tylko nazwy parametrów uwzględniające wielkość liter w szablonie trasy. Poniższy przykład wymaga użycia /{Param?}
w szablonie trasy, aby uzyskać wartość za pomocą TryGetValue, a nie /{param?}
. Jeśli /{param?}
jest używany w tym scenariuszu, TryGetValue zwraca wartość false
i message
nie zostaje ustawione na żaden z ciągów message
.
SetParamsAsync.razor
:
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async"
@page "/set-params-async/{Param}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
Inicjowanie składnika (OnInitialized{Async}
)
OnInitialized i OnInitializedAsync są używane wyłącznie do inicjowania składnika przez cały okres istnienia wystąpienia składnika. Zmiany wartości parametrów nie powinny mieć wpływu na inicjalizację wykonywaną w tych metodach. Na przykład ładowanie opcji statycznych do listy rozwijanej, która nie zmienia się przez okres istnienia składnika i która nie jest zależna od wartości parametrów, jest wykonywana w jednej z tych metod cyklu życia. Jeśli wartości parametrów lub zmiany w wartościach parametrów wpływają na stan składnika, użyj OnParametersSet{Async}
zamiast tego.
Te metody są wywoływane, gdy składnik jest inicjowany po otrzymaniu jego początkowych parametrów w pliku SetParametersAsync. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
Jeśli używana jest synchroniczna inicjalizacja składnika nadrzędnego, gwarantowane jest zakończenie tej inicjalizacji przed rozpoczęciem inicjalizacji składnika podrzędnego. Jeśli jest używana asynchroniczna inicjalizacja składnika nadrzędnego, nie można określić kolejności ukończenia inicjalizacji składnika nadrzędnego i podrzędnego, ponieważ zależy ona od działania kodu inicjalizacyjnego.
W przypadku operacji synchronicznej zastąp OnInitialized:
OnInit.razor
:
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
Aby wykonać operację asynchroniczną, zastąp metodę OnInitializedAsync i użyj operatora await
.
protected override async Task OnInitializedAsync()
{
await ...
}
Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj OnInitializedAsync na klasie bazowej.
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
Nie jest konieczne wywołanie ComponentBase.OnInitializedAsync , chyba że niestandardowa klasa bazowa jest używana z logiką niestandardową. Aby uzyskać więcej informacji, zobacz sekcję Metody cyklu życia klasy bazowej.
Komponent musi zapewnić, że jest w prawidłowym stanie do renderowania, gdy OnInitializedAsync oczekuje na Task, które jest potencjalnie niekompletne. Jeśli metoda zwraca niekompletną Task, część metody, która kończy się synchronicznie, musi pozostawić składnik w prawidłowym stanie do renderowania. Aby uzyskać więcej informacji, zobacz wprowadzenie Blazor oraz zarządzania składnikami w ASP.NET Core.
Blazor aplikacje, które prerenderują swoją zawartość na serwerze, wywołają OnInitializedAsyncdwa razy:
- Raz, gdy składnik jest początkowo renderowany statycznie jako część strony.
- Po raz drugi, gdy przeglądarka renderuje składnik.
Aby zapobiec dwukrotnemu uruchamianiu kodu OnInitializedAsync dewelopera podczas prerenderingu, zobacz sekcję Stateful reconnection after prerendering. Zawartość w sekcji koncentruje się na Blazor Web Apps i stanowym SignalRponownym połączeniu. Aby zachować stan podczas wykonywania kodu inicjalizacyjnego w trakcie prerenderingu, zobacz Prerenderowanie składników ASP.NET CoreRazor.
Aby zapobiec dwukrotnemu uruchamianiu kodu OnInitializedAsync dewelopera podczas prerenderingu, zobacz sekcję Stateful reconnection after prerendering. Mimo że zawartość w sekcji koncentruje się na stanowym Blazor ServerSignalR, scenariusz prerenderingu w hostowanych rozwiązaniach (Blazor WebAssembly) obejmuje podobne warunki i podejścia, aby zapobiec dwukrotnemu wykonywaniu kodu dewelopera. Aby zachować stan podczas wykonywania kodu inicjowania podczas prerenderingu, zobacz Integrowanie składników ASP.NET Core Razor z MVC lub Razor Pages.
Chociaż aplikacja jest w trakcie wstępnego renderowania Blazor, niektóre akcje, takie jak wywoływanie JavaScriptu (JS interop), nie są możliwe. W przypadku wstępnego wyrenderowania składniki mogą wymagać innego renderowania. Aby uzyskać więcej informacji, zobacz sekcję Prerendering z użyciem interopercji JavaScript.
Jeśli procedury obsługi zdarzeń są udostępniane w kodzie dewelopera, odłącz je przy usuwaniu. Aby uzyskać więcej informacji, zobacz usuwanie komponentów w ASP.NET Core Razor.
Użyj renderowania strumieniowego ze statycznym renderowaniem po stronie serwera (statycznym SSR) lub prerenderingiem, aby ulepszyć środowisko użytkownika dla komponentów, które wykonują długotrwałe zadania asynchroniczne wymagające OnInitializedAsync pełnego renderowania. Aby uzyskać więcej informacji, zobacz następujące zasoby:
Po ustawieniu parametrów (OnParametersSet{Async}
)
OnParametersSet lub OnParametersSetAsync są nazywane:
Po zainicjowaniu składnika w OnInitialized lub OnInitializedAsync.
Kiedy komponent nadrzędny odświeża się i dostarcza:
- Znane lub pierwotne niezmienne typy, gdy co najmniej jeden parametr uległ zmianie.
- Parametry złożone. Platforma nie może wiedzieć, czy wartości parametru typu złożonego zmieniły się wewnętrznie, więc zawsze traktuje zestaw parametrów jako zmieniony, gdy obecny jest co najmniej jeden złożony parametr.
Aby uzyskać więcej informacji na temat konwencji renderowania, zobacz renderowanie składników ASP.NET CoreRazor.
Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
Metody można wywołać, nawet jeśli wartości parametrów nie uległy zmianie. To zachowanie podkreśla potrzebę zaimplementowania przez deweloperów dodatkowej logiki w ramach metod w celu sprawdzenia, czy wartości parametrów rzeczywiście uległy zmianie przed ponownym zainicjowaniem danych lub stanem zależnym od tych parametrów.
W poniższym przykładowym składniku przejdź do strony składnika pod adresem URL:
- Z datą rozpoczęcia, którą otrzymano od
StartDate
:/on-parameters-set/2021-03-19
- Bez daty rozpoczęcia, gdzie
StartDate
jest przypisana wartość bieżącego czasu lokalnego:/on-parameters-set
Uwaga
W trasie składnika nie można jednocześnie ograniczyć parametru DateTime za pomocą ograniczenia trasy datetime
i ustawić parametru jako opcjonalnego. W związku z tym poniższy OnParamsSet
składnik używa dwóch @page
dyrektyw do obsługi routingu z i bez podanego segmentu dat w adresie URL.
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
Praca asynchroniczna przy stosowaniu parametrów i wartości właściwości musi odbywać się podczas zdarzenia cyklu życia OnParametersSetAsync.
protected override async Task OnParametersSetAsync()
{
await ...
}
Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj OnParametersSetAsync na klasie bazowej.
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
Nie jest konieczne wywołanie ComponentBase.OnParametersSetAsync , chyba że niestandardowa klasa bazowa jest używana z logiką niestandardową. Aby uzyskać więcej informacji, zobacz sekcję Metody cyklu życia klasy bazowej.
Komponent musi zapewnić, że jest w prawidłowym stanie do renderowania, gdy OnParametersSetAsync oczekuje na Task, które jest potencjalnie niekompletne. Jeśli metoda zwraca niekompletną Task, część metody, która kończy się synchronicznie, musi pozostawić składnik w prawidłowym stanie do renderowania. Aby uzyskać więcej informacji, zobacz wprowadzenie Blazor oraz zarządzania składnikami w ASP.NET Core.
Jeśli procedury obsługi zdarzeń są udostępniane w kodzie dewelopera, odłącz je przy usuwaniu. Aby uzyskać więcej informacji, zobacz usuwanie komponentów w ASP.NET Core Razor.
Jeśli składnik jednorazowy nie używa CancellationToken, OnParametersSet i OnParametersSetAsync należy sprawdzić, czy składnik jest usunięty. Jeśli OnParametersSetAsync zwraca niekompletne Task, składnik musi upewnić się, że fragment metody kończący się synchronicznie pozostawia składnik w prawidłowym stanie do renderowania. Aby uzyskać więcej informacji, zobacz wprowadzenie Blazor oraz zarządzania składnikami w ASP.NET Core.
Aby uzyskać więcej informacji na temat parametrów i ograniczeń trasy, zobacz ASP.NET Routing i nawigacja podstawowaBlazor.
Aby zapoznać się z przykładem ręcznego implementowania SetParametersAsync
w celu zwiększenia wydajności w niektórych scenariuszach, zobacz ASP.NET Core rendering performance best practices (Najlepsze rozwiązania dotyczące wydajności renderowania podstawowegoBlazor).
Po renderowaniu składnika (OnAfterRender{Async}
)
OnAfterRender i OnAfterRenderAsync są wywoływane po interaktywnym renderowaniu składnika oraz gdy interfejs użytkownika zakończy aktualizację (na przykład po dodaniu elementów do przeglądarki DOM). Odwołania do elementów i składników zostają wypełnione w tym momencie. Ten etap służy do wykonywania dodatkowych kroków inicjalizacji z renderowaną zawartością, takich jak wywołania międzyoperacyjne, które współdziałają z renderowanymi elementami DOM. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
Te metody nie są wywoływane podczas prerenderingu ani statycznego renderowania po stronie serwera (statyczne SSR) na serwerze, ponieważ te procesy nie są dołączone do modelu DOM przeglądarki na żywo i zostały już ukończone przed zaktualizowaniem modelu DOM.
W przypadku OnAfterRenderAsync, komponent nie renderuje się automatycznie ponownie po zakończeniu działania jakiegokolwiek zwróconego Task
, aby uniknąć nieskończonej pętli renderowania.
OnAfterRender i OnAfterRenderAsync są wywoływane po zakończeniu renderowania składnika. Odwołania do elementów i składników zostają wypełnione w tym momencie. Ten etap służy do wykonywania dodatkowych kroków inicjalizacji z renderowaną zawartością, takich jak wywołania międzyoperacyjne, które współdziałają z renderowanymi elementami DOM. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
Te metody nie są wywoływane podczas prerenderingu, ponieważ prerendering nie jest powiązany z żywym DOM-em przeglądarki i jest już ukończony przed jego zaktualizowaniem.
W przypadku OnAfterRenderAsync, komponent nie renderuje się automatycznie ponownie po zakończeniu działania jakiegokolwiek zwróconego Task
, aby uniknąć nieskończonej pętli renderowania.
Parametr firstRender
dla OnAfterRender i OnAfterRenderAsync:
- Jest ustawiany na wartość
true
po raz pierwszy, gdy wystąpienie składnika jest renderowane. - Może służyć do zapewnienia, że praca inicjalizacji jest wykonywana tylko raz.
AfterRender.razor
:
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
Przykład AfterRender.razor
generuje następujące dane wyjściowe do konsoli po załadowaniu strony i po wybraniu przycisku.
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
Asynchroniczna praca musi natychmiast po renderowaniu wystąpić podczas zdarzenia cyklu życia OnAfterRenderAsync.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj OnAfterRenderAsync na klasie bazowej.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
Nie jest konieczne wywołanie ComponentBase.OnAfterRenderAsync , chyba że niestandardowa klasa bazowa jest używana z logiką niestandardową. Aby uzyskać więcej informacji, zobacz sekcję Metody cyklu życia klasy bazowej.
Nawet jeśli zwracasz Task z OnAfterRenderAsync, platforma nie planuje dalszego cyklu renderowania Twojego składnika po zakończeniu tego zadania. Pozwala to uniknąć nieskończonej pętli renderowania. Różni się to od innych metod cyklu życia, które zaplanują kolejny cykl renderowania po zakończeniu zwracanego Task.
OnAfterRender oraz OnAfterRenderAsyncnie są wywoływane podczas procesu prerenderingu na serwerze. Metody są wywoływane, gdy składnik jest renderowany interaktywnie po prerenderingu. Kiedy aplikacja wykonuje wstępne renderowanie:
- Składnik działa na serwerze, aby utworzyć statyczne znacznikowanie HTML w odpowiedzi HTTP. W tej fazie OnAfterRender i OnAfterRenderAsync nie są wywoływane.
-
Blazor Gdy skrypt (
blazor.{server|webassembly|web}.js
) zostanie uruchomiony w przeglądarce, składnik zostanie uruchomiony ponownie w trybie renderowania interakcyjnego. Po ponownym uruchomieniu składnika, OnAfterRender oraz OnAfterRenderAsyncsą wywoływane, ponieważ aplikacja nie znajduje się już w fazie prerenderingu.
Jeśli procedury obsługi zdarzeń są udostępniane w kodzie dewelopera, odłącz je przy usuwaniu. Aby uzyskać więcej informacji, zobacz usuwanie komponentów w ASP.NET Core Razor.
Metody cyklu życia klasy bazowej
Podczas zastępowania metod cyklu życia dla Blazor, nie jest konieczne wywoływanie metod cyklu życia klasy bazowej dla elementu ComponentBase. Jednak składnik powinien wywołać zastąpioną metodę cyklu życia klasy bazowej w następujących sytuacjach:
- Podczas zastępowania ComponentBase.SetParametersAsync, zwykle wywoływana jest metoda
await base.SetParametersAsync(parameters);
, ponieważ metoda klasy bazowej wywołuje inne metody cyklu życia i w złożony sposób wyzwala renderowanie. Aby uzyskać więcej informacji, zobacz sekcję Kiedy parametry są ustawione (SetParametersAsync
). - Jeśli metoda klasy bazowej zawiera logikę, która musi zostać wykonana. Użytkownicy biblioteki zwykle wywołują metody cyklu życia klasy bazowej przy dziedziczeniu, ponieważ klasy bazowe biblioteki często mają niestandardową logikę cyklu życia do wykonania. Jeśli aplikacja używa klasy bazowej z biblioteki, zapoznaj się z dokumentacją biblioteki, aby uzyskać wskazówki.
W poniższym przykładzie, aby upewnić się, że metoda base.OnInitialized();
klasy bazowej zostanie wykonana, wywoływana jest metoda OnInitialized
. Bez wywołania BlazorRocksBase2.OnInitialized
nie jest wykonywane.
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
Zmiany stanu (StateHasChanged
)
StateHasChanged powiadamia składnik, że jego stan uległ zmianie. Jeśli ma to zastosowanie, wywołanie StateHasChanged kolejkuje ponowne renderowanie, które jest inicjowane, gdy główny wątek aplikacji jest wolny.
StateHasChanged są wywoływane automatycznie dla EventCallback metod. Aby uzyskać więcej informacji na temat wywołań zwrotnych zdarzeń, zobacz ASP.NET Core Blazor obsługę zdarzeń.
Aby uzyskać więcej informacji na temat renderowania składników i kiedy wywołać StateHasChanged, w tym kiedy wywołać go za pomocą ComponentBase.InvokeAsync, zobacz ASP.NET Core Razor renderowanie składników.
Obsługa niekompletnych akcji asynchronicznych podczas renderowania
Akcje asynchroniczne wykonywane w zdarzeniach cyklu życia mogły nie zostać ukończone przed renderowaniem składnika. Obiekty mogą być null
lub tylko częściowo zapełnione danymi podczas wykonywania metody cyklu życia. Podaj logikę renderowania, aby potwierdzić, że obiekty są inicjowane. Renderuj zastępcze elementy UI (na przykład komunikat ładowania), podczas gdy obiekty to null
.
W poniższym składniku Slow
, OnInitializedAsync jest zastępowany, aby asynchronicznie wykonać długotrwałe zadanie. Podczas gdy isLoading
jest true
, użytkownikowi wyświetlany jest komunikat ładowania. Po zakończeniu wykonywania Task
zwróconego przez OnInitializedAsync składnik jest przerenderowany z zaktualizowanym stanem, pokazując komunikat "Finished!
".
Slow.razor
:
@page "/slow"
<h2>Slow Component</h2>
@if (isLoading)
{
<div><em>Loading...</em></div>
}
else
{
<div>Finished!</div>
}
@code {
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
isLoading = false;
}
private Task LoadDataAsync()
{
return Task.Delay(10000);
}
}
Poprzedni składnik używa zmiennej isLoading
do wyświetlania komunikatu ładowania. Podobne podejście jest używane w przypadku składnika, który ładuje dane do kolekcji i sprawdza, czy kolekcja jest null
, aby przedstawić komunikat ładowania. Poniższy przykład sprawdza kolekcję movies
dla null
, aby wyświetlić komunikat ładowania lub wyświetlić kolekcję filmów:
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
@* display movies *@
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovies();
}
}
Wstępne renderowanie czeka na quiescence, co oznacza, że komponent nie renderuje, dopóki wszystkie komponenty w drzewie renderowania nie zakończą renderowania. Oznacza to, że komunikat ładowania nie jest wyświetlany, gdy metoda OnInitializedAsync składnika podrzędnego wykonuje długotrwałe zadanie podczas prerenderingu. Aby zademonstrować to zachowanie, umieść poprzedni składnik Slow
w składniku Home
aplikacji testowej:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Slow />
Uwaga
Mimo że w przykładach zawartych w tej konkretnej sekcji omówiono metodę OnInitializedAsync cyklu życia, inne metody cyklu życia wykonywane podczas wstępnego renderowania mogą opóźnić końcowe renderowanie składnika. Tylko OnAfterRender{Async}
nie jest wykonywany podczas prerenderowania i jest odporny na opóźnienia spowodowane ciszą.
Podczas prerenderingu składnik Home
nie jest renderowany, dopóki składnik Slow
nie zostanie renderowany, co trwa dziesięć sekund. Interfejs użytkownika jest pusty w tym dziesięciosekundowym okresie i nie ma komunikatu ładowania. Po wstępnym renderowaniu składnik Home
zostaje wyrenderowany, a komunikat ładowania składnika Slow
jest wyświetlany. Po upływie dziesięciu sekund składnik Slow
w końcu wyświetli gotowy komunikat.
Jak pokazano w poprzednim pokazie, bezczynność podczas prerenderingu skutkuje złym doświadczeniem użytkownika. Aby ulepszyć doświadczenie użytkownika, zacznij od zaimplementowania strumieniowego renderingu, aby uniknąć oczekiwania na ukończenie zadania asynchronicznego podczas prerenderingu.
Dodaj atrybut [StreamRendering]
do składnika Slow
(użyj [StreamRendering(true)]
na platformie .NET 8):
@attribute [StreamRendering]
Gdy składnik Home
jest wstępnie renderowany, składnik Slow
jest szybko renderowany z komunikatem ładowania. Składnik Home
nie czeka na dziesięć sekund na zakończenie renderowania składnika Slow
. Jednak gotowy komunikat wyświetlany na końcu prerenderingu jest zastępowany przez komunikat ładowania, podczas gdy składnik w końcu renderuje, co jest kolejnym dziesięcioma sekundowym opóźnieniem. Dzieje się tak, ponieważ składnik Slow
jest renderowany dwukrotnie, wraz z LoadDataAsync
, który jest też wykonywany dwa razy. Gdy składnik uzyskuje dostęp do zasobów, takich jak usługi i bazy danych, podwójne wykonywanie wywołań usługi i zapytania bazy danych powoduje niepożądane obciążenie zasobów aplikacji.
Aby rozwiązać problem podwójnego renderowania komunikatu ładowania i ponownego wykonywania wywołań usługi i bazy danych, utrwalaj wstępnie wyrenderowany stan z PersistentComponentState na potrzeby końcowego renderowania składnika, jak widać w następujących aktualizacjach dotyczących składnika Slow
.
@page "/slow"
@attribute [StreamRendering]
<h2>Slow Component</h2>
@if (Data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@Data</div>
}
@code {
[SupplyParameterFromPersistentComponentState]
public string? Data { get; set; }
protected override async Task OnInitializedAsync()
{
Data ??= await LoadDataAsync();
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState
<h2>Slow Component</h2>
@if (data is null)
{
<div><em>Loading...</em></div>
}
else
{
<div>@data</div>
}
@code {
private string? data;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
{
data = await LoadDataAsync();
}
else
{
data = restored!;
}
// Call at the end to avoid a potential race condition at app shutdown
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
}
private Task PersistData()
{
ApplicationState.PersistAsJson(nameof(data), data);
return Task.CompletedTask;
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(5000);
return "Finished!";
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}
Łącząc renderowanie strumieniowe z trwałym stanem składnika:
- Usługi i bazy danych wymagają tylko jednego wywołania inicjowania składnika.
- Składniki szybko renderują swoje interfejsy użytkownika, wyświetlając komunikaty ładowania podczas długotrwałych zadań, aby zapewnić jak najlepsze wrażenia użytkownika.
Aby uzyskać więcej informacji, zobacz następujące zasoby:
:::moniker-end
Bezczynność podczas uprzedniego renderowania powoduje słabe doświadczenie użytkownika. Opóźnienie można rozwiązać w aplikacjach przeznaczonych dla platformy .NET 8 lub nowszej z funkcją o nazwie renderowania strumieniowego, zwykle w połączeniu z utrwalaniem stanu składnika podczas wstępnego renderowania, aby uniknąć oczekiwania na zakończenie zadania asynchronicznego. W wersjach platformy .NET starszych niż .NET 8 wykonywanie długotrwałego zadania w tle, które ładują dane po zakończeniu renderowania, może rozwiązać długie opóźnienie renderowania z powodu bezczynności.
Radzenie sobie z błędami
Aby uzyskać informacje na temat obsługi błędów podczas wykonywania metod cyklu życia, zobacz Obsługa błędów w aplikacjach ASP.NET CoreBlazor.
Ponowne łączenie z utrzymaniem stanu po wstępnym renderowaniu
Podczas prerenderingu na serwerze składnik jest początkowo renderowany statycznie jako część strony. Po nawiązaniu przez przeglądarkę połączenia z serwerem SignalR składnik zostaje przetworzony ponownie i staje się interaktywny.
OnInitialized{Async}
Jeśli metoda cyklu życia inicjowania składnika jest obecna, metoda jest wykonywana dwa razy:
- Gdy składnik jest wstępnie renderowany statycznie.
- Po nawiązaniu połączenia z serwerem.
Może to spowodować zauważalną zmianę danych wyświetlanych w interfejsie użytkownika po zakończeniu renderowania składnika. Aby uniknąć tego zachowania, należy przekazać identyfikator, aby zapamiętać stan podczas prerenderingu i odzyskać stan po jego zakończeniu.
Poniższy kod demonstruje element WeatherForecastService
, który pozwala uniknąć zmiany wyświetlania danych z powodu wstępnego przetwarzania. Oczekiwany Delay (await Task.Delay(...)
) symuluje krótkie opóźnienie przed zwróceniem danych z metody GetForecastAsync
.
Dodaj usługi z IMemoryCache do kolekcji usług w pliku AddMemoryCache aplikacji Program
.
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
Aby uzyskać więcej informacji na temat
Zawartość w tej sekcji koncentruje się na
Mimo że zawartość w tej sekcji koncentruje się na połączeniu stanowym Blazor Server i SignalR ponownym nawiązaniu połączenia, scenariusz prerenderingu w hostowanych Blazor WebAssembly rozwiązaniach (WebAssemblyPrerendered) obejmuje podobne warunki i podejścia, aby zapobiec dwukrotnemu wykonywaniu kodu dewelopera. Aby zachować stan podczas wykonywania kodu inicjowania podczas prerenderingu, zobacz Integrowanie składników ASP.NET Core Razor z MVC lub Razor Pages.
Wstępne renderowanie z użyciem funkcji międzyoperacyjnych JavaScript
Ta sekcja dotyczy aplikacji po stronie serwera, które wstępnie renderują Razor składniki. Prerendering jest omówione w Prerender ASP.NET Core komponentyRazor.
Uwaga
Nawigacja wewnętrzna na potrzeby interakcyjnego routingu w Blazor Web App nie obejmuje żądania nowej zawartości strony z serwera. W związku z tym wstępne renderowanie nie występuje w przypadku żądań stron wewnętrznych. Jeśli aplikacja przyjmuje interaktywny routing, wykonaj pełne przeładowanie strony dla przykładów komponentów, które pokazują działanie wstępnego renderowania. Aby uzyskać więcej informacji, zobacz
Ta sekcja dotyczy aplikacji po stronie serwera i hostowanych Blazor WebAssembly aplikacji, które wstępnie renderują Razor składniki. Prerendering jest omówiony w Integrowaniu składników ASP.NET Core Razor z MVC lub Razor Pages.
Podczas wstępnego przetwarzania wywołanie kodu JavaScript (JS) nie jest możliwe. W poniższym przykładzie pokazano, jak używać JS interopu w logice inicjalizacji składnika w sposób zgodny z prerenderingiem.
Następująca scrollElementIntoView
funkcja:
- Przewija do przekazanego elementu za pomocą
scrollIntoView
. - Zwraca wartość właściwości elementu
top
zgetBoundingClientRect
metody .
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Gdzie IJSRuntime.InvokeAsync wywołuje funkcję JS w kodzie składnika, ten ElementReference jest używany tylko w metodzie cyklu życia OnAfterRenderAsync i nie w żadnej wcześniejszej metodzie, ponieważ element DOM HTML nie istnieje do momentu wyrenderowania składnika.
StateHasChanged
(źródło referencyjne) jest wywoływane w celu dodawania do kolejki ponownego renderowania składnika z nowym stanem uzyskanym z JS wywołania międzyoperacyjnego (aby uzyskać więcej informacji, zobacz ASP.NET Core Razor renderowania składników). Pętla nieskończona nie jest tworzona, ponieważ StateHasChanged jest wywoływana tylko wtedy, gdy scrollPosition
ma wartość null
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
Powyższy przykład zanieczyszcza klienta za pomocą funkcji globalnej. Aby uzyskać lepsze podejście do aplikacji produkcyjnych, zobacz izolację języka JavaScript w modułach JavaScript.
Praca w tle z możliwością anulowania
Składniki często wykonują długotrwałe prace w tle, takie jak wykonywanie wywołań sieciowych (HttpClient) i interakcja z bazami danych. Pożądane jest zatrzymanie pracy w tle w celu zaoszczędzenia zasobów systemowych w kilku sytuacjach. Na przykład operacje asynchroniczne w tle nie są automatycznie zatrzymywane, gdy użytkownik przechodzi z dala od składnika.
Inne przyczyny, dla których elementy robocze w tle mogą wymagać anulowania, obejmują:
- Rozpoczęto wykonywanie zadania w tle z błędnymi danymi wejściowymi lub parametrami przetwarzania.
- Bieżący zestaw wykonywania elementów roboczych w tle musi zostać zastąpiony nowym zestawem elementów roboczych.
- Należy zmienić priorytet aktualnie wykonywanych zadań.
- Aplikacja musi zostać zamknięta w celu ponownego wdrożenia serwera.
- Zasoby serwera stają się ograniczone, co wymaga przeplanowania elementów roboczych w tle.
Aby zaimplementować wzorzec pracy w tle z możliwością anulowania w składniku:
- Użyj CancellationTokenSource i CancellationToken.
- Po usunięciu składnika i w dowolnym momencie, gdy pożądane jest ręczne anulowanie tokenu, wywołaj
CancellationTokenSource.Cancel
, aby zasygnalizować, że praca w tle powinna zostać anulowana. - Po zakończeniu wywołania asynchronicznego wykonaj ThrowIfCancellationRequested na tokenie.
W poniższym przykładzie:
-
await Task.Delay(10000, cts.Token);
reprezentuje długotrwałą pracę asynchroniczną w tle. -
BackgroundResourceMethod
reprezentuje długotrwałą metodę w tle, która nie powinna być uruchamiana, jeśliResource
zostanie zneutralizowany przed wywołaniem metody.
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
Aby wyświetlić wskaźnik ładowania podczas wykonywania pracy w tle, użyj następującego podejścia.
Utwórz składnik wskaźnika ładowania z parametrem Loading
, który może wyświetlać zawartość podrzędną wewnątrz RenderFragment. Dla parametru Loading
:
- Kiedy
true
, wyświetl wskaźnik ładowania. - Kiedy
false
, renderuj zawartość komponentu (ChildContent
). Aby uzyskać więcej informacji, zobacz Fragmenty renderowania zawartości podrzędnej.
ContentLoading.razor
:
@if (Loading)
{
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public bool Loading { get; set; }
}
Aby załadować style CSS dla wskaźnika, dodaj je do zawartości <head>
z komponentem HeadContent. Aby uzyskać więcej informacji, zobacz Blazor.
@if (Loading)
{
<!-- OPTIONAL ...
<HeadContent>
<style>
...
</style>
</HeadContent>
-->
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
@ChildContent
}
...
Zawiń znaczniki składnika Razor za pomocą składnika ContentLoading
i przekaż wartość w polu języka C# do parametru Loading
podczas inicjowania przez składnik.
<ContentLoading Loading="@loading">
...
</ContentLoading>
@code {
private bool loading = true;
...
protected override async Task OnInitializedAsync()
{
await LongRunningWork().ContinueWith(_ => loading = false);
}
...
}
Blazor Server zdarzenia ponownego połączenia
Zdarzenia cyklu życia składnika omówione w tym artykule działają oddzielnie od procedur obsługi zdarzeń ponownego łączenia po stronie serwera. Po utracie SignalR połączenia z klientem, przerwane zostaną tylko aktualizacje UI. Aktualizacje interfejsu użytkownika są wznawiane po ponownym nawiązaniu połączenia. Aby uzyskać więcej informacji na temat zdarzeń i konfiguracji programu obsługi obwodu, zobacz wskazówki dotyczące ASP.NET CoreBlazorSignalR.
Dodatkowe zasoby
Obsługa przechwyconych wyjątków poza cyklem życia komponentu Razor