diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs b/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs index 8ccbfc119bc0..41d2a3579103 100644 --- a/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs +++ b/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs @@ -30,6 +30,11 @@ public class Router : IComponent, IDisposable /// assemblies, for components matching the URI. /// [Parameter] private Assembly AppAssembly { get; set; } + + /// + /// Gets or sets the route that should be used when no components are found with the requested route. + /// + [Parameter] private string FallbackRoute { get; set; } private RouteTable Routes { get; set; } @@ -74,15 +79,21 @@ protected virtual void Render(RenderTreeBuilder builder, Type handler, IDictiona builder.CloseComponent(); } - private void Refresh() + private void Refresh(bool useFallback = false) { - var locationPath = UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute); + var locationPath = useFallback ? FallbackRoute : UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute); locationPath = StringUntilAny(locationPath, _queryOrHashStartChar); var context = new RouteContext(locationPath); Routes.Route(context); if (context.Handler == null) { - throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with a route for '/{locationPath}'."); + if (useFallback || FallbackRoute == null) + { + throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with {(useFallback ? "the fallback route" : "a route for")} '/{locationPath}'."); + } + + Refresh(useFallback: true); + return; } if (!typeof(IComponent).IsAssignableFrom(context.Handler)) diff --git a/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs b/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs index f7d0f69e633c..3070bad7d118 100644 --- a/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs +++ b/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs @@ -88,6 +88,15 @@ public void CanArriveAtNonDefaultPage() AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } + [Fact] + public void CanArriveAtFallbackPageFromBadURI() + { + SetUrlViaPushState("/Oopsie_Daisies%20%This_Aint_A_Real_Page"); + + var app = MountTestComponent(); + Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text); + } + [Fact] public void CanFollowLinkToOtherPage() { diff --git a/src/Components/test/testapps/BasicTestApp/RouterTest/Error404.cshtml b/src/Components/test/testapps/BasicTestApp/RouterTest/Error404.cshtml new file mode 100644 index 000000000000..f135b639db19 --- /dev/null +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/Error404.cshtml @@ -0,0 +1,2 @@ +@page "/Error404" +
Oops, that component wasn't found!
diff --git a/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml b/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml index 59912840cb92..807a3ce00302 100644 --- a/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml @@ -1 +1 @@ - +