životní cyklus komponent ASP.NET Core Razor

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Tento článek vysvětluje životní cyklus komponent ASP.NET Core Razor a způsob použití událostí životního cyklu.

Události životního cyklu

Komponenta Razor zpracovává Razor události životního cyklu komponent v sadě synchronních a asynchronních metod životního cyklu. Metody životního cyklu lze přepsat, aby během inicializace a vykreslování komponent prováděly další operace.

Tento článek zjednodušuje zpracováníudálostch Možná budete muset získat přístup k referenčnímu ComponentBase zdroji , abyste mohli integrovat vlastní zpracování událostí se Blazorzpracováním událostí životního cyklu. Komentáře ke kódu v referenčním zdroji zahrnují další poznámky ke zpracování událostí životního cyklu, které se nezobrazují v tomto článku nebo v dokumentaci k rozhraní API.

Poznámka:

Odkazy na dokumentaci k referenčnímu zdroji .NET obvykle načítají výchozí větev úložiště, která představuje aktuální vývoj pro příští verzi .NET. Pokud chcete vybrat značku pro konkrétní verzi, použijte rozevírací seznam pro přepnutí větví nebo značek. Další informace najdete v tématu Jak vybrat značku verze zdrojového kódu ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Následující zjednodušené diagramy znázorňují Razor zpracování událostí životního cyklu součástí. Metody C# přidružené k událostem životního cyklu jsou definovány příklady v následujících částech tohoto článku.

Události životního cyklu komponent:

  1. Pokud se komponenta vykresluje poprvé na požadavku:
    • Vytvořte instanci komponenty.
    • Proveďte injektáž vlastností.
    • Zavolejte OnInitialized{Async}. Pokud je vrácen neúplný Task , Task je očekávána a pak se komponenta znovu vyřeší. Synchronní metoda se volá před asychronousovou metodou.
  2. Zavolejte OnParametersSet{Async}. Pokud je vrácen neúplný Task , Task je očekávána a pak se komponenta znovu vyřeší. Synchronní metoda se volá před asychronousovou metodou.
  3. Vykreslení pro všechny synchronní práce a dokončení Task

Poznámka:

Asynchronní akce prováděné v událostech životního cyklu nemusí být dokončeny před vykresleným komponentou. Další informace najdete v části Zpracování neúplných asynchronních akcí v části vykreslení dále v tomto článku.

Nadřazená komponenta se vykresluje před podřízenými komponentami, protože vykreslování určuje, které podřízené položky jsou přítomné. Pokud se použije synchronní inicializace nadřazené komponenty, je zaručeno, že se nejprve dokončí inicializace nadřazené součásti. Pokud se používá asynchronní inicializace nadřazené komponenty, pořadí dokončení inicializace nadřazené a podřízené komponenty nelze určit, protože závisí na spuštěném inicializačním kódu.

Události životního cyklu komponent v komponentě RazorBlazor

Zpracování událostí MODELU DOM:

  1. Obslužná rutina události se spustí.
  2. Pokud je vrácen neúplný Task , Task je očekávána a pak se komponenta znovu vyřeší.
  3. Vykreslení pro všechny synchronní práce a dokončení Task

Zpracování událostí MODELU DOM

Životní Render cyklus:

  1. Pokud jsou splněny obě následující podmínky, vyhněte se dalším operacím vykreslování komponenty:
    • Nejedná se o první vykreslení.
    • ShouldRender vrátí false.
  2. Sestavte rozdíl stromu vykreslování (rozdíl) a vykreslujte komponentu.
  3. Čeká na aktualizaci DOM.
  4. Zavolejte OnAfterRender{Async}. Synchronní metoda se volá před asychronousovou metodou.

Životní cyklus vykreslování

Volání vývojářů, která mají vést k StateHasChanged vykreslení Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor.

Při nastavení parametrů (SetParametersAsync)

SetParametersAsync nastaví parametry zadané nadřazeným prvkem komponenty ve stromu vykreslení nebo z parametrů trasy.

Parametr metody ParameterView obsahuje sadu hodnot parametrů komponenty pro komponentu pokaždé, když SetParametersAsync je volána. Přepsáním SetParametersAsync metody může kód vývojáře pracovat přímo s ParameterViewparametry.

