Skip to content

Commit aea9826

Browse files
committed
Feedback.
1 parent 9f7fceb commit aea9826

File tree

6 files changed

+75
-125
lines changed

6 files changed

+75
-125
lines changed

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22
Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler<Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs!>!
33
Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void
4+
Microsoft.AspNetCore.Components.Routing.IHostEnvironmentNavigationManager.Initialize(string! baseUri, string! uri, System.Func<string!, System.Threading.Tasks.Task!>! onNavigateTo) -> void
45
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs
56
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs.NotFoundEventArgs() -> void
67
Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.ComponentStatePersistenceManager(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager!>! logger, System.IServiceProvider! serviceProvider) -> void
@@ -10,4 +11,4 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri
1011
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void
1112
Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
1213
static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration<TService>(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
13-
static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
14+
static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!

src/Components/Components/src/Routing/IHostEnvironmentNavigationManager.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,13 @@ public interface IHostEnvironmentNavigationManager
1515
/// <param name="baseUri">The base URI.</param>
1616
/// <param name="uri">The absolute URI.</param>
1717
void Initialize(string baseUri, string uri);
18+
19+
/// <summary>
20+
/// Initializes the <see cref="NavigationManager" />.
21+
/// </summary>
22+
/// <param name="baseUri">The base URI.</param>
23+
/// <param name="uri">The absolute URI.</param>
24+
/// <param name="onNavigateTo">A delegate that points to a method handling navigation events. </param>
25+
void Initialize(string baseUri, string uri, Func<string, Task> onNavigateTo) =>
26+
Initialize(baseUri, uri);
1827
}

src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen
1212
private static bool _throwNavigationException =>
1313
AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue;
1414

15+
private Func<string, Task>? _onNavigateTo;
16+
1517
void IHostEnvironmentNavigationManager.Initialize(string baseUri, string uri) => Initialize(baseUri, uri);
1618

19+
void IHostEnvironmentNavigationManager.Initialize(string baseUri, string uri, Func<string, Task> onNavigateTo)
20+
{
21+
_onNavigateTo = onNavigateTo;
22+
Initialize(baseUri, uri);
23+
}
24+
1725
protected override void NavigateToCore(string uri, NavigationOptions options)
1826
{
1927
var absoluteUriString = ToAbsoluteUri(uri).AbsoluteUri;
@@ -23,52 +31,16 @@ protected override void NavigateToCore(string uri, NavigationOptions options)
2331
}
2432
else
2533
{
26-
if (!IsInternalUri(absoluteUriString))
27-
{
28-
// it's an external navigation, avoid Uri validation exception
29-
BaseUri = GetBaseUriFromAbsoluteUri(absoluteUriString);
30-
}
31-
Uri = absoluteUriString;
32-
NotifyLocationChanged(isInterceptedLink: false);
34+
_ = PerformNavigationAsync();
3335
}
34-
}
35-
36-
// ToDo: the following are copy-paste, consider refactoring to a common place
37-
private bool IsInternalUri(string uri)
38-
{
39-
var normalizedBaseUri = NormalizeBaseUri(BaseUri);
40-
return uri.StartsWith(normalizedBaseUri, StringComparison.OrdinalIgnoreCase);
41-
}
4236

43-
private static string GetBaseUriFromAbsoluteUri(string absoluteUri)
44-
{
45-
// Find the position of the first single slash after the scheme (e.g., "https://")
46-
var schemeDelimiterIndex = absoluteUri.IndexOf("://", StringComparison.Ordinal);
47-
if (schemeDelimiterIndex == -1)
37+
async Task PerformNavigationAsync()
4838
{
49-
throw new ArgumentException($"The provided URI '{absoluteUri}' is not a valid absolute URI.");
50-
}
51-
52-
// Find the end of the authority section (e.g., "https://example.com/")
53-
var authorityEndIndex = absoluteUri.IndexOf('/', schemeDelimiterIndex + 3);
54-
if (authorityEndIndex == -1)
55-
{
56-
// If no slash is found, the entire URI is the authority (e.g., "https://example.com")
57-
return NormalizeBaseUri(absoluteUri + "/");
58-
}
59-
60-
// Extract the base URI up to the authority section
61-
return NormalizeBaseUri(absoluteUri.Substring(0, authorityEndIndex + 1));
62-
}
63-
64-
private static string NormalizeBaseUri(string baseUri)
65-
{
66-
var lastSlashIndex = baseUri.LastIndexOf('/');
67-
if (lastSlashIndex >= 0)
68-
{
69-
baseUri = baseUri.Substring(0, lastSlashIndex + 1);
39+
if (_onNavigateTo == null)
40+
{
41+
throw new InvalidOperationException($"'{GetType().Name}' method for endpoint-based navigation has not been initialized.");
42+
}
43+
await _onNavigateTo(absoluteUriString);
7044
}
71-
72-
return baseUri;
7345
}
7446
}

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Components.Endpoints.Rendering;
45
using Microsoft.AspNetCore.Components.Rendering;
56
using Microsoft.AspNetCore.Components.RenderTree;
6-
using Microsoft.AspNetCore.Components.Routing;
77
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.WebUtilities;
89
using Microsoft.Extensions.DependencyInjection;
910
using Microsoft.Extensions.Hosting;
11+
using System.Buffers;
1012
using System.Globalization;
1113
using System.Linq;
1214
using System.Text;
@@ -85,26 +87,23 @@ private void SetNotFoundResponse(object? sender, EventArgs args)
8587
SignalRendererToFinishRendering();
8688
}
8789

