Udostępnij za pośrednictwem


cykl życia składnika ASP.NET Core Razor

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.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). 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.

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 można zastąpić, aby wykonywać dodatkowe operacje w składnikach podczas inicjowania i renderowania składnikó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 Blazorzdarzeń cyklu życia, może być konieczne uzyskanie dostępu do ComponentBase źródła referencyjnego. Komentarze kodu w źródle referencyjnym zawierają dodatkowe uwagi dotyczące przetwarzania zdarzeń cyklu życia, które nie są wyświetlane w tym artykule ani w dokumentacji interfejsu API.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .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:

  1. Jeśli składnik jest renderowy po raz pierwszy na żądanie:
    • Utwórz wystąpienie składnika.
    • Wykonaj iniekcję właściwości.
    • Wywołaj polecenie OnInitialized{Async}. Jeśli zostanie zwrócona niekompletna Task , element jest oczekiwany, Task a następnie składnik jest rerendered. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
  2. Wywołaj polecenie OnParametersSet{Async}. Jeśli zostanie zwrócona niekompletna Task , element jest oczekiwany, Task a następnie składnik jest rerendered. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.
  3. Renderuj dla całej pracy synchronicznej i ukończ Tasks.

Uwaga

Akcje asynchroniczne wykonywane w zdarzeniach cyklu życia mogą nie zostać wykonane przed renderowaniem składnika. Aby uzyskać więcej informacji, zobacz sekcję Obsługa niekompletnych akcji asynchronicznych w sekcji renderowania w dalszej części tego artykułu.

Składnik nadrzędny renderuje się przed składnikami podrzędnymi, ponieważ renderowanie określa, które elementy podrzędne są obecne. Jeśli jest używana synchroniczna inicjowanie składnika nadrzędnego, inicjowanie nadrzędne jest gwarantowane jako pierwsze. Jeśli jest używana asynchroniczna inicjowanie składnika nadrzędnego, nie można określić kolejności ukończenia inicjowania składnika nadrzędnego i podrzędnego, ponieważ zależy od uruchomionego kodu inicjowania.

Zdarzenia cyklu życia składnika w elemencie RazorBlazor

Przetwarzanie zdarzeń DOM:

  1. Zostanie uruchomiona procedura obsługi zdarzeń.
  2. Jeśli zostanie zwrócona niekompletna Task , element jest oczekiwany, Task a następnie składnik jest rerendered.
  3. Renderuj dla całej pracy synchronicznej i ukończ Tasks.

Przetwarzanie zdarzeń DOM

Cykl Render życia:

  1. Unikaj dalszych operacji renderowania w składniku, gdy zostaną spełnione oba następujące warunki:
    • Nie jest to pierwszy render.
    • ShouldRender zwraca wartość false.
  2. Skompiluj różnicę drzewa renderowania (różnicę) i renderuj składnik.
  3. Poczekaj na aktualizację modelu DOM.
  4. Wywołaj polecenie OnAfterRender{Async}. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.

Cykl życia renderowania

Wywołania deweloperów powodują StateHasChanged wyświetlenie elementu rerender. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.

Gdy parametry są ustawione (SetParametersAsync)

SetParametersAsync ustawia parametry dostarczane przez element nadrzędny składnika w drzewie renderowania lub z 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 odpowiednią wartość w obiekcie ParameterView. Parametry, w których nie ma odpowiedniej wartości, ParameterView pozostają niezmienione.

Ogólnie rzecz biorąc, kod powinien wywołać metodę klasy bazowej (await base.SetParametersAsync(parameters);) podczas zastępowania SetParametersAsyncmetody . 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 ComponentBase źródła odwołania podczas tworzenia struktury kodu bez wywoływania metody klasy bazowej, ponieważ wywołuje inne metody cyklu życia i wyzwala renderowanie w złożony sposób.

Uwaga

Linki dokumentacji do źródła referencyjnego platformy .NET zwykle ładują domyślną gałąź repozytorium, która odzwierciedla bieżące programowanie dla następnej wersji platformy .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 ComponentBase.SetParametersAsync renderowania parametrów przychodzących, ale nie są przetwarzane, możesz przekazać pusty ParameterView element do metody klasy bazowej:

await base.SetParametersAsync(ParameterView.Empty);

Jeśli programy obsługi zdarzeń są udostępniane w kodzie dewelopera, usuń ich z usuwania. Aby uzyskać więcej informacji, zobacz sekcję Usuwanie składników i IAsyncDisposableIDisposable .