Výchozí implementace SetParametersAsync nastaví hodnotu každé vlastnosti s atributem [Parameter][CascadingParameter], který má odpovídající hodnotu v objektu .ParameterView Parametry, které nemají odpovídající hodnotu, ParameterView zůstanou beze změny.

Obecně platí, že kód by měl při přepsání SetParametersAsyncvolat metodu základní třídy (await base.SetParametersAsync(parameters);). V pokročilých scénářích může vývojářský kód interpretovat hodnoty příchozích parametrů jakýmkoli způsobem, protože nevyvolá metodu základní třídy. Například není nutné přiřazovat příchozí parametry vlastnostem třídy. Při strukturování kódu bez volání metody základní třídy však musíte odkazovat na ComponentBase referenční zdroj , protože volá jiné metody životního cyklu a spouští vykreslování komplexním způsobem.

Poznámka:

Odkazy na dokumentaci k referenčnímu zdroji .NET obvykle načítají výchozí větev úložiště, která představuje aktuální vývoj pro příští verzi .NET. Pokud chcete vybrat značku pro konkrétní verzi, použijte rozevírací seznam pro přepnutí větví nebo značek. Další informace najdete v tématu Jak vybrat značku verze zdrojového kódu ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Pokud chcete spoléhat na inicializaci a vykreslení logiky příchozích ComponentBase.SetParametersAsync parametrů, ale ne zpracovat, máte možnost předat prázdnou ParameterView metodu základní třídy:

await base.SetParametersAsync(ParameterView.Empty);

Pokud jsou obslužné rutiny událostí k dispozici v kódu vývojáře, zrušte jejich odstranění. Další informace najdete v části Vyřazení komponent a IDisposableIAsyncDisposable části.

V následujícím příkladu přiřadí hodnotu parametruParam, ParameterView.TryGetValue pokud value je analýza parametru Param trasy úspěšná. Pokud value není null, tato hodnota se zobrazí komponentou.

I když porovnávání parametrů trasy nerozlišuje velká a malá písmena, TryGetValue odpovídá pouze názvům parametrů rozlišujících malá a velká písmena v šabloně trasy. Následující příklad vyžaduje použití /{Param?} v šabloně trasy k získání hodnoty s TryGetValue, nikoli /{param?}. Pokud /{param?} se v tomto scénáři používá, TryGetValue vrátí false hodnotu a message není nastavená na jeden message řetězec.

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?}"

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

Inicializace komponent (OnInitialized{Async})

OnInitialized a OnInitializedAsync slouží výhradně k inicializaci komponenty po celou dobu životnosti instance komponenty. Změny hodnot parametrů a hodnot parametrů by neměly mít vliv na inicializaci provedenou v těchto metodách. Například načtení statických možností do rozevíracího seznamu, který se nemění po celou dobu životnosti komponenty a který není závislý na hodnotách parametrů, se provádí v některé z těchto metod životního cyklu. Pokud hodnoty parametrů nebo změny hodnot parametrů ovlivňují stav součásti, použijte OnParametersSet{Async} místo toho.

Tyto metody jsou vyvolány při inicializaci komponenty po přijetí jeho počátečních parametrů v SetParametersAsync. Synchronní metoda se volá před asynchronní metodou.

Pokud se použije synchronní inicializace nadřazené komponenty, je zaručeno dokončení nadřazené inicializace před inicializací podřízené komponenty. Pokud se používá asynchronní inicializace nadřazené komponenty, pořadí dokončení inicializace nadřazené a podřízené komponenty nelze určit, protože závisí na spuštěném inicializačním kódu.

Pro synchronní operaci přepište 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"

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

Pokud chcete provést asynchronní operaci, přepište OnInitializedAsync a použijte await operátor:

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

Pokud se vlastní základní třída používá s logikou vlastní inicializace, zavolejte OnInitializedAsync základní třídu:

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

    await base.OnInitializedAsync();
}

Není nutné volat ComponentBase.OnInitializedAsync , pokud se vlastní základní třída nepoužívá s vlastní logikou. Další informace naleznete v části Metody životního cyklu základní třídy.

Blazor aplikace, které předvolají svůj obsah na serveru, volají OnInitializedAsyncdvakrát:

  • Jakmile se komponenta zpočátku vykreslí staticky jako součást stránky.
  • Podruhé, když prohlížeč vykreslí komponentu.

