diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs b/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs index 8ccbfc119bc0..a38d17db6191 100644 --- a/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs +++ b/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Layouts; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Services; @@ -30,6 +29,11 @@ public class Router : IComponent, IDisposable /// assemblies, for components matching the URI. /// [Parameter] private Assembly AppAssembly { get; set; } + + /// + /// Gets or sets the type of the component that should be used as a fallback when no match is found for the requested route. + /// + [Parameter] private Type FallbackComponent { get; set; } private RouteTable Routes { get; set; } @@ -80,9 +84,17 @@ private void Refresh() 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 (FallbackComponent != null) + { + context.Handler = FallbackComponent; + } + else + { + throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with a route for '/{locationPath}', and no fallback is defined."); + } } 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..812936c20b02 --- /dev/null +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/Error404.cshtml @@ -0,0 +1 @@ +
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..fb0e962f09c9 100644 --- a/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml @@ -1 +1 @@ - + \ No newline at end of file