This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
Note
This isn't the latest version of this article. For the current release, see the.NET 10 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the.NET and .NET Core Support Policy. For the current release, see the.NET 9 version of this article.
This article describes how Blazor manages unhandled exceptions and how to develop apps that detect and handle errors.
When a Blazor app isn't functioning properly during development, receiving detailed error information from the app assists in troubleshooting and fixing the issue. When an error occurs, Blazor apps display a light yellow bar at the bottom of the screen:
The UI for this error handling experience is part of theBlazor project templates. Not all versions of the Blazor project templates use thedata-nosnippet attribute to signal to browsers not to cache the contents of the error UI, but all versions of the Blazor documentation apply the attribute.
In a Blazor Web App, customize the experience in theMainLayout component. Because theEnvironment Tag Helper (for example,<environment include="Production">...</environment>) isn't supported in Razor components, the following example injectsIHostEnvironment to configure error messages for different environments.
At the top ofMainLayout.razor:
@inject IHostEnvironment HostEnvironmentCreate or modify the Blazor error UI markup:
<div data-nosnippet> @if (HostEnvironment.IsProduction()) { <span>An error has occurred.</span> } else { <span>An unhandled exception occurred.</span> } <a href="">Reload</a> <a>🗙</a></div>In a Blazor Server app, customize the experience in thePages/_Host.cshtml file. The following example uses theEnvironment Tag Helper to configure error messages for different environments.
In a Blazor Server app, customize the experience in thePages/_Layout.cshtml file. The following example uses theEnvironment Tag Helper to configure error messages for different environments.
In a Blazor Server app, customize the experience in thePages/_Host.cshtml file. The following example uses theEnvironment Tag Helper to configure error messages for different environments.
Create or modify the Blazor error UI markup:
<div data-nosnippet> <environment include="Staging,Production"> An error has occurred. </environment> <environment include="Development"> An unhandled exception occurred. </environment> <a href="">Reload</a> <a>🗙</a></div>In a Blazor WebAssembly app, customize the experience in thewwwroot/index.html file:
<div data-nosnippet> An unhandled error has occurred. <a href="">Reload</a> <a>🗙</a></div>Theblazor-error-ui element is normally hidden due to the presence of thedisplay: none style of theblazor-error-ui CSS class in the app's auto-generated stylesheet. When an error occurs, the framework appliesdisplay: block to the element.
Theblazor-error-ui element is normally hidden due to the presence of thedisplay: none style of theblazor-error-ui CSS class in the site's stylesheet in thewwwroot/css folder. When an error occurs, the framework appliesdisplay: block to the element.
This section applies to Blazor Web Apps operating over a circuit.
This section applies to Blazor Server apps.
Client-side errors don't include the call stack and don't provide detail on the cause of the error, but server logs do contain such information. For development purposes, sensitive circuit error information can be made available to the client by enabling detailed errors.
SetCircuitOptions.DetailedErrors totrue. For more information and an example, seeASP.NET Core Blazor SignalR guidance.
An alternative to settingCircuitOptions.DetailedErrors is to set theDetailedErrors configuration key totrue in the app'sDevelopment environment settings file (appsettings.Development.json). Additionally, setSignalR server-side logging (Microsoft.AspNetCore.SignalR) toDebug orTrace for detailed SignalR logging.
appsettings.Development.json:
{ "DetailedErrors": true, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.SignalR": "Debug" } }}TheDetailedErrors configuration key can also be set totrue using theASPNETCORE_DETAILEDERRORS environment variable with a value oftrue onDevelopment/Staging environment servers or on your local system.
Warning
Always avoid exposing error information to clients on the Internet, which is a security risk.
This section applies to Blazor Web Apps.
Use theRazorComponentsServiceOptions.DetailedErrors option to control producing detailed information on errors for Razor component server-side rendering. The default value isfalse.
The following example enables detailed errors:
builder.Services.AddRazorComponents(options => options.DetailedErrors = builder.Environment.IsDevelopment());Warning
Only enable detailed errors in theDevelopment environment. Detailed errors may contain sensitive information about the app that malicious users can use in an attack.
The preceding example provides a degree of safety by setting the value ofDetailedErrors based on the value returned byIsDevelopment. When the app is in theDevelopment environment,DetailedErrors is set totrue. This approach isn't foolproof because it's possible to host a production app on a public server in theDevelopment environment.
For an app to continue after an error, the app must have error handling logic. Later sections of this article describe potential sources of unhandled exceptions.
In production, don't render framework exception messages or stack traces in the UI. Rendering exception messages or stack traces could:
This section applies to server-side apps operating over a circuit.
Razor components with server interactivity enabled are stateful on the server. While users interact with the component on the server, they maintain a connection to the server known as acircuit. The circuit holds active component instances, plus many other aspects of state, such as:
If a user opens the app in multiple browser tabs, the user creates multiple independent circuits.
Blazor treats most unhandled exceptions as fatal to the circuit where they occur. If a circuit is terminated due to an unhandled exception, the user can only continue to interact with the app by reloading the page to create a new circuit. Circuits outside of the one that's terminated, which are circuits for other users or other browser tabs, aren't affected. This scenario is similar to a desktop app that crashes. The crashed app must be restarted, but other apps aren't affected.
The framework terminates a circuit when an unhandled exception occurs for the following reasons:
For approaches to handling exceptions globally, see the following sections:
Error boundaries provide a convenient approach for handling exceptions. TheErrorBoundary component:
To define an error boundary, use theErrorBoundary component to wrap one or more other components. The error boundary manages unhandled exceptions thrown by the components that it wraps.
<ErrorBoundary> ...</ErrorBoundary>To implement an error boundary in a global fashion, add the boundary around the body content of the app's main layout.
InMainLayout.razor:
<article> <ErrorBoundary> @Body </ErrorBoundary></article>In Blazor Web Apps with the error boundary only applied to a staticMainLayout component, the boundary is only active during static server-side rendering (static SSR). The boundary doesn't activate just because a component further down the component hierarchy is interactive.
An interactive render mode can't be applied to theMainLayout component because the component'sBody parameter is aRenderFragment delegate, which is arbitrary code and can't be serialized. To enable interactivity broadly for theMainLayout component and the rest of the components further down the component hierarchy, the app must adopt a global interactive render mode by applying the interactive render mode to theHeadOutlet andRoutes component instances in the app's root component, which is typically theApp component. The following example adopts the Interactive Server (InteractiveServer) render mode globally.
InComponents/App.razor:
<HeadOutlet @rendermode="InteractiveServer" />...<Routes @rendermode="InteractiveServer" />If you prefer not to enable global interactivity, place the error boundary farther down the component hierarchy. The important concepts to keep in mind are that wherever the error boundary is placed:
Note
The preceding considerations aren't relevant for standalone Blazor WebAssembly apps because the client-side rendering (CSR) of a Blazor WebAssembly app is completely interactive.
Consider the following example, where an exception thrown by an embedded counter component is caught by an error boundary in theHome component, which adopts an interactive render mode.
EmbeddedCounter.razor:
<h1>Embedded Counter</h1><p role="status">Current count: @currentCount</p><button @onclick="IncrementCount">Click me</button>@code { private int currentCount = 0; private void IncrementCount() { currentCount++; if (currentCount > 5) { throw new InvalidOperationException("Current count is too big!"); } }}Home.razor:
@page "/"@rendermode InteractiveServer<PageTitle>Home</PageTitle><h1>Home</h1><ErrorBoundary> <EmbeddedCounter /></ErrorBoundary>Consider the following example, where an exception thrown by an embedded counter component is caught by an error boundary in theHome component.
EmbeddedCounter.razor:
<h1>Embedded Counter</h1><p role="status">Current count: @currentCount</p><button @onclick="IncrementCount">Click me</button>@code { private int currentCount = 0; private void IncrementCount() { currentCount++; if (currentCount > 5) { throw new InvalidOperationException("Current count is too big!"); } }}Home.razor:
@page "/"<PageTitle>Home</PageTitle><h1>Home</h1><ErrorBoundary> <EmbeddedCounter /></ErrorBoundary>If the unhandled exception is thrown for acurrentCount over five:
System.InvalidOperationException: Current count is too big!).TheErrorBoundary component renders an empty<div> element using theblazor-error-boundary CSS class for its error content. The colors, text, and icon for the default UI are defined in the app's stylesheet in thewwwroot folder, so you're free to customize the error UI.