88-
private void OnNavigateTo(object? sender, LocationChangedEventArgs args)
90+
private async Task OnNavigateTo(string uri)
8991
{
9092
if (_httpContext.Response.HasStarted)
9193
{
92-
// We cannot redirect after the response has already started
93-
RenderMetaRefreshTag(args.Location);
94+
var defaultBufferSize = 16 * 1024;
95+
await using var writer = new HttpResponseStreamWriter(_httpContext.Response.Body, Encoding.UTF8, defaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
96+
using var bufferWriter = new BufferedTextWriter(writer);
97+
HandleNavigationAfterResponseStarted(bufferWriter, _httpContext, uri);
98+
await bufferWriter.FlushAsync();
9499
}
95100
else
96101
{
97-
_httpContext.Response.Redirect(args.Location);
102+
_httpContext.Response.Redirect(uri);
98103
}
99104
SignalRendererToFinishRendering();
100105
}
101106

102-
private void RenderMetaRefreshTag(string location)
103-
{
104-
var metaTag = $"<meta http-equiv=\"refresh\" content=\"0;url={location}\" />";
105-
_httpContext.Response.WriteAsync(metaTag);
106-
}
107-
108107
private void UpdateNamedSubmitEvents(in RenderBatch renderBatch)
109108
{
110109
if (renderBatch.NamedEventChanges is { } changes)

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,11 @@ internal async Task InitializeStandardComponentServicesAsync(
7979
IFormCollection? form = null)
8080
{
8181
var navigationManager = httpContext.RequestServices.GetRequiredService<NavigationManager>();
82-
((IHostEnvironmentNavigationManager)navigationManager)?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request));
82+
((IHostEnvironmentNavigationManager)navigationManager)?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request), OnNavigateTo);
8383

8484
if (navigationManager != null)
8585
{
8686
navigationManager.OnNotFound += SetNotFoundResponse;
87-
navigationManager.LocationChanged += OnNavigateTo;
8887
}
8988

9089
var authenticationStateProvider = httpContext.RequestServices.GetService<AuthenticationStateProvider>();

src/Components/Server/src/Circuits/RemoteNavigationManager.cs

Lines changed: 38 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal sealed partial class RemoteNavigationManager : NavigationManager, IHost
2020
private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.HttpNavigationManager.EnableThrowNavigationException";
2121
private static bool _throwNavigationException =>
2222
AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue;
23+
private Func<string, Task>? _onNavigateTo;
2324

2425
public event EventHandler<Exception>? UnhandledException;
2526

@@ -48,6 +49,19 @@ public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger)
4849
NotifyLocationChanged(isInterceptedLink: false);
4950
}
5051