Pokud chcete zabránit tomu, aby kód vývojáře při OnInitializedAsync předkonenderování běžel dvakrát, podívejte se na stavové opětovné připojení po předkončování oddílu. Obsah v části se zaměřuje na Blazor Web Apps a stavové SignalRopětovné připojení. Chcete-li zachovat stav během provádění inicializačního kódu při předrenderování, přečtěte si téma Prerender ASP.NET Základní Razor komponenty.

Pokud chcete zabránit tomu, aby kód vývojáře při OnInitializedAsync předkonenderování běžel dvakrát, podívejte se na stavové opětovné připojení po předkončování oddílu. I když se obsah v této části zaměřuje na Blazor Server stavové SignalRopětovné připojení, scénář předřazení v hostovaných Blazor WebAssembly řešeníchWebAssemblyPrerendered () zahrnuje podobné podmínky a přístupy, aby se zabránilo dvojímu spuštění kódu vývojáře. Chcete-li zachovat stav během provádění inicializačního kódu při předrenderování, přečtěte si téma Prerender a integrace komponent ASP.NET CoreRazor.

Blazor I když je aplikace předem vyřazující, některé akce, jako je volání do JavaScriptu (JSinterop), nejsou možné. Komponenty se můžou při předkreslování vykreslit odlišně. Další informace najdete v části Prerendering s javascriptovou interoperabilitou .

Pokud jsou obslužné rutiny událostí k dispozici v kódu vývojáře, zrušte jejich odstranění. Další informace najdete v části Vyřazení součástí.IDisposableIAsyncDisposable

Vykreslování streamování s vykreslováním na straně statického serveru (static SSR) nebo předběžné vykreslování můžete zlepšit uživatelské prostředí pro komponenty, které provádějí dlouhotrvající asynchronní úlohy OnInitializedAsync , aby bylo možné plně vykreslit. Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor.

Po nastavení parametrů (OnParametersSet{Async})

OnParametersSet nebo OnParametersSetAsync jsou volána:

  • Po inicializaci komponenty nebo OnInitializedOnInitializedAsync.

  • Když nadřazená komponenta rerenderuje a dodává:

    • Známé nebo primitivní neměnné typy, pokud se alespoň jeden parametr změnil.
    • Komplexní parametry typu Architektura nedokáže zjistit, jestli hodnoty parametru s komplexním typem interně ztlumily, takže architektura vždy považuje sadu parametrů za změněnou, pokud jsou přítomny jeden nebo více komplexních parametrů.

    Další informace o konvencích vykreslování najdete v tématu ASP.NET vykreslování komponent CoreRazor.

Synchronní metoda se volá před asychronousovou metodou.

Metody lze vyvolat i v případě, že se hodnoty parametrů nezměnily. Toto chování podtržítko nutnost, aby vývojáři implementovali do metod další logiku, aby zkontrolovali, jestli se hodnoty parametrů skutečně změnily před opětovnou inicializací dat nebo stavu závislých na těchto parametrech.

V následujícím příkladu přejděte na stránku komponenty na adrese URL:

  • S počátečním datem, které přijal StartDate: /on-parameters-set/2021-03-19
  • Bez počátečního data, kde StartDate je přiřazena hodnota aktuálního místního času: /on-parameters-set

Poznámka:

V trase komponenty není možné omezit DateTime parametr s omezením datetime trasy a nastavit parametr jako volitelný. Následující OnParamsSet komponenta proto používá dvě @page direktivy ke zpracování směrování s zadaným segmentem data v adrese URL a bez zadaného segmentu kalendářního data.

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

<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}).";
        }
    }
}

Asynchronní práce při použití parametrů a hodnot vlastností musí proběhnout během OnParametersSetAsync události životního cyklu:

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

Pokud se vlastní základní třída používá s logikou vlastní inicializace, zavolejte OnParametersSetAsync základní třídu:

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

    await base.OnParametersSetAsync();
}

Není nutné volat ComponentBase.OnParametersSetAsync , pokud se vlastní základní třída nepoužívá s vlastní logikou. Další informace naleznete v části Metody životního cyklu základní třídy.

Pokud jsou obslužné rutiny událostí k dispozici v kódu vývojáře, zrušte jejich odstranění. Další informace najdete v části Vyřazení součástí.IDisposableIAsyncDisposable

