diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md index 99a709a181c4..9ec538a7ee8d 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. If Not Found content is configured through `Router.NotFoundPage` or `NotFoundEventArgs.Path`, the router renders Not Found content. -* **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**: 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. @@ -328,10 +330,10 @@ 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 in a supported context, making it possible to handle missing routes with a consistent user experience. `Pages/NotFound.razor`: @@ -375,6 +377,58 @@ 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 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. + + 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() +{ + using var context = DbFactory.CreateDbContext(); + context.Attach(Movie!).State = EntityState.Modified; + + try + { + await context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!MovieExists(Movie!.Id)) + { + Navigation.NotFound(); + + return; + } + else + { + throw; + } + } + + 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-3.md b/aspnetcore/blazor/tutorials/movie-database-app/part-3.md index 6c6f98c3a6b5..cb59a3c6d9f9 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 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 :::moniker range="< aspnetcore-10.0" diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md index 56ddb8d31865..2edc8a8f213e 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-4.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-4.md @@ -1,10 +1,11 @@ --- 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' 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 +284,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 +314,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: