Sdílet prostřednictvím


životní cyklus komponent ASP.NET Core Razor

Poznámka:

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

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

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 v tomto článku ve verzi .NET 9.

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 asynchronní 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 asynchronní 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 můžou zpožďovat vykreslování komponent nebo zobrazovat data. Další informace najdete v části Zpracování neúplných asynchronních akcí při vykreslování 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 asynchronní metodou.

Životní cyklus vykreslování

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

Klid během předběžného vykreslování

V aplikacích na straně serveru Blazor předrenderování čeká na klid , což znamená, že se komponenta nevykreslí, dokud se všechny komponenty ve stromu vykreslování nedokončí vykreslování. Klid může vést ke znatelným zpožděním při vykreslování, když komponenta provádí dlouhotrvající úlohy během inicializace a dalších metod životního cyklu, což vede k špatnému uživatelskému zážitku. Další informace najdete v části Zpracování neúplných asynchronních akcí při vykreslování dále v tomto článku.

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í await base.SetParametersAsync(parameters);volat metodu základní třídy (SetParametersAsync). 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 parametruParameterView.TryGetValue, Param 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?}"

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

<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ředkreslování, přečtěte si téma Integrace komponent ASP.NET Core Razor s MVC nebo Razor Pages.

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 naleznete v následujících zdrojích:

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 Razor komponent Core.

Synchronní metoda se volá před asynchronní 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}"

<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 Blazor ASP.NET Core.

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

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

Změny stavu (StateHasChanged)

StateHasChanged oznámí komponentě, že se změnil její stav. Pokud je to možné, volání StateHasChanged enqueues rerender, který nastane, když hlavní vlákno aplikace je zdarma.

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

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 Razor komponent Core.

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

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ě Slow je OnInitializedAsync přepsán, aby asynchronně spustil časově náročnou úlohu. Zatímco isLoading je true, uživateli se zobrazí zpráva načítání. Po dokončení Task vráceného OnInitializedAsync se komponenta znovu vykreslí s aktualizovaným stavem, který ukazuje zprávu "Finished!".

Slow.razor:

@page "/slow"

<h2>Slow Component</h2>

@if (isLoading)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>Finished!</div>
}

@code {
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
        isLoading = false;
    }

    private Task LoadDataAsync()
    {
        return Task.Delay(10000);
    }
}

Předchozí komponenta používá k zobrazení zprávy načítání proměnnou isLoading. Podobný přístup se používá pro komponentu, která načítá data do kolekce a kontroluje, zda je kolekce null, aby zobrazila zprávu o načítání. Následující příklad zkontroluje kolekci movies pro null, aby zobrazil načítací zprávu nebo zobrazil kolekci filmů:

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @* display movies *@
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovies();
    }
}

Prerendering čeká na klid, což znamená, že komponenta se nevykreslí, dokud všechny komponenty ve stromu vykreslování nedokončí vykreslování. To znamená, že zpráva o načítání se nezobrazuje, když podřízená komponenta metodou OnInitializedAsync spouští dlouhotrvající úlohu při předkreslování. Chcete-li toto chování předvést, umístěte předchozí komponentu Slow do komponenty Home testovací aplikace:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SlowComponent />

Poznámka:

I když příklady v této části diskutují o metodě životního cyklu OnInitializedAsync, jiné metody životního cyklu, které se provádějí během předkončování, mohou zpozdit konečné vykreslení komponenty. Pouze OnAfterRender{Async} se nespustí během prerenderingu a je imunní vůči zpožděním způsobeným klidem.

Během předkreslování se komponenta Home nevykreslí, dokud se nevykreslí komponenta Slow, což trvá deset sekund. Uživatelské rozhraní je během tohoto desetisekundového období prázdné a není k dispozici žádná zpráva o načítání. Po předběžném vykreslování se vykresluje komponenta Home a zobrazí se zpráva o načítání komponenty Slow. Po deseti sekundách komponenta Slow nakonec zobrazí dokončenou zprávu.

Jak znázorňuje předchozí ukázka, nehybnost během prerenderingu vede k špatné uživatelské zkušenosti. Pro zlepšení uživatelské zkušenosti začněte implementací streamovaného vykreslování, abyste se vyhnuli čekání na dokončení asynchronní úlohy při předběžném vykreslování.

