Skip to content

Commit 2d6d07d

Browse files
authored
Improve OnNotFound example (#35475)
1 parent 00e3827 commit 2d6d07d

File tree

1 file changed

+172
-11
lines changed

1 file changed

+172
-11
lines changed

aspnetcore/blazor/fundamentals/routing.md

Lines changed: 172 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -777,36 +777,197 @@ When a component is rendered with a global interactive render mode, calling `Not
777777
}
778778
```
779779

780-
You can use the `OnNotFound` event for notifications when `NotFound` is invoked. The following example uses a render fragment (<xref:Microsoft.AspNetCore.Components.RenderFragment>) to render the Not Found content.
780+
You can use the `OnNotFound` event for notifications when `NotFound` is invoked. The event is only fired when `NotFound` is called, not for any 404 response. For example, setting `HttpContextAccessor.HttpContext.Response.StatusCode` to `404` doesn't trigger `NotFound`/`OnNotFound`.
781781

782-
`Routes.razor`:
782+
<!-- UPDATE 10.0 - For Pre5, the following can be expanded to
783+
cover CSR with an added bit of coverage for
784+
re-execution middleware. -->
785+
786+
In the following example for components that adopt [interactive server-side rendering (interactive SSR)](xref:blazor/fundamentals/index#client-and-server-rendering-concepts), custom content is rendered depending on where `OnNotFound` is called. If the event is triggered by the following `Movie` component when a movie isn't found on component initialization, a custom message states that the requested movie isn't found. If the event is triggered by the `User` component, a different message states that the user isn't found.
787+
788+
The following `NotFoundContext` service manages the context and the message for when content isn't found by components.
789+
790+
`NotFoundContext.cs`:
791+
792+
```csharp
793+
public class NotFoundContext
794+
{
795+
public string Heading { get; private set; } = "Not Found";
796+
public string Message { get; private set; } =
797+
"Sorry, the page that you requested couldn't be found.";
798+
799+
public void UpdateContext(string heading, string message)
800+
{
801+
Heading = heading;
802+
Message = message;
803+
}
804+
}
805+
```
806+
807+
The service is registered in the server-side `Program` file:
808+
809+
```csharp
810+
builder.Services.AddScoped<NotFoundContext>();
811+
```
812+
813+
The `Routes` component (`Routes.razor`):
814+
815+
* Injects the `NotFoundContext` service.
816+
* Displays the heading (`Heading`) and message (`Message`) when `OnNotFound` is triggered by a call to `NotFound`.
783817

784818
```razor
785-
@inject NavigationManager Navigation
786-
@inject ILogger<Routes> Logger
819+
@inject NotFoundContext NotFoundContext
787820
788-
<Router AppAssembly="typeof(Program).Assembly" NotFound="renderFragment">
821+
<Router AppAssembly="typeof(Program).Assembly">
789822
<Found Context="routeData">
790823
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
791824
<FocusOnNavigate RouteData="routeData" Selector="h1" />
792825
</Found>
826+
<NotFound>
827+
<LayoutView Layout="typeof(Layout.MainLayout)">
828+
<h1>@NotFoundContext.Heading</h1>
829+
<div>
830+
<p>@NotFoundContext.Message</p>
831+
</div>
832+
</LayoutView>
833+
</NotFound>
793834
</Router>
835+
```
836+
837+
In the following example components:
838+
839+
* The `NotFoundContext` service is injected, along with the <xref:Microsoft.AspNetCore.Components.NavigationManager>.
840+
* In <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A>, `HandleNotFound` is an event handler assigned to the `OnNotFound` event. `HandleNotFound` calls `NotFoundContext.UpdateContext` to set a heading and message for Not Found content that's displayed by the `Router` component in the `Routes` component (`Routes.razor`).
841+
* The components would normally use an ID from a route parameter to obtain a movie or user from a data store, such as a database. In the following examples, no entity is returned (`null`) to simulate what happens when an entity isn't found.
842+
* When no entity is returned to <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A>, `NavigationManager.NotFound` is called, which in turn triggers the `OnNotFound` event and the `HandleNotFound` event handler. Not Found content is displayed by the router.
843+
* The `HandleNotFound` method is unhooked on component disposal in <xref:System.IDisposable.Dispose%2A?displayProperty=nameWithType>.
844+
845+
`Movie` component (`Movie.razor`):
846+
847+
```razor
848+
@page "/movie/{Id:int}"
849+
@implements IDisposable
850+
@inject NavigationManager NavigationManager
851+
@inject NotFoundContext NotFoundContext
852+
853+
<div>
854+
No matter what ID is used, no matching movie is returned
855+
from the call to GetMovie().
856+
</div>
794857
795858
@code {
796-
private RenderFragment renderFragment =
797-
@<div><h1>Not Found</h1><p>Sorry! Nothing to show.</p></div>;
859+
[Parameter]
860+
public int Id { get; set; }
861+
862+
protected override async Task OnInitializedAsync()
863+
{
864+
NavigationManager.OnNotFound += HandleNotFound;
798865
799-
protected override void OnInitialized() => Navigation.OnNotFound += OnNotFound;
866+
var movie = await GetMovie(Id);
867+
868+
if (movie == null)
869+
{
870+
NavigationManager.NotFound();
871+
}
872+
}
873+
874+
private void HandleNotFound(object? sender, NotFoundEventArgs e)
875+
{
876+
NotFoundContext.UpdateContext("Movie Not Found",
877+
"Sorry! The requested movie wasn't found.");
878+
}
879+
880+
private async Task<MovieItem[]?> GetMovie(int id)
881+
{
882+
// Simulate no movie with matching id found
883+
return await Task.FromResult<MovieItem[]?>(null);
884+
}
800885
801-
private void OnNotFound(object? sender, EventArgs args)
886+
void IDisposable.Dispose()
802887
{
803-
Logger.LogError("Something wasn't found!");
888+
NavigationManager.OnNotFound -= HandleNotFound;
804889
}
805890
806-
public void Dispose() => Navigation.OnNotFound -= OnNotFound;
891+
public class MovieItem
892+
{
893+
public int Id { get; set; }
894+
public string? Title { get; set; }
895+
}
896+
}
897+
```
898+
899+
`User` component (`User.razor`):
900+
901+
```razor
902+
@page "/user/{Id:int}"
903+
@implements IDisposable
904+
@inject NavigationManager NavigationManager
905+
@inject NotFoundContext NotFoundContext
906+
907+
<div>
908+
No matter what ID is used, no matching user is returned
909+
from the call to GetUser().
910+
</div>
911+
912+
@code {
913+
[Parameter]
914+
public int Id { get; set; }
915+
916+
protected override async Task OnInitializedAsync()
917+
{
918+
NavigationManager.OnNotFound += HandleNotFound;
919+
920+
var user = await GetUser(Id);
921+
922+
if (user == null)
923+
{
924+
NavigationManager.NotFound();
925+
}
926+
}
927+
928+
private void HandleNotFound(object? sender, NotFoundEventArgs e)
929+
{
930+
NotFoundContext.UpdateContext("User Not Found",
931+
"Sorry! The requested user wasn't found.");
932+
}
933+
934+
private async Task<UserItem[]?> GetUser(int id)
935+
{
936+
// Simulate no user with matching id found
937+
return await Task.FromResult<UserItem[]?>(null);
938+
}
939+
940+
void IDisposable.Dispose()
941+
{
942+
NavigationManager.OnNotFound -= HandleNotFound;
943+
}
944+
945+
public class UserItem
946+
{
947+
public int Id { get; set; }
948+
public string? Name { get; set; }
949+
}
807950
}
808951
```
809952

953+
To reach the preceding components in a local demonstration with a test app, create entries in the `NavMenu` component (`NavMenu.razor`) to reach the `Movie` and `User` components. The entity IDs, passed as route parameters, in the following example are mock values that have no effect because they aren't actually used by the components, which simulate not finding a movie or user.
954+
955+
In `NavMenu.razor`:
956+
957+
```razor
958+
<div class="nav-item px-3">
959+
<NavLink class="nav-link" href="movie/1">
960+
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Movie
961+
</NavLink>
962+
</div>
963+
964+
<div class="nav-item px-3">
965+
<NavLink class="nav-link" href="user/2">
966+
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User
967+
</NavLink>
968+
</div>
969+
```
970+
810971
:::moniker-end
811972

812973
:::moniker range=">= aspnetcore-8.0"

0 commit comments

Comments
 (0)