ASP.NET Core Razor component lifecycle

Note

This isn't the latest version of this article. To switch to the latest, use the ASP.NET Core version selector at the top of the table of contents.

Version selector

If the selector isn't visible in a narrow browser window, widen the window or select the vertical ellipsis () > Table of contents.

Table of contents selector

This article explains the ASP.NET Core Razor component lifecycle and how to use lifecycle events.

The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering.

This article simplifies component lifecycle event processing in order to clarify complex framework logic. You may need to access the ComponentBase reference source to integrate custom event processing with Blazor's lifecycle event processing. Code comments in the reference source include additional remarks on lifecycle event processing that don't appear in this article or in the API documentation. Blazor's lifecycle event processing has changed over time and is subject to change without notice each release.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Lifecycle events

The following simplified diagrams illustrate Razor component lifecycle event processing. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article.

Component lifecycle events:

  1. If the component is rendering for the first time on a request:
  2. Call OnParametersSet{Async}. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Note

Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the Handle incomplete async actions at render section later in this article.

A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

Component lifecycle events of a Razor component in Blazor

Document Object Model (DOM) event processing:

  1. The event handler is run.
  2. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Document Object Model (DOM) event processing

The Render lifecycle:

  1. Avoid further rendering operations on the component:
  2. Build the render tree diff (difference) and render the component.
  3. Await the DOM to update.
  4. Call OnAfterRender{Async}.

Render lifecycle

Developer calls to StateHasChanged result in a render. For more information, see ASP.NET Core Razor component rendering.

When parameters are set (SetParametersAsync)

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

The method's ParameterView parameter contains the set of component parameter values for the component each time SetParametersAsync is called. By overriding the SetParametersAsync method, developer code can interact directly with ParameterView's parameters.

The default implementation of SetParametersAsync sets the value of each property with the [Parameter] or [CascadingParameter] attribute that has a corresponding value in the ParameterView. Parameters that don't have a corresponding value in ParameterView are left unchanged.

If base.SetParametersAsync isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

In the following example, ParameterView.TryGetValue assigns the Param parameter's value to value if parsing a route parameter for Param is successful. When value isn't null, the value is displayed by the component.

Although route parameter matching is case insensitive, TryGetValue only matches case-sensitive parameter names in the route template. The following example requires the use of /{Param?} in the route template in order to get the value with TryGetValue, not /{param?}. If /{param?} is used in this scenario, TryGetValue returns false and message isn't set to either message string.

Pages/SetParamsAsync.razor:

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

Component initialization (OnInitialized{Async})

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync.

If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

For a synchronous operation, override OnInitialized:

Pages/OnInit.razor:

@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

To perform an asynchronous operation, override OnInitializedAsync and use the await operator:

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

Blazor apps that prerender their content on the server call OnInitializedAsync twice:

  • Once when the component is initially rendered statically as part of the page.
  • A second time when the browser renders the component.

To prevent developer code in OnInitializedAsync from running twice when prerendering, see the Stateful reconnection after prerendering section. Although the content in the section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the Prerendering with JavaScript interop section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

After parameters are set (OnParametersSet{Async})

OnParametersSet or OnParametersSetAsync are called:

  • After the component is initialized in OnInitialized or OnInitializedAsync.

  • When the parent component rerenders and supplies:

    • Known or primitive immutable types when at least one parameter has changed.
    • Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present.

    For more information on rendering conventions, see ASP.NET Core Razor component rendering.

For the following example component, navigate to the component's page at a URL:

  • With a start date that's received by StartDate: /on-parameters-set/2021-03-19
  • Without a start date, where StartDate is assigned a value of the current local time: /on-parameters-set

Pages/OnParamsSet.razor:

Note

In a component route, it isn't possible to both constrain a DateTime parameter with the route constraint datetime and make the parameter optional. Therefore, the following OnParamsSet component uses two @page directives to handle routing with and without a supplied date segment in the URL.

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

Asynchronous work when applying parameters and property values must occur during the OnParametersSetAsync lifecycle event:

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

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

For more information on route parameters and constraints, see ASP.NET Core Blazor routing and navigation.

After component render (OnAfterRender{Async})

OnAfterRender and OnAfterRenderAsync are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements.

The firstRender parameter for OnAfterRender and OnAfterRenderAsync:

  • Is set to true the first time that the component instance is rendered.
  • Can be used to ensure that initialization work is only performed once.

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<button @onclick="LogInformation">Log information (and trigger a render)</button>

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    }
}

Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event:

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

Even if you return a Task from OnAfterRenderAsync, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned Task completes.

OnAfterRender and OnAfterRenderAsync aren't called during the prerendering process on the server. The methods are called when the component is rendered interactively after prerendering. When the app prerenders:

  1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, OnAfterRender and OnAfterRenderAsync aren't called.
  2. When the Blazor script (blazor.webassembly.js or blazor.server.js) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, OnAfterRender and OnAfterRenderAsync are called because the app isn't in the prerendering phase any longer.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

State changes (StateHasChanged)

StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered.

StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.

For more information on component rendering and when to call StateHasChanged, including when to invoke it with ComponentBase.InvokeAsync, see ASP.NET Core Razor component rendering.

Handle incomplete async actions at render

Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be null or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are null.

In the FetchData component of the Blazor templates, OnInitializedAsync is overridden to asynchronously receive forecast data (forecasts). When forecasts is null, a loading message is displayed to the user. After the Task returned by OnInitializedAsync completes, the component is rerendered with the updated state.

Pages/FetchData.razor in the Blazor Server template:

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
    }
}

Handle errors

For information on handling errors during lifecycle method execution, see Handle errors in ASP.NET Core Blazor apps.

Stateful reconnection after prerendering

In a Blazor Server app when RenderMode is ServerPrerendered, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered again and interactive. If the OnInitialized{Async} lifecycle method for initializing the component is present, the method is executed twice:

  • When the component is prerendered statically.
  • After the server connection has been established.

This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering.