52+
/// <summary>
53+
/// Initializes the <see cref="NavigationManager" />.
54+
/// </summary>
55+
/// <param name="baseUri">The base URI.</param>
56+
/// <param name="uri">The absolute URI.</param>
57+
/// <param name="onNavigateTo">A delegate that points to a method handling navigation events. </param>
58+
public void Initialize(string baseUri, string uri, Func<string, Task> onNavigateTo)
59+
{
60+
_onNavigateTo += onNavigateTo;
61+
base.Initialize(baseUri, uri);
62+
NotifyLocationChanged(isInterceptedLink: false);
63+
}
64+
5165
/// <summary>
5266
/// Initializes the <see cref="RemoteNavigationManager"/>.
5367
/// </summary>
@@ -87,33 +101,18 @@ public async ValueTask<bool> HandleLocationChangingAsync(string uri, string? sta
87101
protected override void NavigateToCore(string uri, NavigationOptions options)
88102
{
89103
Log.RequestingNavigation(_logger, uri, options);
90-
91-
if (_jsRuntime == null)
92-
{
93-
var absoluteUriString = ToAbsoluteUri(uri).AbsoluteUri;
94-
if (_throwNavigationException)
95-
{
96-
throw new NavigationException(absoluteUriString);
97-
}
98-
else
99-
{
100-
if (!IsInternalUri(absoluteUriString))
101-
{
102-
// it's an external navigation, avoid Uri validation exception
103-
BaseUri = GetBaseUriFromAbsoluteUri(absoluteUriString);
104-
}
105-
Uri = absoluteUriString;
106-
NotifyLocationChanged(isInterceptedLink: false);
107-
return;
108-
}
109-
}
110-
111104
_ = PerformNavigationAsync();
112105

113106
async Task PerformNavigationAsync()
114107
{
115108
try
116109
{
110+
if (_jsRuntime == null)
111+
{
112+
await NavigateWithEndpoint(uri);
113+
return;
114+
}
115+
117116
var shouldContinueNavigation = await NotifyLocationChangingAsync(uri, options.HistoryEntryState, false);
118117

119118
if (!shouldContinueNavigation)
@@ -140,69 +139,40 @@ async Task PerformNavigationAsync()
140139
}
141140
}
142141

143-
private bool IsInternalUri(string uri)
142+
private async Task NavigateWithEndpoint(string uri)
144143
{
145-
var normalizedBaseUri = NormalizeBaseUri(BaseUri);
146-
return uri.StartsWith(normalizedBaseUri, StringComparison.OrdinalIgnoreCase);
147-
}
148-
149-
private static string GetBaseUriFromAbsoluteUri(string absoluteUri)
150-
{
151-
// Find the position of the first single slash after the scheme (e.g., "https://")
152-
var schemeDelimiterIndex = absoluteUri.IndexOf("://", StringComparison.Ordinal);
153-
if (schemeDelimiterIndex == -1)
154-
{
155-
throw new ArgumentException($"The provided URI '{absoluteUri}' is not a valid absolute URI.");
156-
}
157-
158-
// Find the end of the authority section (e.g., "https://example.com/")
159-
var authorityEndIndex = absoluteUri.IndexOf('/', schemeDelimiterIndex + 3);
160-
if (authorityEndIndex == -1)
144+
var absoluteUriString = ToAbsoluteUri(uri).AbsoluteUri;
145+
if (_throwNavigationException)
161146
{
162-
// If no slash is found, the entire URI is the authority (e.g., "https://example.com")
163-
return NormalizeBaseUri(absoluteUri + "/");
147+
throw new NavigationException(absoluteUriString);
164148
}
165-
166-
// Extract the base URI up to the authority section
167-
return NormalizeBaseUri(absoluteUri.Substring(0, authorityEndIndex + 1));
168-
}
169-
170-
private static string NormalizeBaseUri(string baseUri)
171-
{
172-
var lastSlashIndex = baseUri.LastIndexOf('/');
173-
if (lastSlashIndex >= 0)
149+
else
174150
{
175-
baseUri = baseUri.Substring(0, lastSlashIndex + 1);
151+
if (_onNavigateTo == null)
152+
{
153+
throw new InvalidOperationException($"'{GetType().Name}' method for endpoint-based navigation has not been initialized.");
154+
}
155+
await _onNavigateTo(absoluteUriString);
176156
}
177-
178-
return baseUri;
179157
}
180158

181159
/// <inheritdoc />
182160
public override void Refresh(bool forceReload = false)
183161
{
184-
if (_jsRuntime == null)
185-
{
186-
var absoluteUriString = ToAbsoluteUri(Uri).AbsoluteUri;
187-
if (_throwNavigationException)
188-
{
189-
throw new NavigationException(absoluteUriString);
190-
}
191-
else
192-
{
193-
Uri = absoluteUriString;
194-
NotifyLocationChanged(isInterceptedLink: false);
195-
return;
196-
}
197-
}
198-
199162
_ = RefreshAsync();
200163

201164
async Task RefreshAsync()
202165
{
203166
try
204167
{
205-
await _jsRuntime.InvokeVoidAsync(Interop.Refresh, forceReload);
168+
if (_jsRuntime == null)
169+
{
170+
await NavigateWithEndpoint(Uri);
171+
}
172+
else
173+
{
174+
await _jsRuntime.InvokeVoidAsync(Interop.Refresh, forceReload);
175+
}
206176
}
207177
catch (Exception ex)
208178
{

0 commit comments

Comments
 (0)