Skip to content

HttpNavigationManager no longer uses NavigationException #61306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0be84e4
Rise even instead of throwing.
ilonatommy Apr 3, 2025
2e5f7dc
Clean up, delay not needed.
ilonatommy Apr 3, 2025
5ebaaac
Fix typo + test old way of workign as well.
ilonatommy Apr 3, 2025
1ea43be
Update name to be full namespace + remove readonly.
ilonatommy Apr 4, 2025
e9bd57e
Feedback - interactive SSR updates required exposing some methods.
ilonatommy Apr 7, 2025
ebc2537
Fix missing xml.
ilonatommy Apr 7, 2025
dff3c70
Fix build of tests.
ilonatommy Apr 7, 2025
19d3fe9
Fix nullable.
ilonatommy Apr 7, 2025
def3e5c
Feedback - limit public API changes.
ilonatommy Apr 8, 2025
9f79f51
Handle the case when response started.
ilonatommy Apr 8, 2025
e647f2a
Proposal of fixing external navigation.
ilonatommy Apr 9, 2025
5e6d104
Update src/Components/test/testassets/Components.TestServer/RazorComp…
ilonatommy Apr 9, 2025
8413a40
Merge branch 'main' into fix-59451
ilonatommy Apr 10, 2025
9f7fceb
Merge branch 'fix-59451' of https://github.com/ilonatommy/aspnetcore …
ilonatommy Apr 10, 2025
aea9826
Feedback.
ilonatommy Apr 11, 2025
bafa937
More effective stopping of the renderer.
ilonatommy Apr 14, 2025
3521c8f
POST cannot safely redirect like GET does, the body should be preserved.
ilonatommy Apr 14, 2025
a3ca937
Reuse the logic from navigation exception.
ilonatommy Apr 14, 2025
5d7eb68
Editing the ongoing render batch is not possible - for non-streaming …
ilonatommy Apr 14, 2025
69426fd
Missing change for the last commit.
ilonatommy Apr 15, 2025
e0b407c
Rename switch to match http and remote navigator.
ilonatommy Apr 15, 2025
dc45f3e
Adjust test for the new behavior.
ilonatommy Apr 15, 2025
b36db8b
Fix exception - driven navigation.
ilonatommy Apr 15, 2025
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
12 changes: 12 additions & 0 deletions src/Components/Components/src/NavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ public event EventHandler<NotFoundEventArgs> OnNotFound
// The URI. Always represented an absolute URI.
private string? _uri;
private bool _isInitialized;
private const string EnableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.HttpNavigationManager.EnableThrowNavigationException";

/// <summary>
/// Gets a value indicating whether navigation exceptions should be thrown during navigation.
/// </summary>
/// <remarks>
/// This property is controlled by the AppContext switch
/// <c>Microsoft.AspNetCore.Components.Endpoints.HttpNavigationManager.EnableThrowNavigationException</c>.
/// When enabled, navigation operations may throw exceptions for debugging or testing purposes.
/// </remarks>
protected static bool ThrowNavigationException =>
AppContext.TryGetSwitch(EnableThrowNavigationException, out var switchValue) && switchValue;

/// <summary>
/// Gets or sets the current base URI. The <see cref="BaseUri" /> is always represented as an absolute URI in string form with trailing slash.
Expand Down
5 changes: 5 additions & 0 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri
Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration<TService>(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static Microsoft.AspNetCore.Components.NavigationManager.ThrowNavigationException.get -> bool
Microsoft.AspNetCore.Components.Routing.NavigationEventArgs
Microsoft.AspNetCore.Components.Routing.NavigationEventArgs.Uri.get -> string!
Microsoft.AspNetCore.Components.Routing.NavigationEventArgs.NavigationEventArgs(string! uri) -> void
Microsoft.AspNetCore.Components.Routing.IHostEnvironmentNavigationManager.OnNavigateTo -> System.EventHandler<Microsoft.AspNetCore.Components.Routing.NavigationEventArgs!>!
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@ public interface IHostEnvironmentNavigationManager
/// <param name="baseUri">The base URI.</param>
/// <param name="uri">The absolute URI.</param>
void Initialize(string baseUri, string uri);

/// <summary>
/// An event that is triggered when SSR navigation occurs.
/// </summary>
/// <remarks>
/// This event allows subscribers to respond to SSR navigation actions, such as updating state or performing side effects.
/// </remarks>
event EventHandler<NavigationEventArgs> OnNavigateTo;
}
24 changes: 24 additions & 0 deletions src/Components/Components/src/Routing/NavigationEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Components.Routing;