The following code demonstrates an updated WeatherForecastService in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited Delay (await Task.Delay(...)) simulates a short delay before returning data from the GetForecastAsync method.

WeatherForecastService.cs:

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

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

For more information on the RenderMode, see ASP.NET Core Blazor SignalR guidance.

Although the content in this section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

Prerendering with JavaScript interop

This section applies to Blazor Server and hosted Blazor WebAssembly apps that prerender Razor components. Prerendering is covered in Prerender and integrate ASP.NET Core Razor components.

While an app is prerendering, certain actions, such as calling into JavaScript (JS), aren't possible.

For the following example, the setElementText1 function is placed inside the <head> element. The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

The OnAfterRender{Async} lifecycle event isn't called during the prerendering process on the server. Override the OnAfterRender{Async} method to delay JS interop calls until after the component is rendered and interactive on the client after prerendering.

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

The following component demonstrates how to use JS interop as part of a component's initialization logic in a way that's compatible with prerendering. The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. The developer must be careful to avoid creating an infinite loop in this scenario.

For the following example, the setElementText2 function is placed inside the <head> element. The function is called with IJSRuntime.InvokeAsync and returns a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Where JSRuntime.InvokeAsync is called, the ElementReference is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JS element until after the component is rendered.

StateHasChanged is called to rerender the component with the new state obtained from the JS interop call (for more information, see ASP.NET Core Razor component rendering). The code doesn't create an infinite loop because StateHasChanged is only called when data is null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

Component disposal with IDisposable and IAsyncDisposable

If a component implements IDisposable, IAsyncDisposable, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during component initialization.

Components shouldn't need to implement IDisposable and IAsyncDisposable simultaneously. If both are implemented, the framework only executes the asynchronous overload.

Developer code must ensure that IAsyncDisposable implementations don't take a long time to complete.

Disposal of JavaScript interop object references

Examples throughout the JavaScript (JS) interop articles demonstrate typical object disposal patterns:

JS interop object references are implemented as a map keyed by an identifier on the side of the JS interop call that creates the reference. When object disposal is initiated from either the .NET or JS side, Blazor removes the entry from the map, and the object can be garbage collected as long as no other strong reference to the object is present.

At a minimum, always dispose objects created on the .NET side to avoid leaking .NET managed memory.

Document Object Model (DOM) cleanup tasks during component disposal

Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the MutationObserver pattern in JavaScript on the client for the following reasons:

  • The component may have been removed from the DOM by the time your cleanup code executes in Dispose{Async}.
  • In a Blazor Server app, the Blazor renderer may have been disposed by the framework by the time your cleanup code executes in Dispose{Async}.

The MutationObserver pattern allows you to run a function when an element is removed from the DOM.

For guidance on JSDisconnectedException in Blazor Server apps when a circuit is disconnected, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor or Call .NET methods from JavaScript functions in ASP.NET Core Blazor. For general JavaScript interop error handling guidance, see the JavaScript interop section in Handle errors in ASP.NET Core Blazor apps.

Synchronous IDisposable

For synchronous disposal tasks, use IDisposable.Dispose.

The following component:

  • Implements IDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IDisposable

...

@code {
    ...

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

If a single object requires disposal, a lambda can be used to dispose of the object when Dispose is called. The following example appears in the ASP.NET Core Razor component rendering article and demonstrates the use of a lambda expression for the disposal of a Timer.

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

Note

In the preceding example, the call to StateHasChanged is wrapped by a call to ComponentBase.InvokeAsync because the callback is invoked outside of Blazor's synchronization context. For more information, see ASP.NET Core Razor component rendering.

If the object is created in a lifecycle method, such as OnInitialized/OnInitializedAsync, check for null before calling Dispose.

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

For more information, see:

Asynchronous IAsyncDisposable

For asynchronous disposal tasks, use IAsyncDisposable.DisposeAsync.

The following component:

  • Implements IAsyncDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IAsyncDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IAsyncDisposable

...

@code {
    ...

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

For more information, see:

Assignment of null to disposed objects

Usually, there's no need to assign null to disposed objects after calling Dispose/DisposeAsync. Rare cases for assigning null include the following:

  • If the object's type is poorly implemented and doesn't tolerate repeat calls to Dispose/DisposeAsync, assign null after disposal to gracefully skip further calls to Dispose/DisposeAsync.
  • If a long-lived process continues to hold a reference to a disposed object, assigning null allows the garbage collector to free the object in spite of the long-lived process holding a reference to it.

These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning null to disposed objects. In the rare cases where an object must be assigned null, we recommend documenting the reason and seeking a solution that prevents the need to assign null.

StateHasChanged

Note

Calling StateHasChanged in Dispose isn't supported. StateHasChanged might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported.

Event handlers

Always unsubscribe event handlers from .NET events. The following Blazor form examples show how to unsubscribe an event handler in the Dispose method:

  • Private field and lambda approach

    @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;
        }
    }
    
  • Private method approach

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

For more information, see the Component disposal with IDisposable and IAsyncDisposable section.

Anonymous functions, methods, and expressions

When anonymous functions, methods, or expressions, are used, it isn't necessary to implement IDisposable and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem when the object exposing the event outlives the lifetime of the component registering the delegate. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show.

  • Anonymous lambda method approach (explicit disposal not required):

    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);
    }
    
  • Anonymous lambda expression approach (explicit disposal not required):

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

    The full example of the preceding code with anonymous lambda expressions appears in the ASP.NET Core Blazor forms and input components article.

For more information, see Cleaning up unmanaged resources and the topics that follow it on implementing the Dispose and DisposeAsync methods.

Cancelable background work

Components often perform long-running background work, such as making network calls (HttpClient) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component.

Other reasons why background work items might require cancellation include:

  • An executing background task was started with faulty input data or processing parameters.
  • The current set of executing background work items must be replaced with a new set of work items.
  • The priority of currently executing tasks must be changed.
  • The app must be shut down for server redeployment.
  • Server resources become limited, necessitating the rescheduling of background work items.

To implement a cancelable background work pattern in a component:

In the following example:

  • await Task.Delay(5000, cts.Token); represents long-running asynchronous background work.
  • BackgroundResourceMethod represents a long-running background method that shouldn't start if the Resource is disposed before the method is called.

Pages/BackgroundWork.razor:

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

Blazor Server reconnection events

The component lifecycle events covered in this article operate separately from Blazor Server's reconnection event handlers. When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see ASP.NET Core Blazor SignalR guidance.

The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering.

Lifecycle events

The following simplified diagrams illustrate Razor component lifecycle event processing. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article.

Component lifecycle events:

  1. If the component is rendering for the first time on a request:
  2. Call OnParametersSet{Async}. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Note

Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the Handle incomplete async actions at render section later in this article.

A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

Component lifecycle events of a Razor component in Blazor

Document Object Model (DOM) event processing:

  1. The event handler is run.
  2. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Document Object Model (DOM) event processing

The Render lifecycle:

  1. Avoid further rendering operations on the component:
  2. Build the render tree diff (difference) and render the component.
  3. Await the DOM to update.
  4. Call OnAfterRender{Async}.

Render lifecycle

Developer calls to StateHasChanged result in a render. For more information, see ASP.NET Core Razor component rendering.

This article simplifies some aspects of component lifecycle event processing in order to clarify complex framework logic. You may need to access the ComponentBase reference source to integrate custom event processing with Blazor's lifecycle event processing. Code comments in the reference source include additional remarks about lifecycle event processing that don't appear in this article or in the API documentation. Note that Blazor's lifecycle event processing has changed over time and is subject to change without notice each release.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

When parameters are set (SetParametersAsync)

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

The method's ParameterView parameter contains the set of component parameter values for the component each time SetParametersAsync is called. By overriding the SetParametersAsync method, developer code can interact directly with ParameterView's parameters.

The default implementation of SetParametersAsync sets the value of each property with the [Parameter] or [CascadingParameter] attribute that has a corresponding value in the ParameterView. Parameters that don't have a corresponding value in ParameterView are left unchanged.

If base.SetParametersAsync isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

In the following example, ParameterView.TryGetValue assigns the Param parameter's value to value if parsing a route parameter for Param is successful. When value isn't null, the value is displayed by the component.

Although route parameter matching is case insensitive, TryGetValue only matches case-sensitive parameter names in the route template. The following example requires the use of /{Param?} in the route template in order to get the value with TryGetValue, not /{param?}. If /{param?} is used in this scenario, TryGetValue returns false and message isn't set to either message string.

Pages/SetParamsAsync.razor:

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

Component initialization (OnInitialized{Async})

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync.

If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

For a synchronous operation, override OnInitialized:

Pages/OnInit.razor:

@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

To perform an asynchronous operation, override OnInitializedAsync and use the await operator:

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

Blazor apps that prerender their content on the server call OnInitializedAsync twice:

  • Once when the component is initially rendered statically as part of the page.
  • A second time when the browser renders the component.

To prevent developer code in OnInitializedAsync from running twice when prerendering, see the Stateful reconnection after prerendering section. Although the content in the section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the Prerendering with JavaScript interop section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

After parameters are set (OnParametersSet{Async})

OnParametersSet or OnParametersSetAsync are called:

  • After the component is initialized in OnInitialized or OnInitializedAsync.

  • When the parent component rerenders and supplies:

    • Known or primitive immutable types when at least one parameter has changed.
    • Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present.

    For more information on rendering conventions, see ASP.NET Core Razor component rendering.

For the following example component, navigate to the component's page at a URL:

  • With a start date that's received by StartDate: /on-parameters-set/2021-03-19
  • Without a start date, where StartDate is assigned a value of the current local time: /on-parameters-set

Pages/OnParamsSet.razor:

Note

In a component route, it isn't possible to both constrain a DateTime parameter with the route constraint datetime and make the parameter optional. Therefore, the following OnParamsSet component uses two @page directives to handle routing with and without a supplied date segment in the URL.

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

Asynchronous work when applying parameters and property values must occur during the OnParametersSetAsync lifecycle event:

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

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

For more information on route parameters and constraints, see ASP.NET Core Blazor routing and navigation.

After component render (OnAfterRender{Async})

OnAfterRender and OnAfterRenderAsync are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements.

The firstRender parameter for OnAfterRender and OnAfterRenderAsync:

  • Is set to true the first time that the component instance is rendered.
  • Can be used to ensure that initialization work is only performed once.

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<button @onclick="LogInformation">Log information (and trigger a render)</button>

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    }
}

Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event:

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

Even if you return a Task from OnAfterRenderAsync, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned Task completes.

OnAfterRender and OnAfterRenderAsync aren't called during the prerendering process on the server. The methods are called when the component is rendered and interactive after prerendering. When the app prerenders:

  1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, OnAfterRender and OnAfterRenderAsync aren't called.
  2. When the Blazor script (blazor.webassembly.js or blazor.server.js) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, OnAfterRender and OnAfterRenderAsync are called because the app isn't in the prerendering phase any longer.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

State changes (StateHasChanged)

StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered.

StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.

For more information on component rendering and when to call StateHasChanged, including when to invoke it with ComponentBase.InvokeAsync, see ASP.NET Core Razor component rendering.

Handle incomplete async actions at render

Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be null or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are null.

In the FetchData component of the Blazor templates, OnInitializedAsync is overridden to asynchronously receive forecast data (forecasts). When forecasts is null, a loading message is displayed to the user. After the Task returned by OnInitializedAsync completes, the component is rerendered with the updated state.

Pages/FetchData.razor in the Blazor Server template:

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

Handle errors

For information on handling errors during lifecycle method execution, see Handle errors in ASP.NET Core Blazor apps.

Stateful reconnection after prerendering

In a Blazor Server app when RenderMode is ServerPrerendered, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered again and interactive. If the OnInitialized{Async} lifecycle method for initializing the component is present, the method is executed twice:

  • When the component is prerendered statically.
  • After the server connection has been established.

This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering.