W poniższym przykładzie przypisuje wartość parametru Param do value parametru, ParameterView.TryGetValue jeśli analizowanie parametru trasy dla Param elementu zakończy się pomyślnie. Gdy value wartość nie nulljest wartością , jest wyświetlana przez składnik.

Mimo że dopasowywanie parametrów trasy jest niewrażliwe na wielkość liter, TryGetValue w szablonie trasy są zgodne tylko nazwy parametrów z uwzględnieniem wielkości liter. Poniższy przykład wymaga użycia elementu /{Param?} w szablonie trasy, aby uzyskać wartość z wartością TryGetValue, a nie /{param?}. Jeśli /{param?} jest używany w tym scenariuszu, TryGetValue zwraca wartość false i message nie jest ustawiona na ciąg 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 i wartości parametrów nie powinny mieć wpływu na inicjowanie wykonywane 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 jest używana synchroniczna inicjalizacja składnika nadrzędnego, inicjowanie składnika nadrzędnego jest gwarantowane przed zainicjowaniem składnika podrzędnego. Jeśli jest używana asynchroniczna inicjowanie składnika nadrzędnego, nie można określić kolejności ukończenia inicjowania składnika nadrzędnego i podrzędnego, ponieważ zależy od uruchomionego kodu inicjowania.

W przypadku operacji synchronicznej przesłoń OnInitializedpolecenie :

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ąpij OnInitializedAsync operator i użyj await operatora :

protected override async Task OnInitializedAsync()
{
    await ...
}

Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj klasę OnInitializedAsync bazową:

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.

Blazor aplikacje, które prerenderują swoją zawartość na serwerze, wywołają OnInitializedAsync dwa 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 (Stanowe ponowne nawiązywanie połączenia po prerenderingu ). Zawartość w sekcji koncentruje się na Blazor Web Appponownym połączeniu s i stanowymSignalR. Aby zachować stan podczas wykonywania kodu inicjowania podczas prerenderingu, zobacz Prerender ASP.NET Core components (Składniki prerender ASP.NET CoreRazor).

Aby zapobiec dwukrotnemu uruchamianiu kodu OnInitializedAsync dewelopera podczas prerenderingu, zobacz sekcję Stateful reconnection after prerendering (Stanowe ponowne nawiązywanie połączenia po prerenderingu ). Mimo że zawartość w sekcji koncentruje się na Blazor Server ponownym połączeniu i SignalRstanowym, scenariusz prerendering 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 Prerender i integrowanie składników ASP.NET CoreRazor.