Další informace o parametrech trasy a omezeních najdete v tématu ASP.NET Blazor Základní směrování a navigace.

Příklad ruční implementace SetParametersAsync pro zlepšení výkonu v některých scénářích najdete v osvědčených postupech ASP.NET CoreBlazor.

Po vykreslení komponenty (OnAfterRender{Async})

OnAfterRender a OnAfterRenderAsync jsou vyvolány po interaktivním vykreslení komponenty a uživatelské rozhraní se dokončilo aktualizace (například po přidání prvků do počítače DOM prohlížeče). V tomto okamžiku se vyplní odkazy na elementy a komponenty. Tato fáze slouží k provedení dalších kroků inicializace s vykresleným obsahem, jako JS jsou volání zprostředkovatele komunikace, která pracují s vykreslovanými prvky MODELU DOM. Synchronní metoda se volá před asychronousovou metodou.

Tyto metody se nevyvolávají při předběžném vykreslování nebo statickém vykreslování na straně serveru (statické SSR), protože tyto procesy nejsou připojené k živému modelu DOM prohlížeče a jsou již dokončené před aktualizací modelu DOM.

Pro OnAfterRenderAsync, komponenta se po dokončení všech vrácených Task dat automaticky nepředá, aby se zabránilo nekonečné smyčce vykreslení.

OnAfterRender a OnAfterRenderAsync jsou volána po dokončení vykreslování komponenty. V tomto okamžiku se vyplní odkazy na elementy a komponenty. Tato fáze slouží k provedení dalších kroků inicializace s vykresleným obsahem, jako JS jsou volání zprostředkovatele komunikace, která pracují s vykreslovanými prvky MODELU DOM. Synchronní metoda se volá před asychronousovou metodou.

Tyto metody nejsou vyvolány během předrenderingu, protože prerendering není připojen k živému modelu DOM prohlížeče a je již dokončen před aktualizací modelu DOM.

Pro OnAfterRenderAsync, komponenta se po dokončení všech vrácených Task dat automaticky nepředá, aby se zabránilo nekonečné smyčce vykreslení.

Parametr firstRender pro OnAfterRender a OnAfterRenderAsync:

  • Je nastavena na true poprvé, kdy se instance komponenty vykresluje.
  • Dá se použít k zajištění, že se inicializační práce provádí jenom jednou.

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

Při načtení stránky a výběru tlačítka vytvoří ukázka AfterRender.razor následující výstup do konzoly:

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

Asynchronní práce okamžitě po vykreslení musí proběhnout během OnAfterRenderAsync události životního cyklu:

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

Pokud se vlastní základní třída používá s logikou vlastní inicializace, zavolejte OnAfterRenderAsync základní třídu:

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

    await base.OnAfterRenderAsync(firstRender);
}

Není nutné volat ComponentBase.OnAfterRenderAsync , pokud se vlastní základní třída nepoužívá s vlastní logikou. Další informace naleznete v části Metody životního cyklu základní třídy.

I když vrátíte hodnotu z TaskOnAfterRenderAsync, architektura neplánuje další cyklus vykreslení pro vaši komponentu po dokončení této úlohy. Tím se vyhnete nekonečné smyčce vykreslování. To se liší od ostatních metod životního cyklu, které plánují další cyklus vykreslení po dokončení vrácení Task .

OnAfterRender a OnAfterRenderAsyncběhem předběžného procesu na serveru se nevolá. Metody se volají, když se komponenta vykreslí interaktivně po předkreslování. Když aplikace prerenders:

  1. Komponenta se spustí na serveru, aby v odpovědi HTTP provedla určitou statickou značku HTML. Během této fáze OnAfterRender se OnAfterRenderAsync nevolá.
  2. Když se Blazor skript (blazor.{server|webassembly|web}.js) spustí v prohlížeči, komponenta se restartuje v interaktivním režimu vykreslování. Jakmile se komponenta restartuje a OnAfterRenderAsynczavolá se, OnAfterRender protože aplikace už není ve fázi předrenderingu.

Pokud jsou obslužné rutiny událostí k dispozici v kódu vývojáře, zrušte jejich odstranění. Další informace najdete v části Vyřazení součástí.IDisposableIAsyncDisposable

Metody životního cyklu základní třídy