/// <summary>
/// <see cref="EventArgs" /> for <see cref="NavigationManager.LocationChanged" />.
/// </summary>
public class NavigationEventArgs : EventArgs
{
/// <summary>
/// Gets the URI of the navigation event.
/// </summary>
public string Uri { get; }

/// <summary>
/// Initializes a new instance of <see cref="NavigationEventArgs"/>.
/// </summary>
/// <param name="uri">The URI of the navigation event.</param>
public NavigationEventArgs(string uri)
{
Uri = uri;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@ namespace Microsoft.AspNetCore.Components.Endpoints;

internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmentNavigationManager
{
private EventHandler<NavigationEventArgs>? _onNavigateTo;
public event EventHandler<NavigationEventArgs> OnNavigateTo
{
add => _onNavigateTo += value;
remove => _onNavigateTo -= value;
}

void IHostEnvironmentNavigationManager.Initialize(string baseUri, string uri) => Initialize(baseUri, uri);

protected override void NavigateToCore(string uri, NavigationOptions options)
{
var absoluteUriString = ToAbsoluteUri(uri).AbsoluteUri;
throw new NavigationException(absoluteUriString);
if (ThrowNavigationException)
{
throw new NavigationException(absoluteUriString);
}
else
{
_onNavigateTo?.Invoke(this, new NavigationEventArgs(absoluteUriString));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ internal async Task InitializeStandardComponentServicesAsync(
if (navigationManager != null)
{
navigationManager.OnNotFound += SetNotFoundResponse;
if (navigationManager is IHostEnvironmentNavigationManager hostEnvironmentNavigationManager)
{
hostEnvironmentNavigationManager.OnNavigateTo += OnNavigateTo;
}
}

var authenticationStateProvider = httpContext.RequestServices.GetService<AuthenticationStateProvider>();
Expand Down Expand Up @@ -135,6 +139,11 @@ internal async Task InitializeStandardComponentServicesAsync(
}
}

private void OnNavigateTo(object? sender, NavigationEventArgs args)
{
_httpContext.Response.Redirect(args.Uri);
}

private static void InitializeResourceCollection(HttpContext httpContext)
{

Expand Down
8 changes: 8 additions & 0 deletions src/Components/Endpoints/test/RazorComponentResultTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,14 @@ class FakeDataProtector : IDataProtector

class FakeNavigationManager : NavigationManager, IHostEnvironmentNavigationManager
{
private EventHandler<NavigationEventArgs> _onNavigateTo;

public event EventHandler<NavigationEventArgs> OnNavigateTo
{
add => _onNavigateTo += value;
remove => _onNavigateTo -= value;
}

public new void Initialize(string baseUri, string uri)
=> base.Initialize(baseUri, uri);

Expand Down
24 changes: 22 additions & 2 deletions src/Components/Server/src/Circuits/RemoteNavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ internal sealed partial class RemoteNavigationManager : NavigationManager, IHost
private readonly ILogger<RemoteNavigationManager> _logger;
private IJSRuntime _jsRuntime;
private bool? _navigationLockStateBeforeJsRuntimeAttached;
private EventHandler<NavigationEventArgs>? _onNavigateTo;
public event EventHandler<NavigationEventArgs> OnNavigateTo
{
add => _onNavigateTo += value;
remove => _onNavigateTo -= value;
}

public event EventHandler<Exception>? UnhandledException;

Expand Down Expand Up @@ -88,7 +94,14 @@ protected override void NavigateToCore(string uri, NavigationOptions options)
if (_jsRuntime == null)
{
var absoluteUriString = ToAbsoluteUri(uri).AbsoluteUri;
throw new NavigationException(absoluteUriString);
if (ThrowNavigationException)
{
throw new NavigationException(absoluteUriString);
}
else
{
_onNavigateTo?.Invoke(this, new NavigationEventArgs(absoluteUriString));
}
}

_ = PerformNavigationAsync();
Expand Down Expand Up @@ -129,7 +142,14 @@ public override void Refresh(bool forceReload = false)
if (_jsRuntime == null)
{
var absoluteUriString = ToAbsoluteUri(Uri).AbsoluteUri;
throw new NavigationException(absoluteUriString);
if (ThrowNavigationException)
{
throw new NavigationException(absoluteUriString);
}
else
{
_onNavigateTo?.Invoke(this, new NavigationEventArgs(absoluteUriString));
}
}

_ = RefreshAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1399,4 +1399,16 @@ public void CanPersistMultiplePrerenderedStateDeclaratively_Auto_PersistsOnWebAs
Browser.Equal("restored 2", () => Browser.FindElement(By.Id("auto-2")).Text);
Browser.Equal("WebAssembly", () => Browser.FindElement(By.Id("render-mode-auto-2")).Text);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void NavigatesWithInteractivityByRequestRedirection(bool controlFlowByException)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.EnableThrowNavigationException", isEnabled: controlFlowByException);
Navigate($"{ServerPathBase}/routing/ssr-navigate-to");
Browser.Equal("Click submit to navigate to home", () => Browser.Exists(By.Id("test-info")).Text);
Browser.Click(By.Id("redirectButton"));
Browser.Equal("Routing test cases", () => Browser.Exists(By.Id("test-info")).Text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,16 @@ public void CanUseServerAuthenticationStateByDefault()
Browser.Equal("True", () => Browser.FindElement(By.Id("is-in-test-role-1")).Text);
Browser.Equal("True", () => Browser.FindElement(By.Id("is-in-test-role-2")).Text);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void NavigatesWithoutInteractivityByRequestRedirection(bool controlFlowByException)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.EnableThrowNavigationException", isEnabled: controlFlowByException);
Navigate($"{ServerPathBase}/routing/ssr-navigate-to");
Browser.Equal("Click submit to navigate to home", () => Browser.Exists(By.Id("test-info")).Text);
Browser.Click(By.Id("redirectButton"));
Browser.Equal("Routing test cases", () => Browser.Exists(By.Id("test-info")).Text);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@page "/routing"
<h3>Routing test cases</h3>
<h3 id="test-info">Routing test cases</h3>

<ul>
<li>
Expand All @@ -24,12 +24,6 @@
<a href="routing/complex-segment(value)">Complex segments</a>
</li>
<li>
<a href="routing/not-found-ssr">Not found page for Static Server Rendering</a>
</li>
<li>
<a href="routing/not-found-webassembly">Not found page for Interactive WebAssembly rendering</a>
</li>
<li>
<a href="routing/not-found-server">Not found page for Interactive Server rendering</a>
<a href="routing/ssr-navigate-to">Redirect on SSR page</a>
</li>
</ul>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@page "/routing/ssr-navigate-to"
@using Microsoft.AspNetCore.Components.Forms
@inject NavigationManager NavigationManager

<p id="test-info">Click submit to navigate to home</p>
<form method="post" @onsubmit="Submit" @formname="MyUniqueFormName">
<AntiforgeryToken />
<button type="submit" id="redirectButton" class="btn btn-primary">Redirect</button>
</form>

@code {
private void Submit()
{
NavigationManager.NavigateTo("/subdir/routing");
}
}

8 changes: 8 additions & 0 deletions src/Mvc/Mvc.TagHelpers/test/ComponentTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)

class MockNavigationManager : NavigationManager, IHostEnvironmentNavigationManager
{
private EventHandler<NavigationEventArgs> _onNavigateTo;

public event EventHandler<NavigationEventArgs> OnNavigateTo
{
add => _onNavigateTo += value;
remove => _onNavigateTo -= value;
}

public MockNavigationManager()
{
Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash");
Expand Down
Loading