Chociaż aplikacja jest wstępna Blazor , niektóre akcje, takie jak wywoływanie kodu JavaScript (JS międzyoperacyjnego), nie są możliwe. W przypadku wstępnego wyrenderowania składników może być konieczne renderowanie inaczej. Aby uzyskać więcej informacji, zobacz sekcję Prerendering with JavaScript interop (Prerendering with JavaScript interop( Prerendering with JavaScript interop (Wstępne używanie międzyoperacji języka JavaScript).

Jeśli programy obsługi zdarzeń są udostępniane w kodzie dewelopera, usuń ich z usuwania. Aby uzyskać więcej informacji, zobacz sekcję Usuwanie składników.IDisposableIAsyncDisposable

Użyj renderowania strumieniowego ze statycznym renderowaniem po stronie serwera (statycznym usługą SSR) lub prerenderingiem, aby ulepszyć środowisko użytkownika dla składników, które wykonują długotrwałe zadania asynchroniczne w OnInitializedAsync celu pełnego renderowania. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.

Po ustawieniu parametrów (OnParametersSet{Async})

OnParametersSet lub OnParametersSetAsync są wywoływane:

  • Po zainicjowaniu składnika w elemencie OnInitialized lub OnInitializedAsync.

  • Gdy składnik nadrzędny rerenders 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 mają zmutowane wewnętrznie, więc struktura zawsze traktuje zestaw parametrów w sposób zmieniony, gdy obecny jest co najmniej jeden złożony parametr.

    Aby uzyskać więcej informacji na temat konwencji renderowania, zobacz ASP.NET Renderowanie składników 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 odebraną przez 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 ograniczyć parametru z ograniczeniem datetime trasy i ustawić parametr opcjonalny.DateTime 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 podczas stosowania parametrów i wartości właściwości musi wystąpić podczas OnParametersSetAsync zdarzenia cyklu życia:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj klasę OnParametersSetAsync bazową:

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.

Jeśli programy obsługi zdarzeń są udostępniane w kodzie dewelopera, usuń ich z usuwania. Aby uzyskać więcej informacji, zobacz sekcję Usuwanie składników.IDisposableIAsyncDisposable

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 performance best practices (Najlepsze rozwiązania w zakresie wydajności podstawowego ASP.NET CoreBlazor).

Po renderowaniu składnika (OnAfterRender{Async})

OnAfterRender i OnAfterRenderAsync są wywoływane po interaktywnym renderowaniu składnika, a interfejs użytkownika zakończył aktualizowanie (na przykład po dodaniu elementów do przeglądarki DOM). Odwołania do elementów i składników są wypełniane w tym momencie. Ten etap służy do wykonywania dodatkowych kroków inicjowania z renderowaną zawartością, takich jak JS wywołania międzyoperacyjne, które współdziałają z renderowanych elementów 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 OnAfterRenderAsyncelementu składnik nie automatycznie rerender po zakończeniu żadnego zwróconego Task elementu, 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 są wypełniane w tym momencie. Ten etap służy do wykonywania dodatkowych kroków inicjowania z renderowaną zawartością, takich jak JS wywołania międzyoperacyjne, które współdziałają z renderowanych elementów DOM. Metoda synchroniczna jest wywoływana przed metodą asynchroniczną.

Te metody nie są wywoływane podczas prerenderingu, ponieważ prerendering nie jest dołączony do modelu DOM przeglądarki na żywo i jest już ukończony przed zaktualizowaniem modelu DOM.

W przypadku OnAfterRenderAsyncelementu składnik nie automatycznie rerender po zakończeniu żadnego zwróconego Task elementu, aby uniknąć nieskończonej pętli renderowania.

Parametr firstRender dla OnAfterRender i OnAfterRenderAsync:

  • Jest ustawiany na true pierwszy raz, 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, a przycisk jest zaznaczony:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

Praca asynchroniczna natychmiast po renderowaniu OnAfterRenderAsync musi wystąpić podczas zdarzenia cyklu życia:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

Jeśli niestandardowa klasa bazowa jest używana z niestandardową logiką inicjowania, wywołaj klasę OnAfterRenderAsync bazową:

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 element Task z OnAfterRenderAsyncprogramu , platforma nie planuje dalszego cyklu renderowania 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 cyklu.

OnAfterRender i OnAfterRenderAsync nie są wywoływane podczas procesu prerendering na serwerze. Metody są wywoływane, gdy składnik jest renderowany interaktywnie po prerenderingu. Gdy prerenders aplikacji:

  1. Składnik jest wykonywany na serwerze w celu utworzenia statycznego znaczników HTML w odpowiedzi HTTP. W tej fazie OnAfterRender i OnAfterRenderAsync nie są wywoływane.
  2. 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 i OnAfterRenderAsync wywołaniu jest wywoływana, OnAfterRender ponieważ aplikacja nie znajduje się już w fazie prerenderingu.

Jeśli programy obsługi zdarzeń są udostępniane w kodzie dewelopera, usuń ich z usuwania. Aby uzyskać więcej informacji, zobacz sekcję Usuwanie składników.IDisposableIAsyncDisposable

Metody cyklu życia klasy bazowej

Podczas zastępowania Blazormetod cyklu życia metody cyklu życia nie jest konieczne wywołanie 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.SetParametersAsyncmetoda jest zwykle wywoływana, await base.SetParametersAsync(parameters); ponieważ metoda klasy bazowej wywołuje inne metody cyklu życia i wyzwala renderowanie w złożony sposób. Aby uzyskać więcej informacji, zobacz sekcję When parameters are set (SetParametersAsync).
  • Jeśli metoda klasy bazowej zawiera logikę, która musi zostać wykonana. Użytkownicy biblioteki zwykle nazywają metody cyklu życia klasy bazowej podczas dziedziczenia klasy bazowej, 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 wywoływana jest metoda , aby upewnić się, base.OnInitialized(); że metoda klasy bazowej jest wykonywana 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 rerender, który występuje, gdy główny wątek aplikacji jest wolny.

StateHasChanged metoda jest wywoływana automatycznie dla EventCallback metod. Aby uzyskać więcej informacji na temat wywołań zwrotnych zdarzeń, zobacz obsługa zdarzeń ASP.NET CoreBlazor.

Aby uzyskać więcej informacji na temat renderowania składników i kiedy wywołać StateHasChangedmetodę , w tym czas wywołania za pomocą ComponentBase.InvokeAsyncpolecenia , zobacz ASP.NET Renderowanie składnika CoreRazor.

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 niekompletnie wypełniane danymi podczas wykonywania metody cyklu życia. Podaj logikę renderowania, aby potwierdzić, że obiekty są inicjowane. Renderuj elementy symbolu zastępczego interfejsu użytkownika (na przykład komunikat ładowania), gdy obiekty to null.

W poniższym składniku OnInitializedAsync jest zastępowany asynchronicznie podaj dane klasyfikacji filmów (movies). Gdy movies parametr ma nullwartość , zostanie wyświetlony użytkownikowi komunikat ładowania. Po zakończeniu Task zwracanego przez OnInitializedAsync składnik jest rerendered ze zaktualizowanym stanem.

<h1>Sci-Fi Movie Ratings</h1>

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var movie in movies)
        {
            <li>@movie.Title &mdash; @movie.Rating</li>
        }
    </ul>
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovieRatings(DateTime.Now);
    }
}