To change the default error content:
The following example wraps theEmbeddedCounter component and supplies custom error content:
<ErrorBoundary> <ChildContent> <EmbeddedCounter /> </ChildContent> <ErrorContent> <p>😈 A rotten gremlin got us. Sorry!</p> </ErrorContent></ErrorBoundary>For the preceding example, the app's stylesheet presumably includes anerrorUI CSS class to style the content. The error content is rendered from theErrorContent property without a block-level element. A block-level element, such as a division (<div>) or a paragraph (<p>) element, can wrap the error content markup, but it isn't required.
Optionally, use the context (@context) of theErrorContent to obtain error data:
<ErrorContent> @context.HelpLink</ErrorContent>TheErrorContent can also name the context. In the following example, the context is namedexception:
<ErrorContent Context="exception"> @exception.HelpLink</ErrorContent>Warning
Always avoid exposing error information to clients on the Internet, which is a security risk.
If the error boundary is defined in the app's layout, the error UI is seen regardless of which page the user navigates to after the error occurs. We recommend narrowly scoping error boundaries in most scenarios. If you broadly scope an error boundary, you can reset it to a non-error state on subsequent page navigation events by calling the error boundary'sRecover method.
InMainLayout.razor:
@ref attribute directive.OnParameterSet lifecycle method, you can trigger a recovery on the error boundary withRecover to clear the error when the user navigates to a different component....<ErrorBoundary @ref="errorBoundary"> @Body</ErrorBoundary>...@code { private ErrorBoundary? errorBoundary; protected override void OnParametersSet() { errorBoundary?.Recover(); }}To avoid the infinite loop where recovering merely rerenders a component that throws the error again, don't callRecover from rendering logic. Only callRecover when:
The following example permits the user to recover from the exception with a button:
<ErrorBoundary @ref="errorBoundary"> <ChildContent> <EmbeddedCounter /> </ChildContent> <ErrorContent> <div role="alert"> <p>😈 A rotten gremlin got us. Sorry!</p> <p>@context.HelpLink</p> <button @onclick="_ => errorBoundary?.Recover()"> Clear </button> </div> </ErrorContent></ErrorBoundary>@code { private ErrorBoundary? errorBoundary;}You can also subclassErrorBoundary for custom processing by overridingOnErrorAsync. The following example merely logs the error, but you can implement any error handling code you wish. You can remove the line that returns aCompletedTask if your code awaits an asynchronous task.
CustomErrorBoundary.razor:
@inherits ErrorBoundary@inject ILogger<CustomErrorBoundary> Logger@if (CurrentException is null){ @ChildContent}else if (ErrorContent is not null){ @ErrorContent(CurrentException)}@code { protected override Task OnErrorAsync(Exception ex) { Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!"); return Task.CompletedTask; }}The preceding example can also be implemented as a class.
CustomErrorBoundary.cs:
using Microsoft.AspNetCore.Components;using Microsoft.AspNetCore.Components.Web;namespace BlazorSample;public class CustomErrorBoundary : ErrorBoundary{ [Inject] ILogger<CustomErrorBoundary> Logger { get; set; } = default!; protected override Task OnErrorAsync(Exception ex) { Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!"); return Task.CompletedTask; }}Either of the preceding implementations used in a component:
<CustomErrorBoundary> ...</CustomErrorBoundary>The approach described in this section applies to Blazor Server, Blazor WebAssembly, and Blazor Web Apps that adopt a global interactive render mode (InteractiveServer,InteractiveWebAssembly, orInteractiveAuto). The approach doesn't work with Blazor Web Apps that adopt per-page/component render modes or static server-side rendering (static SSR) because the approach relies on aCascadingValue/CascadingParameter, which don't work across render mode boundaries or with components that adopt static SSR.
An alternative to usingError boundaries (ErrorBoundary) is to pass a custom error component as aCascadingValue to child components. An advantage of using a component over using aninjected service or a custom logger implementation is that a cascaded component can render content and apply CSS styles when an error occurs.
The followingProcessError component example merely logs errors, but methods of the component can process errors in any way required by the app, including through the use of multiple error processing methods.
ProcessError.razor:
@inject ILogger<ProcessError> Logger<CascadingValue Value="this" IsFixed="true"> @ChildContent</CascadingValue>@code { [Parameter] public RenderFragment? ChildContent { get; set; } public void LogError(Exception ex) { Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", ex.GetType(), ex.Message); // Call StateHasChanged if LogError directly participates in // rendering. If LogError only logs or records the error, // there's no need to call StateHasChanged. //StateHasChanged(); }}Note
For more information onRenderFragment, seeASP.NET Core Razor components.
CascadingValue<TValue>.IsFixed is used to indicate that a cascading parameter doesn't change after initialization.
When using this approach in a Blazor Web App, open theRoutes component and wrap theRouter component (<Router>...</Router>) with theProcessError component. This permits theProcessError component to cascade down to any component of the app where theProcessError component is received as aCascadingParameter.
InRoutes.razor:
<ProcessError> <Router ...> ... </Router></ProcessError>When using this approach in a Blazor Server or Blazor WebAssembly app, open theApp component, wrap theRouter component (<Router>...</Router>) with theProcessError component. This permits theProcessError component to cascade down to any component of the app where theProcessError component is received as aCascadingParameter.
InApp.razor:
<ProcessError> <Router ...> ... </Router></ProcessError>To process errors in a component:
Designate theProcessError component as aCascadingParameter in the@code block. In an exampleCounter component in an app based on a Blazor project template, add the followingProcessError property:
[CascadingParameter]private ProcessError? ProcessError { get; set; }Call an error processing method in anycatch block with an appropriate exception type. The exampleProcessError component only offers a singleLogError method, but the error processing component can provide any number of error processing methods to address alternative error processing requirements throughout the app. The followingCounter component@code block example includes theProcessError cascading parameter and traps an exception for logging when the count is greater than five:
@code { private int currentCount = 0; [CascadingParameter] private ProcessError? ProcessError { get; set; } private void IncrementCount() { try { currentCount++; if (currentCount > 5) { throw new InvalidOperationException("Current count is over five!"); } } catch (Exception ex) { ProcessError?.LogError(ex); } }}The logged error:
fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!
If theLogError method directly participates in rendering, such as showing a custom error message bar or changing the CSS styles of the rendered elements, callStateHasChanged at the end of theLogError method to rerender the UI.
Because the approaches in this section handle errors with atry-catch statement, an app's SignalR connection between the client and server isn't broken when an error occurs and the circuit remains alive. Other unhandled exceptions remain fatal to a circuit. For more information, see the section onhow a circuit reacts to unhandled exceptions.
An app can use an error processing component as a cascading value to process errors in a centralized way.
The followingProcessError component passes itself as aCascadingValue to child components. The following example merely logs the error, but methods of the component can process errors in any way required by the app, including through the use of multiple error processing methods. An advantage of using a component over using aninjected service or a custom logger implementation is that a cascaded component can render content and apply CSS styles when an error occurs.
ProcessError.razor:
@using Microsoft.Extensions.Logging@inject ILogger<ProcessError> Logger<CascadingValue Value="this" IsFixed="true"> @ChildContent</CascadingValue>@code { [Parameter] public RenderFragment ChildContent { get; set; } public void LogError(Exception ex) { Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", ex.GetType(), ex.Message); }}Note
For more information onRenderFragment, seeASP.NET Core Razor components.
CascadingValue<TValue>.IsFixed is used to indicate that a cascading parameter doesn't change after initialization.
In theApp component, wrap theRouter component with theProcessError component. This permits theProcessError component to cascade down to any component of the app where theProcessError component is received as aCascadingParameter.
App.razor:
<ProcessError> <Router ...> ... </Router></ProcessError>To process errors in a component:
Designate theProcessError component as aCascadingParameter in the@code block:
[CascadingParameter]private ProcessError ProcessError { get; set; }Call an error processing method in anycatch block with an appropriate exception type. The exampleProcessError component only offers a singleLogError method, but the error processing component can provide any number of error processing methods to address alternative error processing requirements throughout the app.
try{ ...}catch (Exception ex){ ProcessError.LogError(ex);}Using the preceding exampleProcessError component andLogError method, the browser's developer tools console indicates the trapped, logged error:
fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.
If theLogError method directly participates in rendering, such as showing a custom error message bar or changing the CSS styles of the rendered elements, callStateHasChanged at the end of theLogError method to rerender the UI.
Because the approaches in this section handle errors with atry-catch statement, a Blazor app's SignalR connection between the client and server isn't broken when an error occurs and the circuit remains alive. Any unhandled exception is fatal to a circuit. For more information, see the section onhow a circuit reacts to unhandled exceptions.
If an unhandled exception occurs, the exception is logged toILogger instances configured in the service container. Blazor apps log console output with the Console Logging Provider. Consider logging to a location on the server (or backend web API for client-side apps) with a provider that manages log size and log rotation. Alternatively, the app can use an Application Performance Management (APM) service, such asAzure Application Insights (Azure Monitor).
Note
NativeApplication Insights features to support client-side apps and native Blazor framework support forGoogle Analytics might become available in future releases of these technologies. For more information, seeSupport App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) andWeb analytics and diagnostics (includes links to community implementations) (dotnet/aspnetcore #5461). In the meantime, a client-side app can use theApplication Insights JavaScript SDK withJS interop to log errors directly to Application Insights from a client-side app.
During development in a Blazor app operating over a circuit, the app usually sends the full details of exceptions to the browser's console to aid in debugging. In production, detailed errors aren't sent to clients, but an exception's full details are logged on the server.
You must decide which incidents to log and the level of severity of logged incidents. Hostile users might be able to trigger errors deliberately. For example, don't log an incident from an error where an unknownProductId is supplied in the URL of a component that displays product details. Not all errors should be treated as incidents for logging.
For more information, see the following articles:
‡Applies to server-side Blazor apps and other server-side ASP.NET Core apps that are web API backend apps for Blazor. Client-side apps can trap and send error information on the client to a web API, which logs the error information to a persistent logging provider.
If an unhandled exception occurs, the exception is logged toILogger instances configured in the service container. Blazor apps log console output with the Console Logging Provider. Consider logging to a more permanent location on the server by sending error information to a backend web API that uses a logging provider with log size management and log rotation. Alternatively, the backend web API app can use an Application Performance Management (APM) service, such asAzure Application Insights (Azure Monitor)†, to record error information that it receives from clients.
You must decide which incidents to log and the level of severity of logged incidents. Hostile users might be able to trigger errors deliberately. For example, don't log an incident from an error where an unknownProductId is supplied in the URL of a component that displays product details. Not all errors should be treated as incidents for logging.
For more information, see the following articles:
†NativeApplication Insights features to support client-side apps and native Blazor framework support forGoogle Analytics might become available in future releases of these technologies. For more information, seeSupport App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) andWeb analytics and diagnostics (includes links to community implementations) (dotnet/aspnetcore #5461). In the meantime, a client-side app can use theApplication Insights JavaScript SDK withJS interop to log errors directly to Application Insights from a client-side app.
‡Applies to server-side ASP.NET Core apps that are web API backend apps for Blazor apps. Client-side apps trap and send error information to a web API, which logs the error information to a persistent logging provider.
Framework and app code may trigger unhandled exceptions in any of the following locations, which are described further in the following sections of this article:
When Blazor creates an instance of a component:
@inject directive or the[Inject] attribute are invoked.An error in an executed constructor or a setter for any[Inject] property results in an unhandled exception and stops the framework from instantiating the component. If the app is operating over a circuit, the circuit fails. If constructor logic may throw exceptions, the app should trap the exceptions using atry-catch statement with error handling and logging.
During the lifetime of a component, Blazor invokeslifecycle methods. If any lifecycle method throws an exception, synchronously or asynchronously, the exception is fatal to a circuit. For components to deal with errors in lifecycle methods, add error handling logic.
In the following example whereOnParametersSetAsync calls a method to obtain a product:
ProductRepository.GetProductByIdAsync method is handled by atry-catch statement.catch block is executed:loadFailed is set totrue, which is used to display an error message to the user.@page "/product-details/{ProductId:int?}"@inject ILogger<ProductDetails> Logger@inject IProductRepository Product<PageTitle>Product Details</PageTitle><h1>Product Details Example</h1>@if (details != null){ <h2>@details.ProductName</h2> <p> @details.Description <a href="@details.Url">Company Link</a> </p> }else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail? details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await Product.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string? ProductName { get; set; } public string? Description { get; set; } public string? Url { get; set; } } /* * Register the service in Program.cs: * using static BlazorSample.Components.Pages.ProductDetails; * builder.Services.AddScoped<IProductRepository, ProductRepository>(); */ public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); } public class ProductRepository : IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id) { return Task.FromResult( new ProductDetail() { ProductName = "Flowbee ", Description = "The Revolutionary Haircutting System You've Come to Love!", Url = "https://flowbee.com/" }); } }}@page "/product-details/{ProductId:int?}"@inject ILogger<ProductDetails> Logger@inject IProductRepository Product<PageTitle>Product Details</PageTitle><h1>Product Details Example</h1>@if (details != null){ <h2>@details.ProductName</h2> <p> @details.Description <a href="@details.Url">Company Link</a> </p> }else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail? details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await Product.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string? ProductName { get; set; } public string? Description { get; set; } public string? Url { get; set; } } /* * Register the service in Program.cs: * using static BlazorSample.Components.Pages.ProductDetails; * builder.Services.AddScoped<IProductRepository, ProductRepository>(); */ public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); } public class ProductRepository : IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id) { return Task.FromResult( new ProductDetail() { ProductName = "Flowbee ", Description = "The Revolutionary Haircutting System You've Come to Love!", Url = "https://flowbee.com/" }); } }}@page "/product-details/{ProductId:int}"@using Microsoft.Extensions.Logging@inject ILogger<ProductDetails> Logger@inject IProductRepository ProductRepository@if (details != null){ <h1>@details.ProductName</h1> <p>@details.Description</p>}else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail? details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await ProductRepository.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string? ProductName { get; set; } public string? Description { get; set; } } public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); }}@page "/product-details/{ProductId:int}"@using Microsoft.Extensions.Logging@inject ILogger<ProductDetails> Logger@inject IProductRepository ProductRepository@if (details != null){ <h1>@details.ProductName</h1> <p>@details.Description</p>}else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail? details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await ProductRepository.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string? ProductName { get; set; } public string? Description { get; set; } } public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); }}@page "/product-details/{ProductId:int}"@using Microsoft.Extensions.Logging@inject ILogger<ProductDetails> Logger@inject IProductRepository ProductRepository@if (details != null){ <h1>@details.ProductName</h1> <p>@details.Description</p>}else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await ProductRepository.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string ProductName { get; set; } public string Description { get; set; } } public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); }}@page "/product-details/{ProductId:int}"@using Microsoft.Extensions.Logging@inject ILogger<ProductDetails> Logger@inject IProductRepository ProductRepository@if (details != null){ <h1>@details.ProductName</h1> <p>@details.Description</p>}else if (loadFailed){ <h1>Sorry, we could not load this product due to an error.</h1>}else{ <h1>Loading...</h1>}@code { private ProductDetail details; private bool loadFailed; [Parameter] public int ProductId { get; set; } protected override async Task OnParametersSetAsync() { try { loadFailed = false; // Reset details to null to display the loading indicator details = null; details = await ProductRepository.GetProductByIdAsync(ProductId); } catch (Exception ex) { loadFailed = true; Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId); } } public class ProductDetail { public string ProductName { get; set; } public string Description { get; set; } } public interface IProductRepository { public Task<ProductDetail> GetProductByIdAsync(int id); }}The declarative markup in a Razor component file (.razor) is compiled into a C# method calledBuildRenderTree. When a component renders,BuildRenderTree executes and builds up a data structure describing the elements, text, and child components of the rendered component.
Rendering logic can throw an exception. An example of this scenario occurs when@someObject.PropertyName is evaluated but@someObject isnull. For Blazor apps operating over a circuit, an unhandled exception thrown by rendering logic is fatal to the app's circuit.
To prevent aNullReferenceException in rendering logic, check for anull object before accessing its members. In the following example,person.Address properties aren't accessed ifperson.Address isnull:
@if (person.Address != null){ <div>@person.Address.Line1</div> <div>@person.Address.Line2</div> <div>@person.Address.City</div> <div>@person.Address.Country</div>}The preceding code assumes thatperson isn'tnull. Often, the structure of the code guarantees that an object exists at the time the component is rendered. In those cases, it isn't necessary to check fornull in rendering logic. In the prior example,person might be guaranteed to exist becauseperson is created when the component is instantiated, as the following example shows:
@code { private Person person = new(); ...}Client-side code triggers invocations of C# code when event handlers are created using:
@onclick@onchange@on... attributes@bindEvent handler code might throw an unhandled exception in these scenarios.
If the app calls code that could fail for external reasons, trap exceptions using atry-catch statement with error handling and logging.
If an event handler throws an unhandled exception (for example, a database query fails) that isn't trapped and handled by developer code:
A component may be removed from the UI, for example, because the user has navigated to another page. When a component that implementsSystem.IDisposable is removed from the UI, the framework calls the component'sDispose method.
If the component'sDispose method throws an unhandled exception in a Blazor app operating over a circuit, the exception is fatal to the app's circuit.
If disposal logic may throw exceptions, the app should trap the exceptions using atry-catch statement with error handling and logging.
For more information on component disposal, seeASP.NET Core Razor component disposal.
IJSRuntime is registered by the Blazor framework.IJSRuntime.InvokeAsync allows .NET code to make asynchronous calls to the JavaScript (JS) runtime in the user's browser.
The following conditions apply to error handling withInvokeAsync:
Promise that completed asrejected. Developer code must catch the exception. If using theawait operator, consider wrapping the method call in atry-catch statement with error handling and logging. Otherwise in a Blazor app operating over a circuit, the failing code results in an unhandled exception that's fatal to the app's circuit.Similarly, JS code may initiate calls to .NET methods indicated by the[JSInvokable] attribute. If these .NET methods throw an unhandled exception:
Promise is rejected.You have the option of using error handling code on either the .NET side or the JS side of the method call.
For more information, see the following articles:
Razor components are prerendered by default so that their rendered HTML markup is returned as part of the user's initial HTTP request.
In a Blazor app operating over a circuit, prerendering works by:
disconnected until the user's browser establishes a SignalR connection back to the same server. When the connection is established, interactivity on the circuit is resumed and the components' HTML markup is updated.For prerendered client-side components, prerendering works by:
If a component throws an unhandled exception during prerendering, for example, during a lifecycle method or in rendering logic:
Under normal circumstances when prerendering fails, continuing to build and render the component doesn't make sense because a working component can't be rendered.
To tolerate errors that may occur during prerendering, error handling logic must be placed inside a component that may throw exceptions. Usetry-catch statements with error handling and logging. Instead of wrapping theComponentTagHelper in atry-catch statement, place error handling logic in the component rendered by theComponentTagHelper.
Components can be nested recursively. This is useful for representing recursive data structures. For example, aTreeNode component can render moreTreeNode components for each of the node's children.
When rendering recursively, avoid coding patterns that result in infinite recursion:
Infinite loops during rendering:
In these scenarios, the Blazor fails and usually attempts to:
To avoid infinite recursion patterns, ensure that recursive rendering code contains suitable stopping conditions.
Most Razor components are implemented as Razor component files (.razor) and are compiled by the framework to produce logic that operates on aRenderTreeBuilder to render their output. However, a developer may manually implementRenderTreeBuilder logic using procedural C# code. For more information, seeASP.NET Core Blazor advanced scenarios (render tree construction).
Warning
Use of manual render tree builder logic is considered an advanced and unsafe scenario, not recommended for general component development.
IfRenderTreeBuilder code is written, the developer must guarantee the correctness of the code. For example, the developer must ensure that:
Incorrect manual render tree builder logic can cause arbitrary undefined behavior, including crashes, the app or server to stop responding, and security vulnerabilities.
Consider manual render tree builder logic on the same level of complexity and with the same level ofdanger as writing assembly code orMicrosoft Intermediate Language (MSIL) instructions by hand.
†Applies to backend ASP.NET Core web API apps that client-side Blazor apps use for logging.
Was this page helpful?
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Was this page helpful?
Want to try using Ask Learn to clarify or guide you through this topic?