diff --git a/src/Microsoft.AspNetCore.Components/Routing/Router.cs b/src/Microsoft.AspNetCore.Components/Routing/Router.cs index 8ccbfc119..c318f202c 100644 --- a/src/Microsoft.AspNetCore.Components/Routing/Router.cs +++ b/src/Microsoft.AspNetCore.Components/Routing/Router.cs @@ -31,6 +31,11 @@ public class Router : IComponent, IDisposable /// [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)) @@ -103,4 +114,4 @@ private void OnLocationChanged(object sender, string newAbsoluteUri) } } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs b/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs index f7d0f69e6..4414414de 100644 --- a/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs +++ b/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/test/testapps/BasicTestApp/RouterTest/Error404.cshtml b/test/testapps/BasicTestApp/RouterTest/Error404.cshtml new file mode 100644 index 000000000..6f0efadec --- /dev/null +++ b/test/testapps/BasicTestApp/RouterTest/Error404.cshtml @@ -0,0 +1,2 @@ +@page "/Error404" +
Oops, that component wasn't found!
\ No newline at end of file diff --git a/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml b/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml index 59912840c..807a3ce00 100644 --- a/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml +++ b/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml @@ -1 +1 @@ - +