Obsługa błędów

Aby uzyskać informacje na temat obsługi błędów podczas wykonywania metody cyklu życia, zobacz Obsługa błędów w aplikacjach ASP.NET CoreBlazor.

Ponowne łączenie stanowe po wstępnym zakończeniu

Podczas prerenderingu na serwerze składnik jest początkowo renderowany statycznie jako część strony. Po nawiązaniu połączenia z serwerem przez przeglądarkę SignalR składnik zostanie ponownie renderowany i interaktywny. OnInitialized{Async} Jeśli metoda cyklu życia inicjowania składnika jest obecna, metoda jest wykonywana dwa razy:

  • Gdy składnik jest wstępnie rozsyłany 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 do pamięci podręcznej stanu podczas prerenderingu i pobrać stan po prerenderingu.

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 GetForecastAsync metody .

Dodaj IMemoryCache usługi w AddMemoryCache kolekcji usług w pliku 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 programu , zobacz wskazówki dotyczące platformy RenderModeASP.NET CoreBlazorSignalR.

Zawartość w tej sekcji koncentruje się na Blazor Web Appponownym połączeniu s i stanowymSignalR. Aby zachować stan podczas wykonywania kodu inicjowania podczas prerenderingu, zobacz Prerender ASP.NET Core components (Składniki prerender ASP.NET CoreRazor).

Mimo że zawartość w tej sekcji koncentruje się na Blazor Server ponownym połączeniu iSignalR stanowym, scenariusz prerendering 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 Prerender i integrowanie składników ASP.NET CoreRazor.

Wstępne używanie międzyoperacji języka JavaScript

Ta sekcja dotyczy aplikacji po stronie serwera, które prerender Razor składniki. Prerendering jest omówiony w składnikach prerender ASP.NET CoreRazor.

Uwaga

Nawigacja wewnętrzna na potrzeby routingu interakcyjnego w Blazor Web Appprogramie nie obejmuje żądania nowej zawartości strony z serwera. W związku z tym wstępne przetwarzanie nie występuje w przypadku żądań stron wewnętrznych. Jeśli aplikacja przyjmuje routing interakcyjny, wykonaj ponowne ładowanie pełnej strony dla przykładów składników, które pokazują zachowanie wstępne. Aby uzyskać więcej informacji, zobacz Prerender ASP.NET Core components (Składniki prerender ASP.NET CoreRazor).

Ta sekcja dotyczy aplikacji po stronie serwera i hostowanych Blazor WebAssembly aplikacji, które prerender Razor składniki. Prerendering jest omówiony w środowisku Prerender i integruje składniki ASP.NET CoreRazor.

Podczas wstępnego przetwarzania wywołanie kodu JavaScript (JS) nie jest możliwe. W poniższym przykładzie pokazano, jak używać JS międzyoperacyjności w ramach logiki inicjowania składnika w sposób zgodny z prerenderingiem.

Następująca scrollElementIntoView funkcja:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

