Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.ResourceAssetProperty!>? properties) -> void
Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.ResourceAssetProperty!>? 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<Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs!>!
Expand Down
13 changes: 12 additions & 1 deletion src/Components/Components/src/Routing/Router.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ static readonly IReadOnlyDictionary<string, object> _emptyParametersDictionary
/// Gets or sets the content to display when no match is found for the requested route.
/// </summary>
[Parameter]
[Obsolete("NotFound is deprecated. Use NotFoundPage instead.")]
public RenderFragment NotFound { get; set; }

/// <summary>
/// Gets or sets the page content to display when no match is found for the requested route.
/// </summary>
[Parameter]
[DynamicallyAccessedMembers(LinkerFlags.Component)]
public Type NotFoundPage { get; set; } = default!;
public Type? NotFoundPage { get; set; }

/// <summary>
/// Gets or sets the content to display when a match is found for the requested route.
Expand Down Expand Up @@ -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("Both NotFound and NotFoundPage parameters are set on Router component. NotFoundPage is preferred and NotFound will be deprecated. Consider using only NotFoundPage.");
}
#pragma warning restore CS0618 // Type or member is obsolete
if (!typeof(IComponent).IsAssignableFrom(NotFoundPage))
{
throw new InvalidOperationException($"The type {NotFoundPage.FullName} " +
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand All @@ -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
}
}
40 changes: 40 additions & 0 deletions src/Components/Components/test/Routing/RouterTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ILoggerFactory>(NullLoggerFactory.Instance);
services.AddSingleton<NavigationManager>(_navigationManager);
services.AddSingleton<INavigationInterception, TestNavigationInterception>();
services.AddSingleton<IScrollToLocationHash, TestScrollToLocationHash>();
var serviceProvider = services.BuildServiceProvider();

var renderer = new TestRenderer(serviceProvider);
renderer.ShouldHandleExceptions = true;
var router = (Router)renderer.InstantiateComponent<Router>();
router.AppAssembly = Assembly.GetExecutingAssembly();
router.Found = routeData => (builder) => builder.AddContent(0, $"Rendering route matching {routeData.PageType}");
renderer.AssignRootComponentId(router);

var parameters = new Dictionary<string, object>
{
{ 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<InvalidOperationException>(async () =>
await renderer.Dispatcher.InvokeAsync(() =>
router.SetParametersAsync(ParameterView.FromDictionary(parameters))));

Assert.Contains("Both NotFound and NotFoundPage parameters are set on Router component", exception.Message);
Assert.Contains("NotFoundPage is preferred and NotFound will be deprecated", exception.Message);
}

internal class TestNavigationManager : NavigationManager
{
public TestNavigationManager() =>
Expand Down Expand Up @@ -306,4 +342,8 @@ public class MatchAnythingComponent : ComponentBase { }

[Route("a/b/c")]
public class MultiSegmentRouteComponent : ComponentBase { }

[Route("not-found")]
public class NotFoundTestComponent : ComponentBase { }

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,25 @@
<HeadOutlet />
</head>
<body>
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
</Found>
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
</Router>
@if (NotFoundPageType != null)
{
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
</Found>
</Router>
}
else
{
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="[data-focus-on-navigate]" />
</Found>
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
</Router>
}
<script>
// This script must come before blazor.web.js to test that
// the framework does the right thing when an element is already focused.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,22 @@
}
}

<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
</Router>
@if (NotFoundPageType != null)
{
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }" NotFoundPage="NotFoundPageType">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
}
else
{
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(TestContentPackage.NotFound.NotFoundPage).Assembly }">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound><p id="not-found-fragment">There's nothing here</p></NotFound>
</Router>
}
Loading