Přidejte atribut [StreamRendering] do komponenty Slow (použijte [StreamRendering(true)] v .NET 8):

@attribute [StreamRendering]

Když je komponenta Home předem vykreslovaná, komponenta Slow se rychle vykresluje se svou zprávou o načítání. Komponenta Home nečeká deset sekund, než komponenta Slow dokončí vykreslování. Dokončená zpráva zobrazená na konci předběžného vykreslování je však nahrazena zprávou načítání, zatímco komponenta nakonec vykresluje, což je další desetisekundové zpoždění. K tomu dochází, protože komponenta Slow se vykresluje dvakrát, stejně jako se LoadDataAsync provádí dvakrát. Když komponenta přistupuje k prostředkům, jako jsou služby a databáze, dvojité spouštění volání služeb a databázových dotazů vytváří nežádoucí zatížení prostředků aplikace.

Pokud chcete vyřešit dvojité vykreslení zprávy o načítání a opětovné spuštění volání služeb a databáze, uchovejte předem vykreslený stav pomocí PersistentComponentState pro konečné vykreslení komponenty, jak je vidět v následujících aktualizacích komponenty Slow.

@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState

<h2>Slow Component</h2>

@if (data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@data</div>
}

@code {
    private string? data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<string>("data", out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("data", data);

        return Task.CompletedTask;
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(10000);
        return "Finished!";
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

Kombinováním streamování vykreslování se stavem perzistentních komponentů:

  • Služby a databáze vyžadují pouze jedno volání inicializace součástí.
  • Komponenty rychle vykreslují své uživatelské rozhraní s načítacími zprávami během dlouhotrvajících úloh pro co nejlepší uživatelský zážitek.

Další informace naleznete v následujících zdrojích:

Nečinnost při předběžném zpracování vede k špatné uživatelské zkušenosti. Zpoždění je možné řešit v aplikacích, které cílí na .NET 8 nebo novější, pomocí funkce nazývané streamované vykreslování, obvykle v kombinaci se zachováním stavu komponent během předevykreslování, aby se zabránilo čekání na dokončení asynchronní úlohy. Ve verzích rozhraní .NET starších než 8.0 může provedení dlouhotrvající úlohy na pozadí, která načte data po konečném vykreslení, vyřešit dlouhou prodlevu vykreslování způsobenou nečinností.

Zpracování chyb

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

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;

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ředkreslování, přečtěte si téma Integrace komponent ASP.NET Core Razor s MVC nebo Razor Pages.

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íBlazor Web Appnezahrnuje 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 se zabývá integrací komponent ASP.NET Core Razor s MVC nebo Razor Pages.

Během předřazení není volání do JavaScriptu (JS) možné. Následující příklad ukazuje, jak používat JS interoperabilitu jako součást inicializační logiky komponenty způsobem, který je kompatibilní s předdeenderingem.

scrollElementIntoView Následující funkce:

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

Kde IJSRuntime.InvokeAsync volá JS funkci v kódu komponenty, je použita pouze v ElementReference a ne v žádné dřívější metodě životního cyklu, OnAfterRenderAsync protože neexistuje žádný prvek HTML DOM, dokud se komponenta nevykreslí.

StateHasChanged(referenční zdroj) je volána k vytvoření fronty součásti s novým stavem získaným z JS volání zprostředkovatele komunikace (další informace najdete v tématu Razor komponent ASP.NET Core). Nekonečná smyčka se nevytvořila, protože StateHasChanged je volána pouze tehdy, když scrollPosition je 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();
        }
    }
}

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

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ůžete například IAsyncDisposable aktivovat před nebo po zavolání nebo dokončení asynchronního Task operátoru await in OnInitalizedAsync . 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 JS JavaScriptu () 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 Blazor ASP.NET Core.

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 Razor komponenty ASP.NET Core 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();
}

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

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íDispose/DisposeAsync . 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:

StateHasChanged Volání Dispose 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 Blazor formulářů ASP.NET Core.

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

Vyřazení během JS spolupráce

Výjimku JSDisconnectedException v potenciálních případech, kdy ztráta okruhu BlazorSignalR zabraňuje JS voláním vzájemné spolupráce a vede k neošetřené výjimce.

Další informace naleznete v následujících zdrojích:

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(10000, 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"
@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