Gdzie IJSRuntime.InvokeAsync wywołuje JS funkcję w kodzie składnika, parametr jest używany tylko w OnAfterRenderAsync metodzie cyklu życia i nie we wcześniejszej metodzie cyklu życia, ElementReference ponieważ nie ma elementu DOM HTML do momentu renderowania składnika.

StateHasChanged(źródło referencyjne) jest wywoływane w celu ściągania rerendering składnika z nowym stanem uzyskanym z JS wywołania międzyoperacji (aby uzyskać więcej informacji, zobacz ASP.NET Renderowanie składników CoreRazor). 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.

Usuwanie składników z elementami IDisposable i IAsyncDisposable

Jeśli składnik implementuje IDisposable element lub IAsyncDisposable, struktura wywołuje usuwanie zasobów po usunięciu składnika z interfejsu użytkownika. Nie polegaj na dokładnym czasie wykonywania tych metod. Na przykład IAsyncDisposable można wyzwalać przed wywołaniem lub po asynchronicznym Task oczekiwanym OnInitalizedAsync w pliku wywołaniu lub zakończeniu. Ponadto kod usuwania obiektów nie powinien zakładać, że istnieją obiekty utworzone podczas inicjowania lub innych metod cyklu życia.

Składniki nie powinny być implementować IDisposable i IAsyncDisposable jednocześnie. Jeśli obie są implementowane, platforma wykonuje tylko przeciążenie asynchroniczne.

Kod dewelopera musi zapewnić, że IAsyncDisposable implementacje nie potrwają długo.

Usuwanie odwołań do obiektów międzyoperacyjnych języka JavaScript

Przykłady w artykułach międzyoperacyjnych języka JavaScript (JS) przedstawiają typowe wzorce usuwania obiektów:

JS Odwołania do obiektów międzyoperacyjności są implementowane jako mapowane przez identyfikator po stronie wywołania międzyoperacyjnego JS , które tworzy odwołanie. Gdy usuwanie obiektu jest inicjowane z platformy .NET lub JS po stronie, Blazor usuwa wpis z mapy, a obiekt może zostać odśmiecany, o ile nie ma innego silnego odwołania do obiektu.

Co najmniej zawsze usuwaj obiekty utworzone po stronie platformy .NET, aby uniknąć wycieku pamięci zarządzanej platformy .NET.

Zadania oczyszczania modelu DOM podczas usuwania składników

Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).

Aby uzyskać wskazówki dotyczące JSDisconnectedException rozłączenia obwodu, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop). Aby uzyskać ogólne wskazówki dotyczące obsługi błędów międzyoperacyjnych języka JavaScript, zobacz sekcję Międzyoperajności języka JavaScript w temacie Obsługa błędów w aplikacjach ASP.NET CoreBlazor.

Synchroniczny IDisposable

W przypadku zadań synchronicznego usuwania użyj polecenia IDisposable.Dispose.

Następujący składnik:

  • Implementuje IDisposable dyrektywę @implementsRazor .
  • Usuwa element obj, który jest typem implementujący IDisposableelement .
  • Jest wykonywane sprawdzanie wartości null, ponieważ obj jest tworzone w metodzie cyklu życia (nie pokazano).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Jeśli pojedynczy obiekt wymaga usunięcia, można użyć lambda do usunięcia obiektu, gdy Dispose jest wywoływany. Poniższy przykład pojawia się w artykule renderowania składnika ASP.NET Core Razor i demonstruje użycie wyrażenia lambda do usuwania elementu Timer.

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Uwaga

W poprzednim przykładzie wywołanie metody StateHasChanged jest opakowane przez wywołanie ComponentBase.InvokeAsync metody , ponieważ wywołanie zwrotne jest wywoływane poza kontekstem Blazorsynchronizacji. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.

Jeśli obiekt jest tworzony w metodzie cyklu życia, takiej jak OnInitialized{Async}, sprawdź, null czy przed wywołaniem metody Dispose.

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

Aby uzyskać więcej informacji, zobacz:

Asynchroniczny IAsyncDisposable

W przypadku zadań usuwania asynchronicznego użyj polecenia IAsyncDisposable.DisposeAsync.

Następujący składnik:

  • Implementuje IAsyncDisposable dyrektywę @implementsRazor .
  • Usuwa element obj, który jest typem niezarządzanym, który implementuje IAsyncDisposableelement .
  • Jest wykonywane sprawdzanie wartości null, ponieważ obj jest tworzone w metodzie cyklu życia (nie pokazano).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Aby uzyskać więcej informacji, zobacz:

