ASP.NET Core Razor komponentens livscykel
Anteckning
Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella versionen, se .NET 9-versionen av den här artikeln .
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Den här artikeln beskriver ASP.NET Core Razor komponentens livscykel och hur du använder livscykelhändelser.
Livscykelhändelser
Den Razor komponenten bearbetar Razor komponentens livscykelhändelser i en uppsättning synkrona och asynkrona livscykelmetoder. Livscykelmetoderna kan åsidosättas för att utföra ytterligare åtgärder i komponenter under komponentinitiering och återgivning.
Den här artikeln förenklar händelsebearbetningen av komponentens livscykel för att förtydliga komplex ramverkslogik och omfattar inte alla ändringar som har gjorts under åren. Du kan behöva komma åt ComponentBase
referenskällan för att integrera anpassad händelsebearbetning med Blazorlivscykelhändelsebearbetning. Kodkommentarer i referenskällan innehåller ytterligare kommentarer om livscykelhändelsebearbetning som inte visas i den här artikeln eller i API-dokumentationen.
Not
Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. Om du vill välja en tagg för en specifik release använder du listrutan för att växla mellan grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).
Följande förenklade diagram illustrerar händelsebearbetning för Razor komponentens livscykel. De C#-metoder som är associerade med livscykelhändelserna definieras med exempel i följande avsnitt i den här artikeln.
Händelser i komponentens livscykel:
- Om komponenten renderar för första gången vid en förfrågan:
- Skapa komponentens instans.
- Utför egenskapsinmatning.
- Ringa
OnInitialized{Async}
. Om en ofullständig Task returneras, avvaktar man Task och sedan återskapas komponenten. Den synkrona metoden anropas före den asynkrona metoden.
- Anropa
OnParametersSet{Async}
. Om en ofullständig Task returneras, väntas Task in och komponenten återskapas. Den synkrona metoden anropas före den asynkrona metoden. - Rendera för allt synkront arbete och slutför uppgift Tasks.
Obs
Asynkrona åtgärder som utförs i livscykelhändelser kan fördröja komponentrendering eller visning av data. Mer information finns i avsnittet Hantera ofullständiga asynkrona åtgärder vid återgivning senare i den här artikeln.
En föräldrakomponent renderas före sina barnkomponenter eftersom rendering bestämmer vilka barnkomponenter som finns. Om synkron initiering av överordnad komponent används kommer den överordnade initieringen garanterat att slutföras först. Om asynkron initiering av överordnad komponent används kan inte slutförandeordningen för initiering av överordnad och underordnad komponent fastställas eftersom den är beroende av att initieringskoden körs.
DOM-händelsebearbetning:
- Händelsehanteraren körs.
- Om en ofullständig Task returneras inväntas Task, och sedan renderas komponenten om.
- Rendera allt synkront arbete och slutför Tasks.
Livscykeln för Render
:
- Undvik ytterligare återgivningsåtgärder på komponenten när båda följande villkor uppfylls:
- Det är inte den första renderingen.
-
ShouldRender
returnerarfalse
.
- Skapa återgivningsträdets diff (skillnad) och rendera komponenten.
- Vänta på att DOM uppdateras.
- Ring
OnAfterRender{Async}
. Den synkrona metoden anropas före den asynkrona metoden.
Utvecklaranrop till StateHasChanged
resulterar i en omarendering. För mer information, se ASP.NET Core Razor komponentåtergivning.
Viloläge vid föråtergivning
I Blazor appar på serversidan väntar prerendering på quiescence, vilket innebär att en komponent inte renderas förrän alla komponenter i återgivningsträdet har slutfört renderingen. Quiescence kan leda till märkbara fördröjningar i renderingen när en komponent utför långvariga uppgifter under initiering och andra livscykelmetoder, vilket leder till en dålig användarupplevelse. Mer information finns i avsnittet Hantera ofullständiga asynkrona åtgärder vid återgivning senare i den här artikeln.
När parametrar anges (SetParametersAsync
)
SetParametersAsync anger parametrar som tillhandahålls av komponentens överordnade i återgivningsträdet eller från vägparametrar.
Metodens ParameterView-parameter innehåller uppsättningen komponentparametern värden för komponenten varje gång SetParametersAsync anropas. Genom att åsidosätta metoden SetParametersAsync kan utvecklarkod interagera direkt med ParameterViewparametrar.
Standardimplementeringen av SetParametersAsync anger värdet för varje egenskap med attributet [Parameter]
eller [CascadingParameter]
som har ett motsvarande värde i ParameterView. Parametrar som inte har ett motsvarande värde i ParameterView lämnas oförändrade.
I allmänhet bör koden anropa basklassmetoden (await base.SetParametersAsync(parameters);
) när du åsidosättar SetParametersAsync. I avancerade scenarier kan utvecklarkod tolka de inkommande parametrarnas värden på alla sätt som krävs genom att inte anropa basklassmetoden. Det finns till exempel inget krav på att tilldela inkommande parametrar till egenskaperna för klassen. Du måste dock referera till ComponentBase
referenskälla när du strukturerar koden utan att anropa basklassmetoden eftersom den anropar andra livscykelmetoder och utlöser rendering på ett komplext sätt.
Not
Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. Om du vill välja en tagg för en specifik version använder du listrutan Växla grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).
Om du vill förlita dig på initierings- och renderingslogik för ComponentBase.SetParametersAsync men inte bearbeta inkommande parametrar kan du skicka en tom ParameterView till basklassmetoden:
await base.SetParametersAsync(ParameterView.Empty);
Om händelsehanterare anges i utvecklarkod, ta bort dem när de inte längre behövs. Mer information finns i avsnittet Komponenthantering med IDisposable
och IAsyncDisposable
.
I följande exempel tilldelar ParameterView.TryGetValueParam
-parameterns värde till value
om parsning av en routningsparameter för Param
lyckas. När value
inte är null
visas värdet av komponenten.
Även om vägparametermatchning är skiftlägesokänsligtmatchar TryGetValue endast skiftlägeskänsliga parameternamn i routningsmallen. Följande exempel kräver användning av /{Param?}
i routningsmallen för att hämta värdet med TryGetValue, inte /{param?}
. Om /{param?}
används i det här scenariot returnerar TryGetValuefalse
och message
är inte inställt på någon av message
strängarna.
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);
}
}
Komponentinitiering (OnInitialized{Async}
)
OnInitialized och OnInitializedAsync används uteslutande för att initiera en komponent under hela komponentinstansens livslängd. Parametervärden och parametervärdeändringar bör inte påverka initieringen som utförs i dessa metoder. Till exempel utförs inläsning av statiska alternativ i en listruta som inte ändras under komponentens livslängd och som inte är beroende av parametervärden i någon av dessa livscykelmetoder. Om parametervärden eller ändringar i parametervärden påverkar komponenttillståndet använder du OnParametersSet{Async}
i stället.
Dessa metoder anropas när komponenten initieras efter att ha tagit emot de första parametrarna i SetParametersAsync. Den synkrona metoden anropas före den asynkrona metoden.
Om synkron initiering av överordnad komponent används kommer den överordnade initieringen garanterat att slutföras innan den underordnade komponenten initieras. Om asynkron initiering av överordnad komponent används kan inte slutförandeordningen för initiering av överordnad och underordnad komponent fastställas eftersom den är beroende av att initieringskoden körs.
För en synkron åtgärd åsidosätter du 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}";
}
}
Om du vill utföra en asynkron åtgärd åsidosätter du OnInitializedAsync och använder operatorn await
:
protected override async Task OnInitializedAsync()
{
await ...
}
Om en anpassad basklass används med anpassad initieringslogik anropar du OnInitializedAsync på basklassen:
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
Det är inte nödvändigt att anropa ComponentBase.OnInitializedAsync om inte en anpassad basklass används med anpassad logik. Mer information finns i avsnittet Livscykelmetoder för basklass.
Blazor appar som förbereder sitt innehåll på servern anropar OnInitializedAsynctvå gånger:
- En gång när komponenten ursprungligen återges statiskt som en del av sidan.
- En andra gång när webbläsaren återger komponenten.
För att förhindra att utvecklarkod i OnInitializedAsync körs två gånger vid förhandsrendering, se avsnittet Stateful-återanslutning efter förhandsrendering. Innehållet i avsnittet fokuserar på Blazor Web Apps och tillståndskänslig SignalRåteranslutning. Information om hur du bevarar tillståndet under exekveringen av initieringskoden vid prerendering finns i Prerender ASP.NET Core Razor-komponenter.
Information om hur du förhindrar att utvecklarkod i OnInitializedAsync körs två gånger vid föråtergivning finns i avsnittet Stillastående återanslutning efter föråtergivning. Även om innehållet i avsnittet fokuserar på Blazor Server och tillståndskänsliga SignalRåteranslutning, innebär scenariot för förretablering i värdbaserade Blazor WebAssembly lösningar (WebAssemblyPrerendered) liknande villkor och metoder för att förhindra att utvecklarkod körs två gånger. Information om hur du bevarar tillståndet under körningen av initieringskoden vid förberedande rendering finns i Integrera ASP.NET Core Razor-komponenter med MVC eller Razor-sidor.
När en Blazor app förbereder rendering är vissa åtgärder, till exempel anrop till JavaScript (JS interop), inte möjliga. Komponenter kan behöva renderas annorlunda när de är för-renderade. Mer information finns i avsnittet Prerendering med JavaScript interop.
Om händelsehanterare anges i utvecklarkoden, koppla bort dem vid avveckling. Mer information finns i avsnittet Komponenthantering med IDisposable
IAsyncDisposable
.
Använd strömningsrendering med statisk återgivning på serversidan (statisk SSR) eller förhandsrendering för att förbättra användarupplevelsen för komponenter som utför långvariga asynkrona uppgifter i OnInitializedAsync för att återges fullständigt. Mer information finns i följande resurser:
När parametrar har angetts (OnParametersSet{Async}
)
OnParametersSet eller OnParametersSetAsync anropas:
När komponenten har initierats i OnInitialized eller OnInitializedAsync.
När den överordnade komponenten skickar om och tillhandahåller:
- Kända eller primitiva oföränderliga typer när minst en parameter har ändrats.
- Parametrar av komplexa datatyper. Ramverket kan inte veta om värdena för en komplex typad parameter har muterats internt, så ramverket behandlar alltid parameteruppsättningen som ändrad när en eller flera komplexa parametrar finns.
Mer information om återgivningskonventioner finns i ASP.NET Core Razor komponentåtergivning.
Den synkrona metoden anropas före den asynkrona metoden.
Metoderna kan anropas även om parametervärdena inte har ändrats. Det här beteendet understryker behovet av att utvecklare implementerar ytterligare logik inom metoderna för att kontrollera om parametervärdena verkligen har ändrats innan data eller tillstånd som är beroende av dessa parametrar initieras på nytt.
För följande exempelkomponent navigerar du till komponentens sida på en URL:
- Med ett startdatum som tas emot av
StartDate
:/on-parameters-set/2021-03-19
- Utan startdatum, där
StartDate
tilldelas ett värde för den aktuella lokala tiden:/on-parameters-set
Notera
I en komponentväg är det inte möjligt att både begränsa en DateTime parameter med vägvillkor datetime
och göra parametern valfri. Därför använder följande OnParamsSet
komponent två @page
-direktiv för att hantera routning med och utan ett angivet datumsegment i URL:en.
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}).";
}
}
}
Asynkront arbete vid tillämpning av parametrar och egenskapsvärden måste ske under OnParametersSetAsync livscykelhändelse:
protected override async Task OnParametersSetAsync()
{
await ...
}
Om en anpassad basklass används med anpassad initieringslogik anropar du OnParametersSetAsync på basklassen:
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
Det är inte nödvändigt att anropa ComponentBase.OnParametersSetAsync om inte en anpassad basklass används med anpassad logik. Mer information finns i avsnittet Livscykelmetoder för basklass.
Om händelsehanterare anges i utvecklarkoden tar du bort dem vid bortskaffande. Mer information finns i avsnittet Komponenthantering med IDisposable
IAsyncDisposable
.
Mer information om routningsparametrar och begränsningar finns i ASP.NET Core Blazor routning och navigering.
Ett exempel på hur du implementerar SetParametersAsync
manuellt för att förbättra prestanda i vissa scenarier finns i bästa praxis för ASP.NET Core Blazor prestanda.
Efter komponentåtergivning (OnAfterRender{Async}
)
OnAfterRender och OnAfterRenderAsync anropas när en komponent har renderats interaktivt och användargränssnittet har uppdaterats (till exempel efter att element har lagts till i webbläsarens DOM). Element- och komponentreferenser fylls i just nu. Använd den här fasen för att utföra ytterligare initieringssteg med det renderade innehållet, till exempel JS interop-anrop som interagerar med de renderade DOM-elementen. Den synkrona metoden anropas före den asynkrona metoden.
Dessa metoder anropas inte under förrendering eller statisk återgivning på serversidan (statisk SSR) på servern eftersom dessa processer inte är kopplade till en realtidswebbläsares DOM och redan är slutförda innan DOM uppdateras.
För OnAfterRenderAsyncåterställs inte komponenten automatiskt efter slutförandet av returnerade Task
för att undvika en oändlig återgivningsloop.
OnAfterRender och OnAfterRenderAsync anropas när en komponent har slutfört renderingen. Element- och komponentreferenser fylls i just nu. Använd den här fasen för att utföra ytterligare initieringssteg med det renderade innehållet, till exempel JS interop-anrop som interagerar med de renderade DOM-elementen. Den synkrona metoden anropas före den asynkrona metoden.
Dessa metoder anropas inte under prerendering eftersom prerendering inte är kopplat till en live-webbläsar-DOM och redan är klar innan DOM uppdateras.
För OnAfterRenderAsyncåterställs inte komponenten automatiskt efter slutförandet av returnerade Task
för att undvika en oändlig återgivningsloop.
Parametern firstRender
för OnAfterRender och OnAfterRenderAsync:
- Anges till
true
första gången komponentinstansen återges. - Kan användas för att säkerställa att initieringsarbetet bara utförs en gång.
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");
}
}
Det AfterRender.razor
exemplet genererar följande utdata till konsolen när sidan läses in och knappen är markerad:
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
Asynkront arbete omedelbart efter återgivningen måste ske under OnAfterRenderAsync livscykelhändelse:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Om en anpassad basklass används med anpassad initieringslogik anropar du OnAfterRenderAsync på basklassen:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
Det är inte nödvändigt att anropa ComponentBase.OnAfterRenderAsync om inte en anpassad basklass används med anpassad logik. Mer information finns i avsnittet Livscykelmetoder för basklass.
Även om du returnerar en Task från OnAfterRenderAsyncschemalägger ramverket inte ytterligare en återgivningscykel för komponenten när aktiviteten har slutförts. Detta är för att undvika en oändlig återgivningsloop. Detta skiljer sig från de andra livscykelmetoderna, som schemalägger ytterligare en återgivningscykel när en returnerad Task har slutförts.
OnAfterRender och OnAfterRenderAsyncanropas inte under förrenderingsprocessen på servern. Metoderna anropas när komponenten återges interaktivt efter förinläsning. När appen renderar i förväg:
- Komponenten körs på servern för att skapa en statisk HTML-kod i HTTP-svaret. Under den här fasen anropas inte OnAfterRender och OnAfterRenderAsync.
- När Blazor skriptet (
blazor.{server|webassembly|web}.js
) startar i webbläsaren startas komponenten om i ett interaktivt återgivningsläge. När en komponent har startats om anropas OnAfterRender och OnAfterRenderAsync eftersom appen inte längre är i prerenderingsfasen.
Om händelsehanterare anges i utvecklarkoden, koppla bort dem när de tas bort. Mer information finns i avsnittet Komponentbortskaffande med IDisposable
IAsyncDisposable
.
Livscykelmetoder för basklass
När du åsidosätter Blazor:s livscykelmetoder är det inte nödvändigt att anropa basklassens livscykelmetoder för ComponentBase. En komponent bör dock anropa en åsidosatt basklasslivscykelmetod i följande situationer:
- När du åsidosätter ComponentBase.SetParametersAsyncanropas vanligtvis
await base.SetParametersAsync(parameters);
eftersom basklassmetoden anropar andra livscykelmetoder och utlöser rendering på ett komplext sätt. Mer information finns i avsnittet När parametrar har angetts (SetParametersAsync
). - Om basklassmetoden innehåller logik som måste köras. Bibliotekskonsumenter anropar vanligtvis livscykelmetoder för basklass när de ärver en basklass eftersom biblioteksbasklasser ofta har anpassad livscykellogik att köra. Om appen använder en basklass från ett bibliotek kan du läsa bibliotekets dokumentation för vägledning.
I följande exempel anropas base.OnInitialized();
för att säkerställa att basklassens OnInitialized
-metod körs. Utan anropet körs inte BlazorRocksBase2.OnInitialized
.
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!");
}
}
Tillståndsändringar (StateHasChanged
)
StateHasChanged meddelar komponenten att dess tillstånd har ändrats. Om det är tillämpligt, anropar StateHasChanged en ny rendering som inträffar när appens huvudtråd är fri.
StateHasChanged anropas automatiskt för EventCallback metoder. Mer information om händelseåteranrop finns i ASP.NET Core Blazor händelsehantering.
Mer information om komponentrendering och när du ska anropa StateHasChanged, inklusive när du ska anropa den med ComponentBase.InvokeAsync, finns i ASP.NET Core Razor komponentåtergivning.
Hantera ofullständiga asynkrona åtgärder vid återgivning
Asynkrona åtgärder som utförs i livscykelhändelser kanske inte har slutförts innan komponenten återges. Objekt kan vara null
eller ofullständigt ifyllda med data medan livscykelmetoden körs. Ange renderingslogik för att bekräfta att objekt initieras. Visa platshållarelement (till exempel ett inläsningsmeddelande) medan objekten laddas null
.
I följande Slow
-komponent åsidosätts OnInitializedAsync för att asynkront köra en långvarig uppgift. När isLoading
är true
visas ett inläsningsmeddelande för användaren. När Task
, som returneras av OnInitializedAsync, har slutförts renderas komponenten på nytt i sitt uppdaterade tillstånd och visar meddelandet "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);
}
}
Föregående komponent använder en isLoading
variabel för att visa inläsningsmeddelandet. En liknande metod används för en komponent som läser in data i en samling och kontrollerar om samlingen är null
för att presentera inläsningsmeddelandet. Det följande exemplet kontrollerar movies
-samlingen för null
för att antingen visa inläsningsmeddelandet eller visa filmsamlingen:
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
@* display movies *@
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovies();
}
}
Prerendering väntar på quiescence, vilket innebär att en komponent inte renderas förrän alla komponenter i återgivningsträdet har slutfört renderingen. Det innebär att ett inläsningsmeddelande inte visas medan en underordnad komponents OnInitializedAsync-metod kör en tidskrävande uppgift under förinläsningen. Om du vill demonstrera det här beteendet placerar du den föregående Slow
komponenten i testappens Home
komponent:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SlowComponent />
Notera
Även om exemplen i det här avsnittet beskriver OnInitializedAsync livscykelmetod kan andra livscykelmetoder som körs under förinläsning fördröja den slutliga återgivningen av en komponent. Endast OnAfterRender{Async}
körs inte under prerendering och är immun mot fördröjningar på grund av quiescence.
Under förrendering renderas inte Home
-komponenten förrän den Slow
komponenten återges, vilket tar tio sekunder. Användargränssnittet är tomt under den här tio sekunder långa perioden och det finns inget inläsningsmeddelande. Efter förrendering renderas Home
-komponenten och Slow
-komponentens inläsningsmeddelande visas. Efter ytterligare tio sekunder visar Slow
komponenten slutligen det färdiga meddelandet.
Som föregående demonstration visar, resulterar passivitet under förberendering i en dålig användarupplevelse. För att förbättra användarupplevelsen börjar du med att implementera strömningsrendering för att undvika att vänta på att den asynkrona uppgiften ska slutföras under prerendering.
Lägg till attributet [StreamRendering]
till komponenten Slow
(använd [StreamRendering(true)]
i .NET 8):
@attribute [StreamRendering]
När Home
-komponenten förhandsrenderas, återges Slow
-komponenten snabbt med sitt laddningsmeddelande. Komponenten Home
väntar inte i tio sekunder på att den Slow
komponenten ska slutföra återgivningen. Det färdiga meddelandet som visas i slutet av prerendering ersätts dock av inläsningsmeddelandet medan komponenten slutligen renderas, vilket är ytterligare tio sekunders fördröjning. Detta beror på att komponenten Slow
renderas två gånger och att LoadDataAsync
också körs två gånger. När en komponent kommer åt resurser, till exempel tjänster och databaser, skapar dubbel körning av tjänstanrop och databasfrågor oönskad belastning på appens resurser.
För att hantera dubbel återgivning av inläsningsmeddelandet och återkörningen av tjänst- och databasanrop: spara förinläst tillstånd med PersistentComponentState för den slutliga återgivningen av komponenten, enligt följande uppdateringar av komponenten 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();
}
}
Genom att kombinera strömningsrendering med beständiga komponenttillstånd:
- Tjänster och databaser kräver bara ett enda anrop för komponentinitiering.
- Komponenter renderar sina användargränssnitt snabbt och visar laddningsmeddelanden under långvariga uppgifter för att ge den bästa användarupplevelsen.
Mer information finns i följande resurser:
Inaktivitet under föråtergivning resulterar i en dålig användarupplevelse. Fördröjningen kan åtgärdas i appar som riktar in sig på .NET 8 eller senare med en funktion som kallas strömningsrendering, vanligtvis kombinerad med bestående komponenttillstånd under för att undvika att vänta på att den asynkrona uppgiften ska slutföras. I versioner av .NET tidigare än 8.0 kan körning av en tidskrävande bakgrundsaktivitet som läser in data efter slutlig återgivning åtgärda en lång återgivningsfördröjning på grund av quiescence.
Hantera felhantering
Information om hur du hanterar fel vid körning av livscykelmetoder finns i Hantera fel i ASP.NET Core Blazor-appar.
Tillståndskänslig återanslutning efter förhandsrendering
Vid prerendering på servern återges en komponent ursprungligen statiskt som en del av sidan. När webbläsaren har upprättat en SignalR anslutning tillbaka till servern blir komponenten återigen och interaktiv. Om OnInitialized{Async}
livscykelmetod för att initiera komponenten finns körs metoden två gånger:
- När komponenten prerendereras statiskt.
- När serveranslutningen har upprättats.
Detta kan resultera i en märkbar ändring i de data som visas i användargränssnittet när komponenten slutligen återges. Undvik det här beteendet genom att skicka in en identifierare för att cachelagra tillståndet under prerendering och hämta tillståndet efter förinläsning.
Följande kod visar en WeatherForecastService
som undviker ändringar i datavisningen på grund av prerendering. Den väntade Delay (await Task.Delay(...)
) simulerar en kort fördröjning innan data returneras från metoden GetForecastAsync
.
Tillför IMemoryCache tjänster med AddMemoryCache i tjänstesamlingen i appens Program
-fil:
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();
});
}
}
Mer information om RenderModefinns i ASP.NET Core BlazorSignalR vägledning.
Innehållet i det här avsnittet fokuserar på Blazor Web Apps och tillståndskänslig SignalRåteranslutning. Information om hur du bevarar tillståndet under utförandet av initieringskoden vid prerendering finns i Prerender ASP.NET Core Razor-komponenter.
Även om innehållet i det här avsnittet fokuserar på Blazor Server och tillståndsbaserad SignalRåteranslutning, omfattar scenariot för förhandsrendering i värdbaserade Blazor WebAssembly-lösningar (WebAssemblyPrerendered) liknande villkor och metoder för att förhindra att utvecklarkod körs två gånger. Information om hur du bevarar tillståndet under körningen av initieringskoden vid förinläsning finns i Integrera ASP.NET Core Razor-komponenter med MVC eller Razor Pages.
Prerendering med JavaScript-interop
Det här avsnittet gäller för appar på serversidan som förberender Razor-komponenter. Prerendering beskrivs i komponenterna Prerender ASP.NET Core Razor.
Anteckning
Intern navigering för interaktiv routning i Blazor Web Appinnebär inte att begära nytt sidinnehåll från servern. Därför sker inte prerendering för interna sidbegäranden. Om appen använder interaktiv ruttning, utför en fullständig sidinläsning för komponentexempel som visar förgenereringsbeteende. Mer information finns i Prerender ASP.NET Core Razor-komponenter.
Det här avsnittet gäller för appar på serversidan och värdbaserade Blazor WebAssembly-appar som förbereder Razor-komponenter. Förrendering beskrivs i Integrera ASP.NET Core Razor-komponenter med MVC eller Razor Pages.
Det går inte att anropa JavaScript (JS) under förinläsningen. I följande exempel visas hur du använder JS interop som en del av en komponents initieringslogik på ett sätt som är kompatibelt med prerendering.
Följande scrollElementIntoView
funktion:
- Rullar till det överförda elementet med
scrollIntoView
. - Returnerar elementets
top
egenskapsvärde från metodengetBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Om IJSRuntime.InvokeAsync anropar funktionen JS i komponentkoden används ElementReference endast i OnAfterRenderAsync och inte i någon tidigare livscykelmetod eftersom det inte finns något HTML DOM-element förrän komponenten har renderats.
StateHasChanged
(referenskälla) anropas för att ange omrendering av komponenten med det nya tillståndet som erhålls från JS interop-anropet (mer information finns i ASP.NET Core Razor komponentrendering). En oändlig loop skapas inte eftersom StateHasChanged bara anropas när scrollPosition
är 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();
}
}
}
Föregående exempel förorenar klienten med en global funktion. En bättre metod i produktionsappar finns i JavaScript-isolering i JavaScript-moduler.
Bortskaffande av komponenter med IDisposable
och IAsyncDisposable
Om en komponent implementerar IDisposable eller IAsyncDisposableanropar ramverket bortskaffande av resurser när komponenten tas bort från användargränssnittet. Förlita dig inte på den exakta tidpunkten för när dessa metoder körs. Till exempel kan IAsyncDisposable utlösas före eller efter att en asynkron Task väntar i OnInitalizedAsync
anropas eller slutförs. Dessutom bör objekthanteringskoden inte förutsätta att objekt som skapats under initieringen eller andra livscykelmetoder finns.
Komponenter ska inte behöva implementera IDisposable och IAsyncDisposable samtidigt. Om båda implementeras kör ramverket bara den asynkrona överbelastningen.
Utvecklarkod måste se till att IAsyncDisposable implementeringar inte tar lång tid att slutföra.
Borttagning av javascript-interopobjektreferenser
Exempel i JavaScript -JS) interop-artiklar demonstrera typiska mönster för bortskaffande av objekt:
När du anropar JS från .NET, enligt beskrivningen i Anropa JavaScript-funktioner från .NET-metoder i ASP.NET Core Blazor, ska du ta bort alla skapade IJSObjectReference/IJSInProcessObjectReference/JSObjectReference antingen från .NET eller från JS för att undvika att läcka JS minne.
När du anropar .NET från JS, enligt beskrivningen i Anropa .NET-metoder från JavaScript-funktioner i ASP.NET Core Blazor, gör du dig av med en skapad DotNetObjectReference antingen från .NET eller från JS för att undvika läckage av .NET-minne.
JS interop-objektreferenser implementeras som en map som styrs av en identifierare på sidan av JS interop-anropet som skapar referensen. När bortskaffande av objekt initieras från antingen .NET- eller JS-sidan tar Blazor bort posten från kartan, och objektet kan bli föremål för avfallshantering så länge det inte finns någon annan stark referens till objektet.
Ta alltid bort objekt som skapats på .NET-sidan för att undvika att läcka .NET-hanterat minne.
DOM-rensningsuppgifter vid bortskaffande av komponenter
Mer information finns i ASP.NET Core Blazor JavaScript-samverkan (JS interop).
Mer information om JSDisconnectedException när en krets är frånkopplad finns i ASP.NET Core Blazor JavaScript-samverkan (JS interop). Allmän vägledning för JavaScript-interopfelhantering finns i avsnittet JavaScript interop i Hantera fel i ASP.NET Core Blazor-appar.
Synkrona IDisposable
För synkrona borttagningsuppgifter använder du IDisposable.Dispose.
Följande komponent:
- Implementerar IDisposable med
@implements
Razor-direktivet. - Gör sig av med
obj
, som är en typ som implementerar IDisposable. - En null-kontroll utförs eftersom
obj
skapas i en livscykelmetod (visas inte).
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
Om ett enskilt objekt kräver bortskaffande kan en lambda användas för att ta bort objektet när Dispose anropas. Följande exempel visas i artikeln ASP.NET Core Razor-komponentåtergivning och visar användningen av ett lambda-uttryck för hantering av en 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();
}
Not
I föregående exempel omsluts anropet till StateHasChanged av ett anrop till ComponentBase.InvokeAsync eftersom återanropet anropas utanför Blazorsynkroniseringskontext. För mer information, se ASP.NET Core Razor rendering av komponenter.
Om objektet skapas i en livscykelmetod, till exempel OnInitialized{Async}
, kontrollerar du null
innan du anropar 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();
}
Mer information finns i:
Asynkrona IAsyncDisposable
För asynkrona borttagningsuppgifter använder du IAsyncDisposable.DisposeAsync.
Följande komponent:
- Implementerar IAsyncDisposable med
@implements
Razor-direktivet. - Tar bort
obj
, som är en ohanterad typ som implementerar IAsyncDisposable. - En null-kontroll utförs eftersom
obj
skapas i en livscykelmetod (visas inte).
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
Mer information finns i:
Tilldelning av null
till avyttrade objekt
Vanligtvis behöver du inte tilldela null
till borttagna objekt när du har anropat Dispose/DisposeAsync. Sällsynta fall för att tilldela null
omfattar följande:
- Om objektets typ är dåligt implementerad och inte tolererar upprepade anrop till Dispose/DisposeAsynctilldelar du
null
efter bortskaffande för att smidigt hoppa över ytterligare anrop till Dispose/DisposeAsync. - Om en långlivad process fortsätter att innehålla en referens till ett bortskaffat objekt kan
null
skräpinsamlare frigöra objektet trots att den långvariga processen innehåller en referens till det.
Det här är ovanliga scenarier. För objekt som implementeras korrekt och fungerar normalt är det ingen idé att tilldela null
till borttagna objekt. I de sällsynta fall där ett objekt måste tilldelas null
rekommenderar vi att du dokumenterar orsaken och söker en lösning som förhindrar behovet av att tilldela null
.
StateHasChanged
Not
Det går inte att anropa StateHasChanged i Dispose
och DisposeAsync
.
StateHasChanged kan anropas som en del av att stänga ner renderaren, så att begära uppdateringar av användargränssnittet medan det sker stöds inte.
Händelsehanterare
Avregistrera alltid händelsehanterare från .NET-händelser. Följande Blazor formulär exempel visar hur du avregistrerar en händelsehanterare i Dispose
-metoden:
Privat fält och lambda-tillvägagångssätt
@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; } }
Tillvägagångssätt för privat metod
@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; } }
Mer information finns i avsnittet Komponenthantering med IDisposable
och IAsyncDisposable
.
Mer information om EditForm komponent och formulär finns i översikten över ASP.NET Core Blazor-formulär och de andra formulärartiklarna i noden Forms.
Anonyma funktioner, metoder och uttryck
När anonyma funktioner, metoder eller uttryck används är det inte nödvändigt att implementera IDisposable och ta bort delegater. Att inte avbryta prenumerationen på ett ombud är dock ett problem när objektet som exponerar händelsen överlever livslängden för komponenten som registrerar ombudet. När detta inträffar leder det till en minnesläcka eftersom den registrerade delegaten håller kvar det ursprungliga objektet. Använd därför endast följande metoder när du vet att händelsedelegaten tar bort snabbt. När du är osäker på livslängden för objekt som kräver bortskaffande, prenumerera på en delegeringsmetod och kassera delegeringen korrekt, som visas i de tidigare exemplen.
Anonym lambda-metodansats (explicit bortskaffande krävs inte):
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); }
Tillvägagångssätt med anonyma lambda-uttryck (explicit bortskaffande krävs inte):
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); }
Det fullständiga exemplet på föregående kodexempel med anonyma lambda-uttryck visas i artikeln om ASP.NET Core Blazor formulärverifiering.
Mer information finns i Rensa ohanterade resurser och de avsnitt som följer på implementeringen av metoderna Dispose
och DisposeAsync
.
Bortskaffande under JS interoperabilitet
Fånga JSDisconnectedException i potentiella situationer där förlust av Blazor:s SignalR-krets förhindrar JS-interopanrop och leder till ett ohanterat undantag.
Mer information finns i följande resurser:
Avbrytbar bakgrundsarbete
Komponenter utför ofta tidskrävande bakgrundsarbete, till exempel att göra nätverksanrop (HttpClient) och interagera med databaser. Det är önskvärt att stoppa bakgrundsarbetet för att spara systemresurser i flera situationer. Till exempel stoppas inte asynkrona bakgrundsåtgärder automatiskt när en användare navigerar bort från en komponent.
Andra orsaker till att bakgrundsarbetsobjekt kan kräva annullering är:
- En körande bakgrundsaktivitet startades med felaktiga indata eller bearbetningsparametrar.
- Den aktuella uppsättningen av bakgrundsprocesser måste ersättas med en ny uppsättning arbetsuppgifter.
- Prioriteten för aktiviteter som körs för närvarande måste ändras.
- Appen måste stängas av för omdistribution av servern.
- Serverresurserna blir begränsade, vilket gör det nödvändigt att schemalägga om bakgrundsarbetsobjekt.
Så här implementerar du ett avbrytbar bakgrundsarbete i en komponent:
- Använd en CancellationTokenSource och CancellationToken.
- När komponenten avlägsnas vid
och när en annullering önskas genom att manuellt avbryta token, anropa för att signalera att bakgrundsarbetet ska avbrytas. - När det asynkrona anropet har returnerats, ska du anropa ThrowIfCancellationRequested på token.
I följande exempel:
-
await Task.Delay(10000, cts.Token);
representerar tidskrävande asynkront bakgrundsarbete. -
BackgroundResourceMethod
representerar en långvarig bakgrundsmetod som inte bör starta omResource
tas bort innan metoden anropas.
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 återanslutningshändelser
Komponenternas livscykelhändelser som beskrivs i den här artikeln fungerar separat från händelsehanterare för återanslutning på serversidan. När SignalR-anslutningen till klienten går förlorad avbryts endast uppdateringar av användargränssnittet. Uppdateringar av användargränssnittet återupptas när anslutningen återupprättas. Mer information om kretshanterarhändelser och konfiguration finns i ASP.NET Core BlazorSignalR vägledning.
Ytterligare resurser
Hantera fångade undantag utanför en Razor komponents livscykel
ASP.NET Core