Skip to content

Commit 11956ec

Browse files
committed
Add NotFoundPage.
1 parent 18b3b4a commit 11956ec

File tree

5 files changed

+89
-9
lines changed

5 files changed

+89
-9
lines changed

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#nullable enable
22
Microsoft.AspNetCore.Components.NavigationManager.NotFoundEvent -> System.EventHandler<System.EventArgs!>!
33
virtual Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void
4+
Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type!
5+
Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void
46
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger, System.IServiceProvider! serviceProvider) -> void
57
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.SetPlatformRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> void
68
Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions

src/Components/Components/src/Routing/Router.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
#nullable disable warnings
55

6+
using System.Diagnostics.CodeAnalysis;
67
using System.Reflection;
78
using System.Reflection.Metadata;
89
using System.Runtime.ExceptionServices;
910
using Microsoft.AspNetCore.Components.HotReload;
1011
using Microsoft.AspNetCore.Components.Rendering;
12+
using Microsoft.AspNetCore.Internal;
1113
using Microsoft.Extensions.Logging;
1214
using Microsoft.Extensions.DependencyInjection;
1315

@@ -70,6 +72,13 @@ static readonly IReadOnlyDictionary<string, object> _emptyParametersDictionary
7072
[Parameter]
7173
public RenderFragment NotFound { get; set; }
7274

75+
/// <summary>
76+
/// Gets or sets the page content to display when no match is found for the requested route.
77+
/// </summary>
78+
[Parameter]
79+
[DynamicallyAccessedMembers(LinkerFlags.Component)]
80+
public Type NotFoundPage { get; set; } = default!;
81+
7382
/// <summary>
7483
/// Gets or sets the content to display when a match is found for the requested route.
7584
/// </summary>
@@ -132,6 +141,22 @@ public async Task SetParametersAsync(ParameterView parameters)
132141
throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(Found)}.");
133142
}
134143

144+
if (NotFoundPage != null)
145+
{
146+
if (!typeof(IComponent).IsAssignableFrom(NotFoundPage))
147+
{
148+
throw new InvalidOperationException($"The type {NotFoundPage.FullName} " +
149+
$"does not implement {typeof(IComponent).FullName}.");
150+
}
151+
152+
var routeAttributes = NotFoundPage.GetCustomAttributes(typeof(RouteAttribute), inherit: true);
153+
if (routeAttributes.Length == 0)
154+
{
155+
throw new InvalidOperationException($"The type {NotFoundPage.FullName} " +
156+
$"does not have a {typeof(RouteAttribute).FullName} applied to it.");
157+
}
158+
}
159+
135160
if (!_onNavigateCalled)
136161
{
137162
_onNavigateCalled = true;
@@ -251,7 +276,22 @@ internal virtual void Refresh(bool isNavigationIntercepted)
251276
// We did not find a Component that matches the route.
252277
// Only show the NotFound content if the application developer programatically got us here i.e we did not
253278
// intercept the navigation. In all other cases, force a browser navigation since this could be non-Blazor content.
254-
_renderHandle.Render(NotFound ?? DefaultNotFoundContent);
279+
_renderHandle.Render(builder =>
280+
{
281+
if (NotFoundPage != null)
282+
{
283+
builder.OpenComponent(0, NotFoundPage);
284+
builder.CloseComponent();
285+
}
286+
else if (NotFound != null)
287+
{
288+
NotFound(builder);
289+
}
290+
else
291+
{
292+
DefaultNotFoundContent(builder);
293+
}
294+
});
255295
}
256296
else
257297
{

src/Components/test/E2ETest/Tests/GlobalInteractivityTest.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ public class GlobalInteractivityTest(
2222
{
2323

2424
[Theory]
25-
[InlineData("server", true)]
26-
[InlineData("webassembly", true)]
27-
[InlineData("ssr", false)]
28-
public void CanRenderNotFoundInteractive(string renderingMode, bool isInteractive)
25+
[InlineData("server", true, false)]
26+
[InlineData("webassembly", true, false)]
27+
[InlineData("server", true, true)]
28+
[InlineData("webassembly", true, true)]
29+
[InlineData("ssr", false, true)]
30+
[InlineData("ssr", false, false)]
31+
public void CanRenderNotFoundPage(string renderingMode, bool isInteractive, bool useCustomNotFoundPage)
2932
{
30-
Navigate($"/subdir/render-not-found-{renderingMode}");
33+
string query = useCustomNotFoundPage ? "?useCustomNotFoundPage=true" : "";
34+
Navigate($"{ServerPathBase}/render-not-found-{renderingMode}{query}");
3135

3236
if (isInteractive)
3337
{
@@ -36,8 +40,16 @@ public void CanRenderNotFoundInteractive(string renderingMode, bool isInteractiv
3640
Browser.Exists(By.Id(buttonId)).Click();
3741
}
3842

39-
var bodyText = Browser.FindElement(By.TagName("body")).Text;
40-
Assert.Contains("There's nothing here", bodyText);
43+
if (useCustomNotFoundPage)
44+
{
45+
var infoText = Browser.FindElement(By.Id("test-info")).Text;
46+
Assert.Contains("Welcome On Custom Not Found Page", infoText);
47+
}
48+
else
49+
{
50+
var bodyText = Browser.FindElement(By.TagName("body")).Text;
51+
Assert.Contains("There's nothing here", bodyText);
52+
}
4153
}
4254

4355
[Fact]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@page "/render-custom-not-found-page"
2+
3+
<h3 id="test-info">Welcome On Custom Not Found Page</h3>
4+
<p>Sorry, the page you are looking for does not exist.</p>

src/Components/test/testassets/Components.WasmMinimal/Routes.razor

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
@using Microsoft.AspNetCore.Components.Routing
2+
@using Components.WasmMinimal.Pages
3+
@inject NavigationManager NavigationManager
24

3-
<Router AppAssembly="@typeof(Program).Assembly">
5+
@code {
6+
[Parameter]
7+
[SupplyParameterFromQuery(Name = "useCustomNotFoundPage")]
8+
public string? UseCustomNotFoundPage { get; set; }
9+
10+
private Type? NotFoundPageType { get; set; }
11+
12+
protected override void OnParametersSet()
13+
{
14+
if (UseCustomNotFoundPage == "true")
15+
{
16+
NotFoundPageType = typeof(CustomNotFoundPage);
17+
}
18+
else
19+
{
20+
NotFoundPageType = null;
21+
}
22+
}
23+
}
24+
25+
<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="NotFoundPageType">
426
<Found Context="routeData">
527
<RouteView RouteData="@routeData" />
628
<FocusOnNavigate RouteData="@routeData" Selector="h1" />

0 commit comments

Comments
 (0)