Při přepsání Blazormetod životního cyklu není nutné volat metody životního cyklu základní třídy pro ComponentBase. Komponenta by však měla v následujících situacích volat metodu životního cyklu přepsané základní třídy:

  • Při přepsání ComponentBase.SetParametersAsyncje await base.SetParametersAsync(parameters); obvykle vyvolána, protože metoda základní třídy volá jiné metody životního cyklu a aktivuje vykreslování složitým způsobem. Další informace najdete v části Kdy jsou parametry nastaveny (SetParametersAsync).
  • Pokud metoda základní třídy obsahuje logiku, která se musí spustit. Příjemci knihovny obvykle volají metody životního cyklu základní třídy při dědění základní třídy, protože základní třídy knihovny často mají vlastní logiku životního cyklu ke spuštění. Pokud aplikace používá základní třídu z knihovny, pokyny najdete v dokumentaci ke knihovně.

V následujícím příkladu je volána, base.OnInitialized(); aby se zajistilo, že se spustí metoda základní třídy OnInitialized . Bez volání BlazorRocksBase2.OnInitialized se nespustí.

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

Změny stavu (StateHasChanged)

StateHasChanged oznámí komponentě, že se změnil její stav. Pokud je to možné, volání StateHasChanged způsobí, že se komponenta znovu vyřeší.

StateHasChanged se volá automaticky pro EventCallback metody. Další informace o zpětných voláních událostí najdete v tématu ASP.NET Zpracování událostí CoreBlazor.

Další informace o vykreslování komponent a o tom, kdy se má volat StateHasChanged, včetně toho, kdy ji ComponentBase.InvokeAsyncvyvolat , najdete v tématu ASP.NET vykreslování komponent CoreRazor.

Zpracování neúplných asynchronních akcí při vykreslování

Asynchronní akce prováděné v událostech životního cyklu nemusí být dokončeny, než se komponenta vykresluje. Objekty můžou být null během provádění metody životního cyklu nebo neúplně vyplněné daty. Zadejte logiku vykreslování pro potvrzení inicializace objektů. Vykreslujte zástupné prvky uživatelského rozhraní (například načítání zprávy), zatímco objekty jsou null.

V následující komponentě je přepsán, OnInitializedAsync aby asynchronně poskytoval data hodnocení filmů (movies). Kdy movies se nulluživateli zobrazí zpráva o načítání. Task Po dokončení vrácení OnInitializedAsync se komponenta znovu zobrazí s aktualizovaným stavem.

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

Zpracování chyb

Informace o zpracování chyb během provádění metody životního cyklu najdete v tématu Zpracování chyb v aplikacích ASP.NET CoreBlazor.

Stavové opětovné připojení po předkončování

Při předběžném vykreslování na serveru se komponenta zpočátku staticky vykresluje jako součást stránky. Jakmile prohlížeč naváže SignalR připojení zpět k serveru, komponenta se znovu vykreslí a interaktivně. OnInitialized{Async} Pokud je k dispozici metoda životního cyklu pro inicializaci komponenty, spustí se metoda dvakrát:

  • Pokud je komponenta předem předkreslována staticky.
  • Po navázání připojení k serveru.

To může mít za následek znatelnou změnu dat zobrazených v uživatelském rozhraní, když se komponenta nakonec vykresluje. Chcete-li se tomuto chování vyhnout, předejte identifikátor pro uložení stavu do mezipaměti během předrenderingu a načtení stavu po předřažení.

Následující kód ukazuje WeatherForecastService , že se vyhnete změně zobrazení dat z důvodu předdekreslování. Očekávaná Delay (await Task.Delay(...)) simuluje krátkou prodlevu před vrácením dat z GetForecastAsync metody.

Přidejte IMemoryCache služby do AddMemoryCache kolekce služeb v souboru aplikace 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;

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

Další informace o tomto RenderModetématu najdete v ASP.NET základních BlazorSignalR doprovodných materiálech.

Obsah v této části se zaměřuje na Blazor Web Apps a stavové SignalRopětovné připojení. Chcete-li zachovat stav během provádění inicializačního kódu při předrenderování, přečtěte si téma Prerender ASP.NET Základní Razor komponenty.

