diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs b/src/Components/src/Microsoft.AspNetCore.Components/Routing/Router.cs index 8ccbfc119bc003cff57e5d337b09777bd8e61498..a38d17db6191df24a316594084ea97055bd9cea2 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 @@ namespace Microsoft.AspNetCore.Components.Routing /// assemblies, for components matching the URI. /// </summary> [Parameter] private Assembly AppAssembly { get; set; } + + /// <summary> + /// Gets or sets the type of the component that should be used as a fallback when no match is found for the requested route. + /// </summary> + [Parameter] private Type FallbackComponent { get; set; } private RouteTable Routes { get; set; } @@ -80,9 +84,17 @@ namespace Microsoft.AspNetCore.Components.Routing 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 82991d47cc4186e36339f33cedcc0cc7ca21b515..f87ed6440ac932b19714fad495167be2b2658b4c 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 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests 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<TestRouter>(); + 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 0000000000000000000000000000000000000000..812936c20b02abc45907dd7ae74b136888fffe53 --- /dev/null +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/Error404.cshtml @@ -0,0 +1 @@ +<div id="test-info">Oops, that component wasn't found!</div> diff --git a/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml b/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml index 59912840cb92b9dc920f20e671306bc219553a9f..fb0e962f09c9d39c79b29c212bd00d88a84149f5 100644 --- a/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml +++ b/src/Components/test/testapps/BasicTestApp/RouterTest/TestRouter.cshtml @@ -1 +1 @@ -<Router AppAssembly=typeof(BasicTestApp.Program).Assembly /> +<Router AppAssembly=typeof(BasicTestApp.Program).Assembly FallbackComponent="typeof(Error404)" /> \ No newline at end of file