The following code demonstrates an updated WeatherForecastService in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited Delay (await Task.Delay(...)) simulates a short delay before returning data from the GetForecastAsync method.

WeatherForecastService.cs:

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

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

For more information on the RenderMode, see ASP.NET Core Blazor SignalR guidance.

Although the content in this section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

Prerendering with JavaScript interop

This section applies to Blazor Server and hosted Blazor WebAssembly apps that prerender Razor components. Prerendering is covered in Prerender and integrate ASP.NET Core Razor components.

While an app is prerendering, certain actions, such as calling into JavaScript (JS), aren't possible.

For the following example, the setElementText1 function is placed inside the <head> element. The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

The OnAfterRender{Async} lifecycle event isn't called during the prerendering process on the server. Override the OnAfterRender{Async} method to delay JS interop calls until after the component is rendered and interactive on the client after prerendering.

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

The following component demonstrates how to use JS interop as part of a component's initialization logic in a way that's compatible with prerendering. The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. The developer must be careful to avoid creating an infinite loop in this scenario.

For the following example, the setElementText2 function is placed inside the <head> element. The function is called with IJSRuntime.InvokeAsync and returns a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Where JSRuntime.InvokeAsync is called, the ElementReference is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JS element until after the component is rendered.

StateHasChanged is called to rerender the component with the new state obtained from the JS interop call (for more information, see ASP.NET Core Razor component rendering). The code doesn't create an infinite loop because StateHasChanged is only called when data is null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

Component disposal with IDisposable and IAsyncDisposable

If a component implements IDisposable, IAsyncDisposable, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during component initialization.

Components shouldn't need to implement IDisposable and IAsyncDisposable simultaneously. If both are implemented, the framework only executes the asynchronous overload.

Developer code must ensure that IAsyncDisposable implementations don't take a long time to complete.

Document Object Model (DOM) cleanup tasks during component disposal

Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the MutationObserver pattern in JavaScript on the client for the following reasons:

  • The component may have been removed from the DOM by the time your cleanup code executes in Dispose{Async}.
  • In a Blazor Server app, the Blazor renderer may have been disposed by the framework by the time your cleanup code executes in Dispose{Async}.

The MutationObserver pattern allows you to run a function when an element is removed from the DOM.

For guidance on JSDisconnectedException in Blazor Server apps when a circuit is disconnected, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor or Call .NET methods from JavaScript functions in ASP.NET Core Blazor. For general JavaScript interop error handling guidance, see the JavaScript interop section in Handle errors in ASP.NET Core Blazor apps.

Synchronous IDisposable

For synchronous disposal tasks, use IDisposable.Dispose.

The following component:

  • Implements IDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IDisposable

...

@code {
    ...

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

If a single object requires disposal, a lambda can be used to dispose of the object when Dispose is called. The following example appears in the ASP.NET Core Razor component rendering article and demonstrates the use of a lambda expression for the disposal of a Timer.

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

Note

In the preceding example, the call to StateHasChanged is wrapped by a call to ComponentBase.InvokeAsync because the callback is invoked outside of Blazor's synchronization context. For more information, see ASP.NET Core Razor component rendering.

If the object is created in a lifecycle method, such as OnInitialized/OnInitializedAsync, check for null before calling Dispose.

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

For more information, see:

Asynchronous IAsyncDisposable

For asynchronous disposal tasks, use IAsyncDisposable.DisposeAsync.

The following component:

  • Implements IAsyncDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IAsyncDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IAsyncDisposable

...

@code {
    ...

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

For more information, see:

Assignment of null to disposed objects

Usually, there's no need to assign null to disposed objects after calling Dispose/DisposeAsync. Rare cases for assigning null include the following:

  • If the object's type is poorly implemented and doesn't tolerate repeat calls to Dispose/DisposeAsync, assign null after disposal to gracefully skip further calls to Dispose/DisposeAsync.
  • If a long-lived process continues to hold a reference to a disposed object, assigning null allows the garbage collector to free the object in spite of the long-lived process holding a reference to it.

These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning null to disposed objects. In the rare cases where an object must be assigned null, we recommend documenting the reason and seeking a solution that prevents the need to assign null.

StateHasChanged

Note

Calling StateHasChanged in Dispose isn't supported. StateHasChanged might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported.

Event handlers

Always unsubscribe event handlers from .NET events. The following Blazor form examples show how to unsubscribe an event handler in the Dispose method:

  • Private field and lambda approach

    @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;
        }
    }
    
  • Private method approach

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

For more information, see the Component disposal with IDisposable and IAsyncDisposable section.

Anonymous functions, methods, and expressions

When anonymous functions, methods, or expressions, are used, it isn't necessary to implement IDisposable and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem when the object exposing the event outlives the lifetime of the component registering the delegate. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show.

  • Anonymous lambda method approach (explicit disposal not required):

    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);
    }
    
  • Anonymous lambda expression approach (explicit disposal not required):

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

    The full example of the preceding code with anonymous lambda expressions appears in the ASP.NET Core Blazor forms and input components article.

For more information, see Cleaning up unmanaged resources and the topics that follow it on implementing the Dispose and DisposeAsync methods.

Cancelable background work

Components often perform long-running background work, such as making network calls (HttpClient) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component.

Other reasons why background work items might require cancellation include:

  • An executing background task was started with faulty input data or processing parameters.
  • The current set of executing background work items must be replaced with a new set of work items.
  • The priority of currently executing tasks must be changed.
  • The app must be shut down for server redeployment.
  • Server resources become limited, necessitating the rescheduling of background work items.

To implement a cancelable background work pattern in a component:

In the following example:

  • await Task.Delay(5000, cts.Token); represents long-running asynchronous background work.
  • BackgroundResourceMethod represents a long-running background method that shouldn't start if the Resource is disposed before the method is called.

Pages/BackgroundWork.razor:

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

Blazor Server reconnection events

The component lifecycle events covered in this article operate separately from Blazor Server's reconnection event handlers. When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see ASP.NET Core Blazor SignalR guidance.