I když se obsah v této části zaměřuje na Blazor Server stavové SignalRopětovné připojení, scénář předřazení v hostovaných Blazor WebAssembly řešeních (WebAssemblyPrerendered) zahrnuje podobné podmínky a přístupy, aby se zabránilo dvojímu spuštění kódu vývojáře. Chcete-li zachovat stav během provádění inicializačního kódu při předrenderování, přečtěte si téma Prerender a integrace komponent ASP.NET CoreRazor.

Prerendering with JavaScript interop

Tato část se týká aplikací na straně serveru, které prerenderovat Razor komponenty. Prerendering je pokrytý komponentami Prerender ASP.NET CoreRazor.

Poznámka:

Interní navigace pro interaktivní směrování ve Blazor Službě Web Apps nezahrnuje vyžádání nového obsahu stránky ze serveru. Proto u interních požadavků na stránku nedojde k předběžnému provedení. Pokud aplikace přijme interaktivní směrování, proveďte opětovné načtení celé stránky pro příklady komponent, které demonstrují chování předběžného formátování. Další informace naleznete v tématu Prerender ASP.NET Základní Razor komponenty.

Tato část se týká aplikací na straně serveru a hostovaných Blazor WebAssembly aplikací, které předem vyřadí Razor komponenty. Prerendering je popsaný v prerenderu a integruje ASP.NET základní Razor komponenty.

I když je aplikace předřazení, některé akce, jako je volání do JavaScriptu (JS), nejsou možné.

V následujícím příkladu setElementText1 je funkce volána a JSRuntimeExtensions.InvokeVoidAsync nevrací hodnotu.

Poznámka:

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu o umístění JavaScriptu v aplikacích ASP.NET CoreBlazor.

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Upozorňující

Předchozí příklad upraví model DOM přímo pouze pro demonstrační účely. Ve většině scénářů se přímá úprava DOM JS nedoporučuje, protože JS může kolidovat se Blazorsledováním změn. Další informace najdete v tématu ASP.NET Interoperabilita Core Blazor JavaScriptu (JSinteroperabilita).

Událost OnAfterRender{Async} životního cyklu se během předběžného procesu na serveru nevolá. Přepište metodu OnAfterRender{Async} zpoždění JS volání zprostředkovatele, dokud se komponenta nevykreslí a interaktivní na klientovi po předběžném vykreslení.

PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Poznámka:

Předchozí příklad znečišťuje klienta globálními funkcemi. Lepší přístup v produkčních aplikacích najdete v tématu Izolace JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText1 = (element, text) => element.innerText = text;

Následující komponenta ukazuje, jak používat JS interoperabilitu jako součást inicializační logiky komponenty způsobem, který je kompatibilní s předdeenderováním. Komponenta ukazuje, že je možné aktivovat aktualizaci vykreslování z vnitřní OnAfterRenderAsyncčásti . Vývojář musí být opatrní, aby se zabránilo vytvoření nekonečné smyčky v tomto scénáři.

V následujícím příkladu setElementText2 je funkce volána s IJSRuntime.InvokeAsync a vrací hodnotu.

Poznámka:

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu o umístění JavaScriptu v aplikacích ASP.NET CoreBlazor.

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Upozorňující

Předchozí příklad upraví model DOM přímo pouze pro demonstrační účely. Ve většině scénářů se přímá úprava DOM JS nedoporučuje, protože JS může kolidovat se Blazorsledováním změn. Další informace najdete v tématu ASP.NET Interoperabilita Core Blazor JavaScriptu (JSinteroperabilita).

Kde JSRuntime.InvokeAsync je volána, se používá pouze v OnAfterRenderAsync a ne v žádné dřívější metodě životního cyklu, ElementReference protože neexistuje žádný prvek HTML DOM, dokud se komponenta nevykreslí.

StateHasChangedje volána k opětovnému vykreslení komponenty s novým stavem získaným z JS volání zprostředkovatele komunikace (další informace najdete v tématu ASP.NET vykreslování komponent CoreRazor). Kód nevytvoří nekonečnou smyčku, protože StateHasChanged je volána pouze tehdy, když data je null.

PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call: 
    <strong id="val-set-by-interop" @ref="divElement"></strong>
</p>