null Przypisanie do obiektów usuniętych

Zwykle nie ma potrzeby przypisywania null do obiektów usuniętych po wywołaniu metody/DisposeDisposeAsync . Rzadkie przypadki przypisywania null obejmują następujące elementy:

  • Jeśli typ obiektu jest źle zaimplementowany i nie toleruje powtórzonych wywołań do Dispose/DisposeAsyncmetody , przypisz po null jego usunięciu, aby bezpiecznie pominąć dalsze wywołania metody .Dispose/DisposeAsync
  • Jeśli długotrwały proces nadal przechowuje odwołanie do usuniętego obiektu, przypisanie pozwala modułowi odśmiecania null pamięci zwolnić obiekt pomimo długotrwałego procesu trzymającego odwołanie do niego.

Są to nietypowe scenariusze. W przypadku obiektów, które są implementowane prawidłowo i zachowują się normalnie, nie ma sensu przypisywania null do usuniętych obiektów. W rzadkich przypadkach, w których należy przypisać nullobiekt , zalecamy udokumentowanie przyczyny i szukanie rozwiązania, które uniemożliwia przypisanie elementu null.

StateHasChanged

Uwaga

Wywoływanie i StateHasChanged Dispose DisposeAsync nie jest obsługiwane. StateHasChanged może być wywoływana w ramach usuwania modułu renderowania, więc żądanie aktualizacji interfejsu użytkownika w tym momencie nie jest obsługiwane.

Procedury obsługi zdarzeń

Zawsze anulują subskrypcję programów obsługi zdarzeń platformy .NET. W poniższych Blazor przykładach formularzy pokazano, jak anulować subskrypcję programu obsługi zdarzeń w metodzie Dispose :

  • Podejście do pola prywatnego i lambda

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        private EventHandler<FieldChangedEventArgs>? fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • Metoda prywatna

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

Aby uzyskać więcej informacji, zobacz sekcję Usuwanie składników i IAsyncDisposableIDisposable .

Aby uzyskać więcej informacji na EditForm temat składnika i formularzy, zobacz omówienie formularzy ASP.NET Core Blazor i inne artykuły formularzy w węźle Formularze.

Funkcje anonimowe, metody i wyrażenia

Gdy są używane funkcje anonimowe, metody lub wyrażenia, nie jest konieczne zaimplementowanie IDisposable i anulowanie subskrypcji delegatów. Jednak anulowanie subskrypcji delegata jest problemem , gdy obiekt uwidacznia zdarzenie przeżywa okres istnienia składnika rejestrującego delegata. W takim przypadku następuje wyciek pamięci, ponieważ zarejestrowany delegat zachowuje oryginalny obiekt przy życiu. W związku z tym należy użyć tylko następujących metod, gdy wiadomo, że delegat zdarzenia szybko usuwa. W przypadku wątpliwości co do okresu istnienia obiektów, które wymagają usunięcia, należy zasubskrybować metodę delegata i prawidłowo usunąć delegata, jak pokazano we wcześniejszych przykładach.

  • Metoda anonimowej metody lambda (jawne usuwanie nie jest wymagane):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • Anonimowe podejście wyrażenia lambda (jawne usuwanie nie jest wymagane):

    private ValidationMessageStore? messageStore;
    
    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    Pełny przykład poprzedniego kodu z anonimowymi wyrażeniami lambda pojawia się w artykule weryfikacji formularzy platformy ASP.NET CoreBlazor.

Aby uzyskać więcej informacji, zobacz Oczyszczanie niezarządzanych zasobów i tematy, które są zgodne z nimi dotyczące implementowania Dispose metod i DisposeAsync .

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ędzenie 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 ponownego zmiany czasu elementów roboczych w tle.

Aby zaimplementować wzorzec pracy w tle z możliwością anulowania w składniku:

W poniższym przykładzie:

  • await Task.Delay(5000, cts.Token); reprezentuje długotrwałą pracę asynchroniczną w tle.
  • BackgroundResourceMethod reprezentuje długotrwałą metodę w tle, która nie powinna być uruchamiana, jeśli Resource metoda zostanie usunięta 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;
        }
    }
}

Blazor Server ponowne nawiązywanie połączenia ze zdarzeniami

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 zostaną przerwane tylko aktualizacje interfejsu użytkownika. 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 Razor życia składnika