The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering.

Lifecycle events

The following simplified diagrams illustrate Razor component lifecycle event processing. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article.

Component lifecycle events:

  1. If the component is rendering for the first time on a request:
  2. Call OnParametersSet{Async}. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Note

Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the Handle incomplete async actions at render section later in this article.

A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

Component lifecycle events of a Razor component in Blazor

Document Object Model (DOM) event processing:

  1. The event handler is run.
  2. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Document Object Model (DOM) event processing

The Render lifecycle:

  1. Avoid further rendering operations on the component:
  2. Build the render tree diff (difference) and render the component.
  3. Await the DOM to update.
  4. Call OnAfterRender{Async}.

Render lifecycle

Developer calls to StateHasChanged result in a render. For more information, see ASP.NET Core Razor component rendering.

This article simplifies some aspects of component lifecycle event processing in order to clarify complex framework logic. You may need to access the ComponentBase reference source to integrate custom event processing with Blazor's lifecycle event processing. Code comments in the reference source include additional remarks about lifecycle event processing that don't appear in this article or in the API documentation. Note that Blazor's lifecycle event processing has changed over time and is subject to change without notice each release.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

When parameters are set (SetParametersAsync)

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

The method's ParameterView parameter contains the set of component parameter values for the component each time SetParametersAsync is called. By overriding the SetParametersAsync method, developer code can interact directly with ParameterView's parameters.

The default implementation of SetParametersAsync sets the value of each property with the [Parameter] or [CascadingParameter] attribute that has a corresponding value in the ParameterView. Parameters that don't have a corresponding value in ParameterView are left unchanged.

If base.SetParametersAsync isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

In the following example, ParameterView.TryGetValue assigns the Param parameter's value to value if parsing a route parameter for Param is successful. When value isn't null, the value is displayed by the component.

Although route parameter matching is case insensitive, TryGetValue only matches case-sensitive parameter names in the route template. The following example requires the use of /{Param?} in the route template in order to get the value with TryGetValue, not /{param?}. If /{param?} is used in this scenario, TryGetValue returns false and message isn't set to either message string.

Pages/SetParamsAsync.razor:

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

Component initialization (OnInitialized{Async})

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync.

If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

For a synchronous operation, override OnInitialized:

Pages/OnInit.razor:

@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

To perform an asynchronous operation, override OnInitializedAsync and use the await operator:

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

Blazor apps that prerender their content on the server call OnInitializedAsync twice:

  • Once when the component is initially rendered statically as part of the page.
  • A second time when the browser renders the component.

To prevent developer code in OnInitializedAsync from running twice when prerendering, see the Stateful reconnection after prerendering section. Although the content in the section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the Prerendering with JavaScript interop section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

After parameters are set (OnParametersSet{Async})

OnParametersSet or OnParametersSetAsync are called:

  • After the component is initialized in OnInitialized or OnInitializedAsync.

  • When the parent component rerenders and supplies:

    • Known or primitive immutable types when at least one parameter has changed.
    • Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present.

    For more information on rendering conventions, see ASP.NET Core Razor component rendering.

For the following example component, navigate to the component's page at a URL:

  • With a start date that's received by StartDate: /on-parameters-set/2021-03-19
  • Without a start date, where StartDate is assigned a value of the current local time: /on-parameters-set

Pages/OnParamsSet.razor:

Note

In a component route, it isn't possible to both constrain a DateTime parameter with the route constraint datetime and make the parameter optional. Therefore, the following OnParamsSet component uses two @page directives to handle routing with and without a supplied date segment in the URL.

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

Asynchronous work when applying parameters and property values must occur during the OnParametersSetAsync lifecycle event:

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

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

For more information on route parameters and constraints, see ASP.NET Core Blazor routing and navigation.

After component render (OnAfterRender{Async})

OnAfterRender and OnAfterRenderAsync are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements.

The firstRender parameter for OnAfterRender and OnAfterRenderAsync:

  • Is set to true the first time that the component instance is rendered.
  • Can be used to ensure that initialization work is only performed once.

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<button @onclick="LogInformation">Log information (and trigger a render)</button>

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    }
}

Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event:

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

Even if you return a Task from OnAfterRenderAsync, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned Task completes.

OnAfterRender and OnAfterRenderAsync aren't called during the prerendering process on the server. The methods are called when the component is rendered and interactive after prerendering. When the app prerenders:

  1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, OnAfterRender and OnAfterRenderAsync aren't called.
  2. When the Blazor script (blazor.webassembly.js or blazor.server.js) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, OnAfterRender and OnAfterRenderAsync are called because the app isn't in the prerendering phase any longer.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

State changes (StateHasChanged)

StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered.

StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.

For more information on component rendering and when to call StateHasChanged, including when to invoke it with ComponentBase.InvokeAsync, see ASP.NET Core Razor component rendering.

Handle incomplete async actions at render

Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be null or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are null.

In the FetchData component of the Blazor templates, OnInitializedAsync is overridden to asynchronously receive forecast data (forecasts). When forecasts is null, a loading message is displayed to the user. After the Task returned by OnInitializedAsync completes, the component is rerendered with the updated state.

Pages/FetchData.razor in the Blazor Server template:

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

Handle errors

For information on handling errors during lifecycle method execution, see Handle errors in ASP.NET Core Blazor apps.

Stateful reconnection after prerendering

In a Blazor Server app when RenderMode is ServerPrerendered, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered again and interactive. If the OnInitialized{Async} lifecycle method for initializing the component is present, the method is executed twice:

  • When the component is prerendered statically.
  • After the server connection has been established.

This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering.

The following code demonstrates an updated WeatherForecastService in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited Delay (await Task.Delay(...)) simulates a short delay before returning data from the GetForecastAsync method.

WeatherForecastService.cs:

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

For more information on the RenderMode, see ASP.NET Core Blazor SignalR guidance.

Although the content in this section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

Prerendering with JavaScript interop

This section applies to Blazor Server and hosted Blazor WebAssembly apps that prerender Razor components. Prerendering is covered in Prerender and integrate ASP.NET Core Razor components.