@code {
    private string? infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Poznámka:

Předchozí příklad znečišťuje klienta globálními funkcemi. Lepší přístup v produkčních aplikacích najdete v tématu Izolace JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Vyřazení součástí s IDisposableIAsyncDisposable

Pokud komponenta implementuje IDisposable nebo IAsyncDisposable, architektura volá odstranění prostředků, když je komponenta odebrána z uživatelského rozhraní. Nespoléhejte na přesné načasování spuštění těchto metod. Může se například IAsyncDisposable aktivovat před nebo po asychronózním Task očekávaném OnInitalizedAsync vstupu nebo dokončení. Kód pro odstranění objektů by také neměl předpokládat, že objekty vytvořené během inicializace nebo jiných metod životního cyklu existují.

Komponenty by se neměly implementovat IDisposable ani IAsyncDisposable současně. Pokud jsou oba implementované, architektura spustí pouze asynchronní přetížení.

Vývojářský kód musí zajistit, aby IAsyncDisposable dokončení implementací trvalo příliš dlouho.

Vyřazení odkazů na objekty zprostředkovatele komunikace JavaScriptu

Příklady v článcích interoperability JavaScriptu (JS) ukazují typické vzory odstranění objektů:

JS odkazy na objekty vzájemné spolupráce jsou implementovány jako mapované pomocí identifikátoru na straně JS volání zprostředkovatele komunikace, který vytvoří odkaz. Když je odstranění objektu inicializováno z rozhraní .NET nebo JS ze strany, Blazor odebere položku z mapy a objekt může být uvolněn z paměti, pokud neexistuje žádný jiný silný odkaz na objekt.

Minimálně vždy odstraňte objekty vytvořené na straně .NET, aby nedošlo k úniku spravované paměti .NET.

Úlohy čištění DOM během odstraňování komponent

Další informace najdete v tématu ASP.NET Interoperabilita Core Blazor JavaScriptu (JSinteroperabilita).

Pokyny k JSDisconnectedException odpojení okruhu najdete v tématu ASP.NET interoperabilita Core Blazor JavaScriptu (JSinteroperabilita). Obecné pokyny pro zpracování chyb interoperability JavaScriptu najdete v části Interoperability JavaScriptu v tématu Zpracování chyb v aplikacích ASP.NET CoreBlazor.

Synchronní IDisposable

Pro synchronní úlohy odstraňování použijte IDisposable.Dispose.

Následující komponenta:

  • Implementuje IDisposable se direktivou @implementsRazor .
  • Disposes of obj, což je typ, který implementuje IDisposable.
  • Provede se kontrola null, protože obj se vytvoří v metodě životního cyklu (není zobrazeno).
@implements IDisposable

...

@code {
    ...

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

Pokud jeden objekt vyžaduje odstranění, lze lambda použít k odstranění objektu, když Dispose je volána. Následující příklad se zobrazí v článku o vykreslování komponenty ASP.NET Core Razor a ukazuje použití výrazu lambda k odstranění 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();
}

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

Poznámka:

V předchozím příkladu je volání StateHasChanged zabaleno voláním ComponentBase.InvokeAsync , protože zpětné volání je vyvoláno mimo Blazorkontext synchronizace. Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor.

Pokud je objekt vytvořen v metodě životního cyklu, například OnInitialized{Async}, zkontrolujte null před voláním 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();
}

Další informace naleznete v tématu:

Asynchronní IAsyncDisposable

Pro asynchronní odstraňování úkolů použijte IAsyncDisposable.DisposeAsync.

Následující komponenta:

  • Implementuje IAsyncDisposable se direktivou @implementsRazor .
  • Disposes of obj, což je nespravovaný typ, který implementuje IAsyncDisposable.
  • Provede se kontrola null, protože obj se vytvoří v metodě životního cyklu (není zobrazeno).
@implements IAsyncDisposable

...

