From f9cf4e6f4909390c1c3918113082fc35be099488 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:18:00 -0400 Subject: [PATCH 1/5] Add and update concurrency Not Found coverage --- aspnetcore/blazor/fundamentals/navigation.md | 59 +++++++++++++++++-- .../tutorials/movie-database-app/part-4.md | 37 +++++++++++- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index 99a709a181c4..75990e67daa1 100644 --- a/aspnetcore/blazor/fundamentals/navigation.md +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -5,7 +5,7 @@ author: guardrex description: Learn about navigation in Blazor, including how to use the Navigation Manager and NavLink component for navigation. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett -ms.date: 06/24/2026 +ms.date: 09/15/2026 uid: blazor/fundamentals/navigation --- # ASP.NET Core Blazor navigation @@ -289,11 +289,13 @@ You can use the `` MSBuild property set t ## Not Found responses - handles scenarios where a requested resource isn't found during static server-side rendering (static SSR) or global interactive rendering: + handles scenarios where a requested resource isn't found: -* **Static SSR**: Calling sets the HTTP status code to 404. +* **Static server-side rendering (static SSR)**: Calling sets the HTTP status code to 404. -* **Interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content. +* **Global interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content. + +* **Per-page/component interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content ***only during prerendering on the server***. * **Streaming rendering**: If [enhanced navigation](xref:blazor/fundamentals/routing?view=aspnetcore-10.0#enhanced-navigation-and-form-handling) is active, [streaming rendering](xref:blazor/components/rendering#streaming-rendering) renders Not Found content without reloading the page. When enhanced navigation is blocked, the framework redirects to Not Found content with a page refresh. @@ -328,7 +330,7 @@ When a component is rendered statically (static SSR) and [!NOTE] > The Blazor project template includes a `NotFound.razor` page. This page automatically renders whenever is called, making it possible to handle missing routes with a consistent user experience. @@ -356,7 +358,7 @@ In the following example, the preceding `NotFound` component is present in the a ``` -When a component is rendered with a global interactive render mode, calling signals the Blazor router to render the `NotFound` component: +When a component is rendered with a global interactive render mode, calling signals the Blazor router to render the `NotFound` component (): ```razor @page "/render-not-found-interactive" @@ -375,6 +377,51 @@ When a component is rendered with a global interactive render mode, calling signals the Blazor router to render the `NotFound` component ***only during prerendering*** (): + +```csharp +protected override void OnInitialized() +{ + // Where your initialization code must lead to a Not Found response: + if (!RendererInfo.IsInteractive) + { + Navigation.NotFound(); + } +} +``` + +The Not Found response and 404 status code aren't applied outside of prerendering, so if prerendering is disabled or the call is made after prerendering, the component should indicate to the user in the component's UI that a resource isn't found. + +In cases where is called in the middle of a method, end the control flow with a `return` statement after calling . In the following movie database update method, a concurrency exception results in checking for the presence of a movie in the database to determine if a Not Found response is appropriate. The `return` statement after calling ensures that the handler selects only the Not Found outcome, independently of a given database provider synchronously or asynchronously executing : + +```razor +private async Task UpdateMovie() +{ + using var context = DbFactory.CreateDbContext(); + context.Attach(Movie!).State = EntityState.Modified; + + try + { + await context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!MovieExists(Movie!.Id)) + { + NavigationManager.NotFound(); + + return; + } + else + { + throw; + } + } + + NavigationManager.NavigateTo("/movies"); +} +``` + Use the event for notifications when is invoked. The event is only fired when is called, not for any 404 response. For example, setting `HttpContextAccessor.HttpContext.Response.StatusCode` to `404` doesn't trigger /. Apps that implement a custom router can use . There are two ways to inform the renderer what page should be rendered when is called. diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md index 56ddb8d31865..3e2bc516e51c 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md @@ -4,7 +4,7 @@ author: guardrex description: This part of the Blazor movie database app tutorial explains the database context and directly working with the database's schema and data. Seeding the database with data is also covered. monikerRange: '>= aspnetcore-8.0' ms.author: wpickett -ms.date: 11/11/2025 +ms.date: 09/15/2026 uid: blazor/tutorials/movie-database-app/part-4 zone_pivot_groups: tooling --- @@ -283,7 +283,7 @@ If the model state has errors when the form is posted, for example if `ReleaseDa ## Concurrency exception handling -Review the `UpdateMovie` method of the `Edit` component (`Components/Pages/MoviePages/Edit.razor`): +Examine the `UpdateMovie` method of the `Edit` component (`Components/Pages/MoviePages/Edit.razor`): :::moniker range=">= aspnetcore-10.0" @@ -313,6 +313,39 @@ private async Task UpdateMovie() } ``` + + +> [!IMPORTANT] +> Due to a bug in the Blazor CRUD template, a `return` statement is missing from the `UpdateMovie` method after is called. The purpose of calling `return` is to ensure the handler (the `UpdateMovie` method) selects only the Not Found outcome, independently of a given database provider synchronously or asynchronously executing . We're in the process of updating the `Edit` component template, and this article will be updated when the scaffolder generates the correct code. +> +> After the line that calls , add a `return` statement: +> +> ```csharp +> return; +> ``` +> +> The `catch` block should look like the following example after the `return` statement is added: +> +> ```csharp +> catch (DbUpdateConcurrencyException) +> { +> if (!MovieExists(Movie!.Id)) +> { +> NavigationManager.NotFound(); +> +> return; +> } +> else +> { +> throw; +> } +> } +> ``` + Concurrency exceptions are detected when one client deletes the movie and a different client posts changes to the movie. To test how concurrency is handled by the preceding code: From eba5439b99d404a0774e7652fc447c34c8080669 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:51:00 -0400 Subject: [PATCH 2/5] Updates --- aspnetcore/blazor/fundamentals/navigation.md | 9 +++++++-- aspnetcore/blazor/tutorials/movie-database-app/part-4.md | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index 75990e67daa1..fd49d4170c2f 100644 --- a/aspnetcore/blazor/fundamentals/navigation.md +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -408,7 +408,7 @@ private async Task UpdateMovie() { if (!MovieExists(Movie!.Id)) { - NavigationManager.NotFound(); + Navigation.NotFound(); return; } @@ -418,10 +418,15 @@ private async Task UpdateMovie() } } - NavigationManager.NavigateTo("/movies"); + Navigation.NavigateTo("/movies"); } ``` +Services not shown in the preceding example: + +* `NavigationManager` is an injected (`@inject NavigationManager Navigation`). +* `DbFactory` is an injected (`@inject IDbContextFactory<{CONTEXT}> DbFactory`, where the `{CONTEXT}` placeholder is a ). + Use the event for notifications when is invoked. The event is only fired when is called, not for any 404 response. For example, setting `HttpContextAccessor.HttpContext.Response.StatusCode` to `404` doesn't trigger /. Apps that implement a custom router can use . There are two ways to inform the renderer what page should be rendered when is called. diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md index 3e2bc516e51c..2edc8a8f213e 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md @@ -1,5 +1,6 @@ --- title: Build a Blazor movie database app (Part 4 - Work with a database) +ai-usage: ai-assisted author: guardrex description: This part of the Blazor movie database app tutorial explains the database context and directly working with the database's schema and data. Seeding the database with data is also covered. monikerRange: '>= aspnetcore-8.0' From dd0743849b6b9f013fbf078ad69b33daa11e6af3 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:12:33 -0400 Subject: [PATCH 3/5] Updates --- aspnetcore/blazor/fundamentals/navigation.md | 4 +++- .../blazor/tutorials/movie-database-app/part-3.md | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index fd49d4170c2f..3d73f46b7ab2 100644 --- a/aspnetcore/blazor/fundamentals/navigation.md +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -390,11 +390,13 @@ protected override void OnInitialized() } ``` +The preceding example would assign a per-page/component render mode at the top of the Razor component file (for example, `@rendermode InteractiveServer`). + The Not Found response and 404 status code aren't applied outside of prerendering, so if prerendering is disabled or the call is made after prerendering, the component should indicate to the user in the component's UI that a resource isn't found. In cases where is called in the middle of a method, end the control flow with a `return` statement after calling . In the following movie database update method, a concurrency exception results in checking for the presence of a movie in the database to determine if a Not Found response is appropriate. The `return` statement after calling ensures that the handler selects only the Not Found outcome, independently of a given database provider synchronously or asynchronously executing : -```razor +```csharp private async Task UpdateMovie() { using var context = DbFactory.CreateDbContext(); diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-3.md b/aspnetcore/blazor/tutorials/movie-database-app/part-3.md index 6c6f98c3a6b5..d486cf2691a8 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-3.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-3.md @@ -1,10 +1,11 @@ --- title: Build a Blazor movie database app (Part 3 - Learn about Razor components) +ai-usage: ai-assisted author: guardrex description: This part of the Blazor movie database app tutorial explains the Razor components in the project that were scaffolded into the app. Improvements are made to the display of movie data. monikerRange: '>= aspnetcore-8.0' ms.author: wpickett -ms.date: 11/11/2025 +ms.date: 09/15/2026 uid: blazor/tutorials/movie-database-app/part-3 zone_pivot_groups: tooling --- @@ -843,6 +844,14 @@ When a movie isn't found, calling + +> [!NOTE] +> Later, you're instructed to make a minor modification the preceding code in the `Edit` component's `UpdateMovie` method, where a `return` statement is added after the call to . Guidance on this change and information on why the change is required is provided in the *Concurrency exception handling* section of , which is the next article in this tutorial. + :::moniker-end :::moniker range="< aspnetcore-10.0" From ef061c695272953655c9dbdcd384d7ec8f64b5b1 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:31:58 -0400 Subject: [PATCH 4/5] Apply batched suggestions from code review Co-authored-by: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Co-authored-by: Luke Latham <1622880+guardrex@users.noreply.github.com> --- aspnetcore/blazor/fundamentals/navigation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index 3d73f46b7ab2..32e92ff64053 100644 --- a/aspnetcore/blazor/fundamentals/navigation.md +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -291,11 +291,11 @@ You can use the `` MSBuild property set t handles scenarios where a requested resource isn't found: -* **Static server-side rendering (static SSR)**: Calling sets the HTTP status code to 404. +* **Static server-side rendering (static SSR)**: Calling sets the HTTP status code to 404. If Not Found content is configured through `Router.NotFoundPage` or `NotFoundEventArgs.Path`, the router renders Not Found content. * **Global interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content. -* **Per-page/component interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content ***only during prerendering on the server***. +* **Per-page/component interactive rendering**: Behaves the same as static server-side rendering (static SSR) during prerendering on the server, while interactive Not Found behavior no-ops. * **Streaming rendering**: If [enhanced navigation](xref:blazor/fundamentals/routing?view=aspnetcore-10.0#enhanced-navigation-and-form-handling) is active, [streaming rendering](xref:blazor/components/rendering#streaming-rendering) renders Not Found content without reloading the page. When enhanced navigation is blocked, the framework redirects to Not Found content with a page refresh. @@ -333,7 +333,7 @@ When a component is rendered statically (static SSR) and [!NOTE] -> The Blazor project template includes a `NotFound.razor` page. This page automatically renders whenever is called, making it possible to handle missing routes with a consistent user experience. +> The Blazor project template includes a `NotFound.razor` page and configures it as the router's `NotFoundPage`. This page renders when is called un a supported context, making it possible to handle missing routes with a consistent user experience. `Pages/NotFound.razor`: @@ -358,7 +358,7 @@ In the following example, the preceding `NotFound` component is present in the a ``` -When a component is rendered with a global interactive render mode, calling signals the Blazor router to render the `NotFound` component (): +When a component is rendered with a global interactive render mode, calling signals the Blazor router to render the `NotFound` component: ```razor @page "/render-not-found-interactive" @@ -394,7 +394,7 @@ The preceding example would assign a per-page/component render mode at the top o The Not Found response and 404 status code aren't applied outside of prerendering, so if prerendering is disabled or the call is made after prerendering, the component should indicate to the user in the component's UI that a resource isn't found. -In cases where is called in the middle of a method, end the control flow with a `return` statement after calling . In the following movie database update method, a concurrency exception results in checking for the presence of a movie in the database to determine if a Not Found response is appropriate. The `return` statement after calling ensures that the handler selects only the Not Found outcome, independently of a given database provider synchronously or asynchronously executing : + doesn't terminate the method. If should end the control flow, add a `return` statement after calling . In the following movie database update method, a concurrency exception results in checking for the presence of a movie in the database to determine if a Not Found response is appropriate. The `return` statement after calling ensures that the handler selects only the Not Found outcome, independently of a given database provider synchronously or asynchronously executing : ```csharp private async Task UpdateMovie() From bf0e1076c15de5f896323eb742416d87a3b5ae68 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Thu, 17 Sep 2026 06:56:00 -0400 Subject: [PATCH 5/5] Apply batched suggestions from code review Co-authored-by: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> --- aspnetcore/blazor/fundamentals/navigation.md | 2 +- aspnetcore/blazor/tutorials/movie-database-app/part-3.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index 32e92ff64053..9ec538a7ee8d 100644 --- a/aspnetcore/blazor/fundamentals/navigation.md +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -333,7 +333,7 @@ When a component is rendered statically (static SSR) and [!NOTE] -> The Blazor project template includes a `NotFound.razor` page and configures it as the router's `NotFoundPage`. This page renders when is called un a supported context, making it possible to handle missing routes with a consistent user experience. +> The Blazor project template includes a `NotFound.razor` page and configures it as the router's `NotFoundPage`. This page renders when is called in a supported context, making it possible to handle missing routes with a consistent user experience. `Pages/NotFound.razor`: diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-3.md b/aspnetcore/blazor/tutorials/movie-database-app/part-3.md index d486cf2691a8..cb59a3c6d9f9 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-3.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-3.md @@ -850,7 +850,7 @@ If there's a concurrency exception and the movie entity no longer exists at the --> > [!NOTE] -> Later, you're instructed to make a minor modification the preceding code in the `Edit` component's `UpdateMovie` method, where a `return` statement is added after the call to . Guidance on this change and information on why the change is required is provided in the *Concurrency exception handling* section of , which is the next article in this tutorial. +> Later, you're instructed to make a minor modification to the preceding code in the `Edit` component's `UpdateMovie` method, where a `return` statement is added after the call to . Guidance on this change and information on why the change is required is provided in the *Concurrency exception handling* section of , which is the next article in this tutorial. :::moniker-end