While an app is prerendering, certain actions, such as calling into JavaScript (JS), aren't possible.

For the following example, the setElementText1 function is placed inside the <head> element. The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

The OnAfterRender{Async} lifecycle event isn't called during the prerendering process on the server. Override the OnAfterRender{Async} method to delay JS interop calls until after the component is rendered and interactive on the client after prerendering.

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

The following component demonstrates how to use JS interop as part of a component's initialization logic in a way that's compatible with prerendering. The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. The developer must be careful to avoid creating an infinite loop in this scenario.

For the following example, the setElementText2 function is placed inside the <head> element. The function is called with IJSRuntime.InvokeAsync and returns a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Where JSRuntime.InvokeAsync is called, the ElementReference is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JS element until after the component is rendered.

StateHasChanged is called to rerender the component with the new state obtained from the JS interop call (for more information, see ASP.NET Core Razor component rendering). The code doesn't create an infinite loop because StateHasChanged is only called when data is null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

Component disposal with IDisposable and IAsyncDisposable

If a component implements IDisposable, IAsyncDisposable, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during component initialization.

Components shouldn't need to implement IDisposable and IAsyncDisposable simultaneously. If both are implemented, the framework only executes the asynchronous overload.

Developer code must ensure that IAsyncDisposable implementations don't take a long time to complete.

Document Object Model (DOM) cleanup tasks during component disposal

Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the MutationObserver pattern in JavaScript on the client for the following reasons:

  • The component may have been removed from the DOM by the time your cleanup code executes in Dispose{Async}.
  • In a Blazor Server app, the Blazor renderer may have been disposed by the framework by the time your cleanup code executes in Dispose{Async}.

The MutationObserver pattern allows you to run a function when an element is removed from the DOM.

For guidance on JSDisconnectedException in Blazor Server apps when a circuit is disconnected, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor or Call .NET methods from JavaScript functions in ASP.NET Core Blazor. For general JavaScript interop error handling guidance, see the JavaScript interop section in Handle errors in ASP.NET Core Blazor apps.

Synchronous IDisposable

For synchronous disposal tasks, use IDisposable.Dispose.

The following component:

  • Implements IDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IDisposable

...

@code {
    ...

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

If a single object requires disposal, a lambda can be used to dispose of the object when Dispose is called. The following example appears in the ASP.NET Core Razor component rendering article and demonstrates the use of a lambda expression for the disposal of a Timer.

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

Note

In the preceding example, the call to StateHasChanged is wrapped by a call to ComponentBase.InvokeAsync because the callback is invoked outside of Blazor's synchronization context. For more information, see ASP.NET Core Razor component rendering.

If the object is created in a lifecycle method, such as OnInitialized/OnInitializedAsync, check for null before calling Dispose.

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

For more information, see:

Asynchronous IAsyncDisposable

For asynchronous disposal tasks, use IAsyncDisposable.DisposeAsync.

The following component:

  • Implements IAsyncDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IAsyncDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IAsyncDisposable

...

@code {
    ...

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

For more information, see:

Assignment of null to disposed objects

Usually, there's no need to assign null to disposed objects after calling Dispose/DisposeAsync. Rare cases for assigning null include the following:

  • If the object's type is poorly implemented and doesn't tolerate repeat calls to Dispose/DisposeAsync, assign null after disposal to gracefully skip further calls to Dispose/DisposeAsync.
  • If a long-lived process continues to hold a reference to a disposed object, assigning null allows the garbage collector to free the object in spite of the long-lived process holding a reference to it.

These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning null to disposed objects. In the rare cases where an object must be assigned null, we recommend documenting the reason and seeking a solution that prevents the need to assign null.

StateHasChanged

Note

Calling StateHasChanged in Dispose isn't supported. StateHasChanged might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported.

Event handlers

Always unsubscribe event handlers from .NET events. The following Blazor form examples show how to unsubscribe an event handler in the Dispose method:

  • Private field and lambda approach

    @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;
        }
    }
    
  • Private method approach

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

For more information, see the Component disposal with IDisposable and IAsyncDisposable section.

Anonymous functions, methods, and expressions

When anonymous functions, methods, or expressions, are used, it isn't necessary to implement IDisposable and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem when the object exposing the event outlives the lifetime of the component registering the delegate. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show.

  • Anonymous lambda method approach (explicit disposal not required):

    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);
    }
    
  • Anonymous lambda expression approach (explicit disposal not required):

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

    The full example of the preceding code with anonymous lambda expressions appears in the ASP.NET Core Blazor forms and input components article.

For more information, see Cleaning up unmanaged resources and the topics that follow it on implementing the Dispose and DisposeAsync methods.

Cancelable background work

Components often perform long-running background work, such as making network calls (HttpClient) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component.

Other reasons why background work items might require cancellation include:

  • An executing background task was started with faulty input data or processing parameters.
  • The current set of executing background work items must be replaced with a new set of work items.
  • The priority of currently executing tasks must be changed.
  • The app must be shut down for server redeployment.
  • Server resources become limited, necessitating the rescheduling of background work items.

To implement a cancelable background work pattern in a component:

In the following example:

  • await Task.Delay(5000, cts.Token); represents long-running asynchronous background work.
  • BackgroundResourceMethod represents a long-running background method that shouldn't start if the Resource is disposed before the method is called.

Pages/BackgroundWork.razor:

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

Blazor Server reconnection events

The component lifecycle events covered in this article operate separately from Blazor Server's reconnection event handlers. When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see ASP.NET Core Blazor SignalR guidance.

The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering.

Lifecycle events

The following simplified diagrams illustrate Razor component lifecycle event processing. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article.

Component lifecycle events:

  1. If the component is rendering for the first time on a request:
  2. Call OnParametersSet{Async}. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Note

Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the Handle incomplete async actions at render section later in this article.

A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

Component lifecycle events of a Razor component in Blazor