@code {
    ...

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

Další informace naleznete v tématu:

null Přiřazení k vyřazeným objektům

Obvykle není nutné přiřazovat null k odstraněným objektům po volání/DisposeDisposeAsync . Mezi vzácné případy přiřazování null patří:

  • Pokud je typ objektu špatně implementován a netoleruje opakované volání Dispose/DisposeAsync, přiřaďte null po odstranění, aby bylo možné elegantně přeskočit další volání .Dispose/DisposeAsync
  • Pokud dlouhodobý proces nadále uchovává odkaz na odstraněný objekt, přiřazení null umožňuje uvolňování paměti uvolnit objekt i přes dlouhotrvající proces, který na něj uchovává odkaz.

Jedná se o neobvyklé scénáře. Pro objekty, které jsou implementovány správně a chovají se normálně, neexistuje žádný bod přiřazování null k odstraněným objektům. Ve výjimečných případech, kdy musí být objekt přiřazen null, doporučujeme zdokumentovat důvod a vyhledat řešení, které brání nutnosti přiřazovat null.

StateHasChanged

Poznámka:

Dispose Volání StateHasChanged a DisposeAsync nepodporuje se. StateHasChanged může být vyvolána jako součást odstraňování rendereru, takže vyžádání aktualizací uživatelského rozhraní v tomto okamžiku není podporováno.

Obslužné rutiny událostí

Obslužné rutiny událostí vždy odhlaste z událostí .NET. Následující Blazor příklady formulářů ukazují, jak v metodě odhlásit obslužnou rutinu Dispose události:

  • Přístup k privátnímu poli a 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;
        }
    }
    
  • Přístup privátní metody

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

Další informace najdete v části Vyřazení komponent a IDisposableIAsyncDisposable části.

Další informace o komponentách EditForm a formulářích najdete v tématu ASP.NET Blazor Přehled základních formulářů a další články formulářů v uzlu Formuláře .

Anonymní funkce, metody a výrazy

Pokud se používají anonymní funkce, metody nebo výrazy, není nutné implementovat IDisposable a odhlásit delegáty. Pokud se však nepodaří zrušit odběr delegáta, je problém , když objekt, který vystavuje událost, prožije dobu života komponenty, která delegáta registruje. Pokud k tomu dojde, dojde k nevrácení paměti, protože registrovaný delegát zachová původní objekt naživu. Proto používejte následující přístupy pouze v případě, že víte, že delegát události rychle odstraní. Pokud máte pochybnosti o životnosti objektů, které vyžadují odstranění, přihlaste se k odběru metody delegáta a správně odstraňte delegáta, jak ukazují předchozí příklady.

  • Anonymní přístup metody lambda (explicitní vyřazení není vyžadováno):

    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);
    }
    
  • Přístup k anonymnímu výrazu lambda (explicitní odstranění se nevyžaduje):

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

    Úplný příklad předchozího kódu s anonymními výrazy lambda se zobrazí v článku o ověřování formulářů ASP.NET CoreBlazor.

Další informace najdete v tématu Čištění nespravovaných prostředků a témat, která se řídí implementací Dispose a DisposeAsync metod.

Zrušitelná práce na pozadí

Komponenty často provádějí dlouho běžící práci na pozadí, jako jsou volání sítě (HttpClient) a interakce s databázemi. V několika situacích je žádoucí zastavit práci na pozadí, aby se zachovaly systémové prostředky. Například asynchronní operace na pozadí se automaticky nezastaví, když uživatel přejde mimo komponentu.

Mezi další důvody, proč můžou pracovní položky na pozadí vyžadovat zrušení, patří:

  • Spuštěná úloha na pozadí byla spuštěna s vadnými vstupními daty nebo parametry zpracování.
  • Aktuální sada spuštěných pracovních položek na pozadí musí být nahrazena novou sadou pracovních položek.
  • Priorita aktuálně spuštěných úkolů se musí změnit.
  • Aplikace se musí vypnout pro opětovné nasazení serveru.
  • Serverové prostředky jsou omezené a je nutné přeplánovat pracovní položky na pozadí.

Implementace zrušitelného pracovního vzoru na pozadí v komponentě:

V následujícím příkladu:

  • await Task.Delay(5000, cts.Token); představuje dlouhotrvající asynchronní práci na pozadí.
  • BackgroundResourceMethod představuje dlouhotrvající metodu na pozadí, která by se neměla spustit, pokud Resource je uvolněna před zavolání 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"
@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 události opětovného připojení

Události životního cyklu součástí popsané v tomto článku fungují odděleně od obslužných rutin událostí opětovného připojení na straně serveru. Když dojde ke SignalR ztrátě připojení k klientovi, přeruší se pouze aktualizace uživatelského rozhraní. Aktualizace uživatelského rozhraní se obnoví při opětovném navázání připojení. Další informace o událostech a konfiguraci obslužné rutiny okruhu najdete v ASP.NET BlazorSignalR základních doprovodných materiálech.

Další materiály

Zpracování zachycených výjimek mimo Razor životní cyklus komponenty