diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index dc07b8afddac..f5e0f10aad7d 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,7 +1,7 @@ #nullable enable *REMOVED*Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList? properties) -> void Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList? properties = null) -> void -Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type! +Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type? Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler! diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index eedff373f656..673950373e28 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -70,6 +70,7 @@ static readonly IReadOnlyDictionary _emptyParametersDictionary /// Gets or sets the content to display when no match is found for the requested route. /// [Parameter] + [Obsolete("NotFound is deprecated. Use NotFoundPage instead.")] public RenderFragment NotFound { get; set; } /// @@ -77,7 +78,7 @@ static readonly IReadOnlyDictionary _emptyParametersDictionary /// [Parameter] [DynamicallyAccessedMembers(LinkerFlags.Component)] - public Type NotFoundPage { get; set; } = default!; + public Type? NotFoundPage { get; set; } /// /// Gets or sets the content to display when a match is found for the requested route. @@ -143,6 +144,12 @@ public async Task SetParametersAsync(ParameterView parameters) if (NotFoundPage != null) { +#pragma warning disable CS0618 // Type or member is obsolete + if (NotFound != null) + { + throw new InvalidOperationException($"Setting {nameof(NotFound)} and {nameof(NotFoundPage)} properties simultaneously is not supported. Use either {nameof(NotFound)} or {nameof(NotFoundPage)}."); + } +#pragma warning restore CS0618 // Type or member is obsolete if (!typeof(IComponent).IsAssignableFrom(NotFoundPage)) { throw new InvalidOperationException($"The type {NotFoundPage.FullName} " + @@ -401,10 +408,12 @@ private void RenderNotFound() new RouteData(NotFoundPage, _emptyParametersDictionary)); builder.CloseComponent(); } +#pragma warning disable CS0618 // Type or member is obsolete else if (NotFound != null) { NotFound(builder); } +#pragma warning restore CS0618 // Type or member is obsolete else { DefaultNotFoundContent(builder); @@ -429,6 +438,7 @@ async Task IHandleAfterRender.OnAfterRenderAsync() private static partial class Log { +#pragma warning disable CS0618 // Type or member is obsolete [LoggerMessage(1, LogLevel.Debug, $"Displaying {nameof(NotFound)} because path '{{Path}}' with base URI '{{BaseUri}}' does not match any component route", EventName = "DisplayingNotFound")] internal static partial void DisplayingNotFound(ILogger logger, string path, string baseUri); @@ -440,5 +450,6 @@ private static partial class Log [LoggerMessage(4, LogLevel.Debug, $"Displaying {nameof(NotFound)} on request", EventName = "DisplayingNotFoundOnRequest")] internal static partial void DisplayingNotFound(ILogger logger); +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/Components/Components/test/Routing/RouterTest.cs b/src/Components/Components/test/Routing/RouterTest.cs index f393a2edccf0..46bbb04a030f 100644 --- a/src/Components/Components/test/Routing/RouterTest.cs +++ b/src/Components/Components/test/Routing/RouterTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#pragma warning disable CS0618 // Type or member is obsolete + using System.Reflection; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; @@ -265,6 +267,40 @@ await _renderer.Dispatcher.InvokeAsync(() => Assert.Equal("Not found", renderedFrame.TextContent); } + [Fact] + public async Task ThrowsExceptionWhenBothNotFoundAndNotFoundPageAreSet() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(NullLoggerFactory.Instance); + services.AddSingleton(_navigationManager); + services.AddSingleton(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + var renderer = new TestRenderer(serviceProvider); + renderer.ShouldHandleExceptions = true; + var router = (Router)renderer.InstantiateComponent(); + router.AppAssembly = Assembly.GetExecutingAssembly(); + router.Found = routeData => (builder) => builder.AddContent(0, $"Rendering route matching {routeData.PageType}"); + renderer.AssignRootComponentId(router); + + var parameters = new Dictionary + { + { nameof(Router.AppAssembly), typeof(RouterTest).Assembly }, + { nameof(Router.NotFound), (RenderFragment)(builder => builder.AddContent(0, "Custom not found")) }, + { nameof(Router.NotFoundPage), typeof(NotFoundTestComponent) } + }; + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => + await renderer.Dispatcher.InvokeAsync(() => + router.SetParametersAsync(ParameterView.FromDictionary(parameters)))); + + Assert.Contains("Setting NotFound and NotFoundPage properties simultaneously is not supported", exception.Message); + Assert.Contains("Use either NotFound or NotFoundPage", exception.Message); + } + internal class TestNavigationManager : NavigationManager { public TestNavigationManager() => @@ -306,4 +342,8 @@ public class MatchAnythingComponent : ComponentBase { } [Route("a/b/c")] public class MultiSegmentRouteComponent : ComponentBase { } + + [Route("not-found")] + public class NotFoundTestComponent : ComponentBase { } + } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor index 2219da3955d2..d2adde64c256 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor @@ -30,13 +30,25 @@ - - - - - -

There's nothing here

-
+ @if (NotFoundPageType != null) + { + + + + + + + } + else + { + + + + + +

There's nothing here

+
+ }