Document Object Model (DOM) event processing:

  1. The event handler is run.
  2. If an incomplete Task is returned, the Task is awaited and then the component is rerendered.
  3. Render for all synchronous work and complete Tasks.

Document Object Model (DOM) event processing

The Render lifecycle:

  1. Avoid further rendering operations on the component:
  2. Build the render tree diff (difference) and render the component.
  3. Await the DOM to update.
  4. Call OnAfterRender{Async}.

Render lifecycle

Developer calls to StateHasChanged result in a render. For more information, see ASP.NET Core Razor component rendering.

This article simplifies some aspects of component lifecycle event processing in order to clarify complex framework logic. You may need to access the ComponentBase reference source to integrate custom event processing with Blazor's lifecycle event processing. Code comments in the reference source include additional remarks about lifecycle event processing that don't appear in this article or in the API documentation. Note that Blazor's lifecycle event processing has changed over time and is subject to change without notice each release.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

When parameters are set (SetParametersAsync)

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

The method's ParameterView parameter contains the set of component parameter values for the component each time SetParametersAsync is called. By overriding the SetParametersAsync method, developer code can interact directly with ParameterView's parameters.

The default implementation of SetParametersAsync sets the value of each property with the [Parameter] or [CascadingParameter] attribute that has a corresponding value in the ParameterView. Parameters that don't have a corresponding value in ParameterView are left unchanged.

If base.SetParametersAsync isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

In the following example, ParameterView.TryGetValue assigns the Param parameter's value to value if parsing a route parameter for Param is successful. When value isn't null, the value is displayed by the component.

Although route parameter matching is case insensitive, TryGetValue only matches case-sensitive parameter names in the route template. The following example requires the use of /{Param?} in the route template in order to get the value with TryGetValue, not /{param?}. If /{param?} is used in this scenario, TryGetValue returns false and message isn't set to either message string.

Pages/SetParamsAsync.razor:

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

Component initialization (OnInitialized{Async})

OnInitialized and OnInitializedAsync are invoked when the component is initialized after having received its initial parameters in SetParametersAsync.

If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running.

For a synchronous operation, override OnInitialized:

Pages/OnInit.razor:

@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

To perform an asynchronous operation, override OnInitializedAsync and use the await operator:

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

Blazor apps that prerender their content on the server call OnInitializedAsync twice:

  • Once when the component is initially rendered statically as part of the page.
  • A second time when the browser renders the component.

To prevent developer code in OnInitializedAsync from running twice when prerendering, see the Stateful reconnection after prerendering section. Although the content in the section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the Prerendering with JavaScript interop section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

After parameters are set (OnParametersSet{Async})

OnParametersSet or OnParametersSetAsync are called:

  • After the component is initialized in OnInitialized or OnInitializedAsync.

  • When the parent component rerenders and supplies:

    • Known or primitive immutable types when at least one parameter has changed.
    • Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present.

    For more information on rendering conventions, see ASP.NET Core Razor component rendering.

For the following example component, navigate to the component's page at a URL:

  • With a start date that's received by StartDate: /on-parameters-set/2021-03-19
  • Without a start date, where StartDate is assigned a value of the current local time: /on-parameters-set

Pages/OnParamsSet.razor:

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

Asynchronous work when applying parameters and property values must occur during the OnParametersSetAsync lifecycle event:

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

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

For more information on route parameters and constraints, see ASP.NET Core Blazor routing and navigation.

After component render (OnAfterRender{Async})

OnAfterRender and OnAfterRenderAsync are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements.

The firstRender parameter for OnAfterRender and OnAfterRenderAsync:

  • Is set to true the first time that the component instance is rendered.
  • Can be used to ensure that initialization work is only performed once.

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<button @onclick="LogInformation">Log information (and trigger a render)</button>

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

    private void LogInformation()
    {
        Logger.LogInformation("LogInformation called");
    }
}

Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event:

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

Even if you return a Task from OnAfterRenderAsync, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned Task completes.

OnAfterRender and OnAfterRenderAsync aren't called during the prerendering process on the server. The methods are called when the component is rendered and interactive after prerendering. When the app prerenders:

  1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, OnAfterRender and OnAfterRenderAsync aren't called.
  2. When the Blazor script (blazor.webassembly.js or blazor.server.js) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, OnAfterRender and OnAfterRenderAsync are called because the app isn't in the prerendering phase any longer.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the Component disposal with IDisposable IAsyncDisposable section.

State changes (StateHasChanged)

StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered.

StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.

For more information on component rendering and when to call StateHasChanged, including when to invoke it with ComponentBase.InvokeAsync, see ASP.NET Core Razor component rendering.

Handle incomplete async actions at render

Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be null or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are null.

In the FetchData component of the Blazor templates, OnInitializedAsync is overridden to asynchronously receive forecast data (forecasts). When forecasts is null, a loading message is displayed to the user. After the Task returned by OnInitializedAsync completes, the component is rerendered with the updated state.

Pages/FetchData.razor in the Blazor Server template:

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

Handle errors

For information on handling errors during lifecycle method execution, see Handle errors in ASP.NET Core Blazor apps.

Stateful reconnection after prerendering

In a Blazor Server app when RenderMode is ServerPrerendered, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered again and interactive. If the OnInitialized{Async} lifecycle method for initializing the component is present, the method is executed twice:

  • When the component is prerendered statically.
  • After the server connection has been established.

This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering.

The following code demonstrates an updated WeatherForecastService in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited Delay (await Task.Delay(...)) simulates a short delay before returning data from the GetForecastAsync method.

WeatherForecastService.cs:

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

For more information on the RenderMode, see ASP.NET Core Blazor SignalR guidance.

Although the content in this section focuses on Blazor Server and stateful SignalR reconnection, the scenario for prerendering in hosted Blazor WebAssembly apps (WebAssemblyPrerendered) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see Prerender and integrate ASP.NET Core Razor components.

Prerendering with JavaScript interop

This section applies to Blazor Server and hosted Blazor WebAssembly apps that prerender Razor components. Prerendering is covered in Prerender and integrate ASP.NET Core Razor components.

While an app is prerendering, certain actions, such as calling into JavaScript (JS), aren't possible.

For the following example, the setElementText1 function is placed inside the <head> element. The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

The OnAfterRender{Async} lifecycle event isn't called during the prerendering process on the server. Override the OnAfterRender{Async} method to delay JS interop calls until after the component is rendered and interactive on the client after prerendering.

Pages/PrerenderedInterop1.razor:

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

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

@code {
    private ElementReference divElement;

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

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

The following component demonstrates how to use JS interop as part of a component's initialization logic in a way that's compatible with prerendering. The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. The developer must be careful to avoid creating an infinite loop in this scenario.

For the following example, the setElementText2 function is placed inside the <head> element. The function is called with IJSRuntime.InvokeAsync and returns a value.

Note

For general guidance on JS location and our recommendations for production apps, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

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

Warning

The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. Directly modifying the DOM with JS isn't recommended in most scenarios because JS can interfere with Blazor's change tracking. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Where JSRuntime.InvokeAsync is called, the ElementReference is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JS element until after the component is rendered.

StateHasChanged is called to rerender the component with the new state obtained from the JS interop call (for more information, see ASP.NET Core Razor component rendering). The code doesn't create an infinite loop because StateHasChanged is only called when data is null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

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

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

            StateHasChanged();
        }
    }
}

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Example:

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

Component disposal with IDisposable and IAsyncDisposable

If a component implements IDisposable, IAsyncDisposable, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during component initialization.

Components shouldn't need to implement IDisposable and IAsyncDisposable simultaneously. If both are implemented, the framework only executes the asynchronous overload.

Developer code must ensure that IAsyncDisposable implementations don't take a long time to complete.

Document Object Model (DOM) cleanup tasks during component disposal

Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the MutationObserver pattern in JavaScript on the client for the following reasons:

  • The component may have been removed from the DOM by the time your cleanup code executes in Dispose{Async}.
  • In a Blazor Server app, the Blazor renderer may have been disposed by the framework by the time your cleanup code executes in Dispose{Async}.

The MutationObserver pattern allows you to run a function when an element is removed from the DOM.

For guidance on JSDisconnectedException in Blazor Server apps when a circuit is disconnected, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor or Call .NET methods from JavaScript functions in ASP.NET Core Blazor. For general JavaScript interop error handling guidance, see the JavaScript interop section in Handle errors in ASP.NET Core Blazor apps.

Synchronous IDisposable

For synchronous disposal tasks, use IDisposable.Dispose.

The following component:

  • Implements IDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IDisposable

...

@code {
    ...

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

If a single object requires disposal, a lambda can be used to dispose of the object when Dispose is called. The following example appears in the ASP.NET Core Razor component rendering article and demonstrates the use of a lambda expression for the disposal of a Timer.

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

Note

In the preceding example, the call to StateHasChanged is wrapped by a call to ComponentBase.InvokeAsync because the callback is invoked outside of Blazor's synchronization context. For more information, see ASP.NET Core Razor component rendering.

If the object is created in a lifecycle method, such as OnInitialized/OnInitializedAsync, check for null before calling Dispose.

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

For more information, see:

Asynchronous IAsyncDisposable

For asynchronous disposal tasks, use IAsyncDisposable.DisposeAsync.

The following component:

  • Implements IAsyncDisposable with the @implements Razor directive.
  • Disposes of obj, which is an unmanaged type that implements IAsyncDisposable.
  • A null check is performed because obj is created in a lifecycle method (not shown).
@implements IAsyncDisposable

...

@code {
    ...

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

For more information, see:

Assignment of null to disposed objects

Usually, there's no need to assign null to disposed objects after calling Dispose/DisposeAsync. Rare cases for assigning null include the following:

  • If the object's type is poorly implemented and doesn't tolerate repeat calls to Dispose/DisposeAsync, assign null after disposal to gracefully skip further calls to Dispose/DisposeAsync.
  • If a long-lived process continues to hold a reference to a disposed object, assigning null allows the garbage collector to free the object in spite of the long-lived process holding a reference to it.

These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning null to disposed objects. In the rare cases where an object must be assigned null, we recommend documenting the reason and seeking a solution that prevents the need to assign null.

StateHasChanged

Note

Calling StateHasChanged in Dispose isn't supported. StateHasChanged might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported.

Event handlers

Always unsubscribe event handlers from .NET events. The following Blazor form examples show how to unsubscribe an event handler in the Dispose method:

  • Private field and lambda approach

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

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

For more information, see the Component disposal with IDisposable and IAsyncDisposable section.

Anonymous functions, methods, and expressions

When anonymous functions, methods, or expressions, are used, it isn't necessary to implement IDisposable and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem when the object exposing the event outlives the lifetime of the component registering the delegate. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show.

  • Anonymous lambda method approach (explicit disposal not required):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new EditContext(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • Anonymous lambda expression approach (explicit disposal not required):

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

    The full example of the preceding code with anonymous lambda expressions appears in the ASP.NET Core Blazor forms and input components article.

For more information, see Cleaning up unmanaged resources and the topics that follow it on implementing the Dispose and DisposeAsync methods.

Cancelable background work

Components often perform long-running background work, such as making network calls (HttpClient) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component.

Other reasons why background work items might require cancellation include:

  • An executing background task was started with faulty input data or processing parameters.
  • The current set of executing background work items must be replaced with a new set of work items.
  • The priority of currently executing tasks must be changed.
  • The app must be shut down for server redeployment.
  • Server resources become limited, necessitating the rescheduling of background work items.

To implement a cancelable background work pattern in a component:

In the following example:

  • await Task.Delay(5000, cts.Token); represents long-running asynchronous background work.
  • BackgroundResourceMethod represents a long-running background method that shouldn't start if the Resource is disposed before the method is called.

Pages/BackgroundWork.razor:

@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 reconnection events

The component lifecycle events covered in this article operate separately from Blazor Server's reconnection event handlers. When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see ASP.NET Core Blazor SignalR guidance.