Blazor API Review: IUriHelper (#12425)
* Rename IUriHelper -> NavigationManager - Remove IUriHelper interface - Rename to NavigationManager - Remove all traces of old naming There's no functional or design change in this commit - just removing all traces of the old name. The next few iterations will try to improve the design. * Minor API tweaks to NavigationManager Making Initialize protected causes problems because right now the server-side code needs to deal with one of two different implementations, hence an exchange type is used. I followed the same pattern that was used for auth for symmetry but I have some *cool* thoughts. - We can remove this when we remove stateful prerendering - I have another idea to banish this pattern to the land of wind and ghosts If this ends up sticking around longer than a week in the code, lets discuss other ideas and try to improve the pattern. * Use hub method for server-side navigation * Get rid of async local * Add hub method test * Misc bikeshedding * Update src/Components/Server/src/Circuits/DefaultCircuitFactory.cs Co-Authored-By: campersau <buchholz.bastian@googlemail.com> * PR feedback
This commit is contained in:
parent
59f6b852c2
commit
9b888e9df5
|
|
@ -1,6 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static partial class JSInteropMethods
|
||||
{
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||
public static void NotifyLocationChanged(string uri, bool isInterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||
{
|
||||
public static partial class BlazorWebAssemblyHost
|
||||
|
|
@ -68,18 +77,6 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
public partial class WebAssemblyUriHelper : Microsoft.AspNetCore.Components.UriHelperBase
|
||||
{
|
||||
internal WebAssemblyUriHelper() { }
|
||||
public static readonly Microsoft.AspNetCore.Blazor.Services.WebAssemblyUriHelper Instance;
|
||||
protected override void EnsureInitialized() { }
|
||||
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Components.Builder
|
||||
{
|
||||
public static partial class ComponentsApplicationBuilderExtensions
|
||||
|
|
|
|||
|
|
@ -92,16 +92,16 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
|||
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
||||
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
|
||||
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
||||
services.AddSingleton<HttpClient>(s =>
|
||||
{
|
||||
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
|
||||
var uriHelper = s.GetRequiredService<IUriHelper>();
|
||||
var navigationManager = s.GetRequiredService<NavigationManager>();
|
||||
return new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(WebAssemblyUriHelper.Instance.GetBaseUri())
|
||||
BaseAddress = new Uri(navigationManager.BaseUri)
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.AspNetCore.Blazor.Services;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods called by interop. Intended for framework use only, not supported for use in application
|
||||
/// code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class JSInteropMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string uri, bool isInterceptedLink)
|
||||
{
|
||||
WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserUriHelperInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Default client-side implementation of <see cref="NavigationManager"/>.
|
||||
/// </summary>
|
||||
internal class WebAssemblyNavigationManager : NavigationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of <see cref="WebAssemblyNavigationManager"/>.
|
||||
/// </summary>
|
||||
public static readonly WebAssemblyNavigationManager Instance = new WebAssemblyNavigationManager();
|
||||
|
||||
// For simplicity we force public consumption of the BrowserNavigationManager through
|
||||
// a singleton. Only a single instance can be updated by the browser through
|
||||
// interop. We can construct instances for testing.
|
||||
internal WebAssemblyNavigationManager()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void EnsureInitialized()
|
||||
{
|
||||
// As described in the comment block above, BrowserNavigationManager is only for
|
||||
// client-side (Mono) use, so it's OK to rely on synchronicity here.
|
||||
var baseUri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetBaseUri);
|
||||
var uri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetLocationHref);
|
||||
Initialize(baseUri, uri);
|
||||
}
|
||||
|
||||
public void SetLocation(string uri, bool isInterceptedLink)
|
||||
{
|
||||
Uri = uri;
|
||||
NotifyLocationChanged(isInterceptedLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||
{
|
||||
if (uri == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
|
||||
WebAssemblyJSRuntime.Instance.Invoke<object>(Interop.NavigateTo, uri, forceLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserUriHelperInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Default client-side implementation of <see cref="IUriHelper"/>.
|
||||
/// </summary>
|
||||
public class WebAssemblyUriHelper : UriHelperBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of <see cref="WebAssemblyUriHelper"/>.
|
||||
/// </summary>
|
||||
public static readonly WebAssemblyUriHelper Instance = new WebAssemblyUriHelper();
|
||||
|
||||
// For simplicity we force public consumption of the BrowserUriHelper through
|
||||
// a singleton. Only a single instance can be updated by the browser through
|
||||
// interop. We can construct instances for testing.
|
||||
internal WebAssemblyUriHelper()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void EnsureInitialized()
|
||||
{
|
||||
WebAssemblyJSRuntime.Instance.Invoke<object>(
|
||||
Interop.ListenForNavigationEvents,
|
||||
typeof(WebAssemblyUriHelper).Assembly.GetName().Name,
|
||||
nameof(NotifyLocationChanged));
|
||||
|
||||
// As described in the comment block above, BrowserUriHelper is only for
|
||||
// client-side (Mono) use, so it's OK to rely on synchronicity here.
|
||||
var baseUri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetBaseUri);
|
||||
var uri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetLocationHref);
|
||||
InitializeState(uri, baseUri);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||
{
|
||||
if (uri == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
|
||||
WebAssemblyJSRuntime.Instance.Invoke<object>(Interop.NavigateTo, uri, forceLoad);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink)
|
||||
{
|
||||
Instance.SetAbsoluteUri(newAbsoluteUri);
|
||||
Instance.TriggerOnLocationChanged(isInterceptedLink);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the document's document.baseURI value, returns the URI
|
||||
/// that can be prepended to relative URI paths to produce an absolute URI.
|
||||
/// This is computed by removing anything after the final slash.
|
||||
/// Internal for tests.
|
||||
/// </summary>
|
||||
/// <param name="absoluteBaseUri">The page's document.baseURI value.</param>
|
||||
/// <returns>The URI prefix</returns>
|
||||
internal static string ToBaseUri(string absoluteBaseUri)
|
||||
{
|
||||
if (absoluteBaseUri != null)
|
||||
{
|
||||
var lastSlashIndex = absoluteBaseUri.LastIndexOf('/');
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
return absoluteBaseUri.Substring(0, lastSlashIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -266,16 +266,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
{
|
||||
public InjectAttribute() { }
|
||||
}
|
||||
public partial interface IUriHelper
|
||||
{
|
||||
event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged;
|
||||
string GetAbsoluteUri();
|
||||
string GetBaseUri();
|
||||
void NavigateTo(string uri);
|
||||
void NavigateTo(string uri, bool forceLoad);
|
||||
System.Uri ToAbsoluteUri(string href);
|
||||
string ToBaseRelativePath(string baseUri, string locationAbsolute);
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false, Inherited=true)]
|
||||
public sealed partial class LayoutAttribute : System.Attribute
|
||||
{
|
||||
|
|
@ -302,6 +292,20 @@ namespace Microsoft.AspNetCore.Components
|
|||
public NavigationException(string uri) { }
|
||||
public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public abstract partial class NavigationManager
|
||||
{
|
||||
protected NavigationManager() { }
|
||||
public string BaseUri { get { throw null; } protected set { } }
|
||||
public string Uri { get { throw null; } protected set { } }
|
||||
public event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> LocationChanged { add { } remove { } }
|
||||
protected virtual void EnsureInitialized() { }
|
||||
protected void Initialize(string baseUri, string uri) { }
|
||||
public void NavigateTo(string uri, bool forceLoad = false) { }
|
||||
protected abstract void NavigateToCore(string uri, bool forceLoad);
|
||||
protected void NotifyLocationChanged(bool isInterceptedLink) { }
|
||||
public System.Uri ToAbsoluteUri(string relativeUri) { throw null; }
|
||||
public string ToBaseRelativePath(string uri) { throw null; }
|
||||
}
|
||||
public abstract partial class OwningComponentBase : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable
|
||||
{
|
||||
protected OwningComponentBase() { }
|
||||
|
|
@ -383,23 +387,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
public RouteAttribute(string template) { }
|
||||
public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IUriHelper
|
||||
{
|
||||
protected UriHelperBase() { }
|
||||
public event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged { add { } remove { } }
|
||||
protected virtual void EnsureInitialized() { }
|
||||
public string GetAbsoluteUri() { throw null; }
|
||||
public virtual string GetBaseUri() { throw null; }
|
||||
public virtual void InitializeState(string uriAbsolute, string baseUriAbsolute) { }
|
||||
public void NavigateTo(string uri) { }
|
||||
public void NavigateTo(string uri, bool forceLoad) { }
|
||||
protected abstract void NavigateToCore(string uri, bool forceLoad);
|
||||
protected void SetAbsoluteBaseUri(string baseUri) { }
|
||||
protected void SetAbsoluteUri(string uri) { }
|
||||
public System.Uri ToAbsoluteUri(string href) { throw null; }
|
||||
public string ToBaseRelativePath(string baseUri, string locationAbsolute) { throw null; }
|
||||
protected void TriggerOnLocationChanged(bool isinterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Components.CompilerServices
|
||||
{
|
||||
|
|
@ -637,16 +624,17 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
}
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
public partial interface IHostEnvironmentNavigationManager
|
||||
{
|
||||
void Initialize(string baseUri, string uri);
|
||||
}
|
||||
public partial interface INavigationInterception
|
||||
{
|
||||
System.Threading.Tasks.Task EnableNavigationInterceptionAsync();
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct LocationChangedEventArgs
|
||||
public partial class LocationChangedEventArgs : System.EventArgs
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { throw null; }
|
||||
public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { }
|
||||
public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with URIs and navigation state.
|
||||
/// </summary>
|
||||
public interface IUriHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current absolute URI.
|
||||
/// </summary>
|
||||
/// <returns>The current absolute URI.</returns>
|
||||
string GetAbsoluteUri();
|
||||
|
||||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
/// </summary>
|
||||
event EventHandler<LocationChangedEventArgs> OnLocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URI into an absolute one (by resolving it
|
||||
/// relative to the current absolute URI).
|
||||
/// </summary>
|
||||
/// <param name="href">The relative URI.</param>
|
||||
/// <returns>The absolute URI.</returns>
|
||||
Uri ToAbsoluteUri(string href);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base URI (with trailing slash) that can be prepended before relative URI paths to produce an absolute URI.
|
||||
/// Typically this corresponds to the 'href' attribute on the document's <base> element.
|
||||
/// </summary>
|
||||
/// <returns>The URI prefix, which has a trailing slash.</returns>
|
||||
string GetBaseUri();
|
||||
|
||||
/// <summary>
|
||||
/// Given a base URI (e.g., one previously returned by <see cref="GetBaseUri"/>),
|
||||
/// converts an absolute URI into one relative to the base URI prefix.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">The base URI prefix (e.g., previously returned by <see cref="GetBaseUri"/>).</param>
|
||||
/// <param name="locationAbsolute">An absolute URI that is within the space of the base URI.</param>
|
||||
/// <returns>A relative URI path.</returns>
|
||||
string ToBaseRelativePath(string baseUri, string locationAbsolute);
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||
void NavigateTo(string uri);
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||
/// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
|
||||
void NavigateTo(string uri, bool forceLoad);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using System;
|
|||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when an <see cref="IUriHelper"/> is not able to navigate to a different url.
|
||||
/// Exception thrown when an <see cref="NavigationManager"/> is not able to navigate to a different url.
|
||||
/// </summary>
|
||||
public class NavigationException : Exception
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an abstraction for querying and mananging URI navigation.
|
||||
/// </summary>
|
||||
public abstract class NavigationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<LocationChangedEventArgs> LocationChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
AssertInitialized();
|
||||
_locationChanged += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
AssertInitialized();
|
||||
_locationChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
private EventHandler<LocationChangedEventArgs> _locationChanged;
|
||||
|
||||
// For the baseUri it's worth storing as a System.Uri so we can do operations
|
||||
// on that type. System.Uri gives us access to the original string anyway.
|
||||
private Uri _baseUri;
|
||||
|
||||
// The URI. Always represented an absolute URI.
|
||||
private string _uri;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <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.
|
||||
/// Typically this corresponds to the 'href' attribute on the document's <base> element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting <see cref="BaseUri" /> will not trigger the <see cref="LocationChanged" /> event.
|
||||
/// </remarks>
|
||||
public string BaseUri
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertInitialized();
|
||||
return _baseUri.OriginalString;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
value = NormalizeBaseUri(value);
|
||||
}
|
||||
|
||||
_baseUri = new Uri(value, UriKind.Absolute);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current URI. The <see cref="Uri" /> is always represented as an absolute URI in string form.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting <see cref="Uri" /> will not trigger the <see cref="LocationChanged" /> event.
|
||||
/// </remarks>
|
||||
public string Uri
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertInitialized();
|
||||
return _uri;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
Validate(_baseUri, value);
|
||||
_uri = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="BaseUri"/>).</param>
|
||||
/// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
|
||||
public void NavigateTo(string uri, bool forceLoad = false)
|
||||
{
|
||||
AssertInitialized();
|
||||
NavigateToCore(uri, forceLoad);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="BaseUri"/>).</param>
|
||||
/// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
|
||||
protected abstract void NavigateToCore(string uri, bool forceLoad);
|
||||
|
||||
/// <summary>
|
||||
/// Called to initialize BaseURI and current URI before these values are used for the first time.
|
||||
/// Override <see cref="EnsureInitialized" /> and call this method to dynamically calculate these values.
|
||||
/// </summary>
|
||||
protected void Initialize(string baseUri, string uri)
|
||||
{
|
||||
// Make sure it's possible/safe to call this method from constructors of derived classes.
|
||||
if (uri == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
|
||||
if (baseUri == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(baseUri));
|
||||
}
|
||||
|
||||
if (_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException($"'{GetType().Name}' already initialized.");
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
Uri = uri;
|
||||
BaseUri = baseUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows derived classes to lazyly self-initialize. Implementations that support lazy-initialization should override
|
||||
/// this method and call <see cref="Initialize(string, string)" />.
|
||||
/// </summary>
|
||||
protected virtual void EnsureInitialized()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URI into an absolute one (by resolving it
|
||||
/// relative to the current absolute URI).
|
||||
/// </summary>
|
||||
/// <param name="relativeUri">The relative URI.</param>
|
||||
/// <returns>The absolute URI.</returns>
|
||||
public Uri ToAbsoluteUri(string relativeUri)
|
||||
{
|
||||
AssertInitialized();
|
||||
return new Uri(_baseUri, relativeUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a base URI (e.g., one previously returned by <see cref="BaseUri"/>),
|
||||
/// converts an absolute URI into one relative to the base URI prefix.
|
||||
/// </summary>
|
||||
/// <param name="uri">An absolute URI that is within the space of the base URI.</param>
|
||||
/// <returns>A relative URI path.</returns>
|
||||
public string ToBaseRelativePath(string uri)
|
||||
{
|
||||
if (uri.StartsWith(_baseUri.OriginalString, StringComparison.Ordinal))
|
||||
{
|
||||
// The absolute URI must be of the form "{baseUri}something" (where
|
||||
// baseUri ends with a slash), and from that we return "something"
|
||||
return uri.Substring(_baseUri.OriginalString.Length);
|
||||
}
|
||||
|
||||
var hashIndex = uri.IndexOf('#');
|
||||
var uriWithoutHash = hashIndex < 0 ? uri : uri.Substring(0, hashIndex);
|
||||
if ($"{uriWithoutHash}/".Equals(_baseUri.OriginalString, StringComparison.Ordinal))
|
||||
{
|
||||
// Special case: for the base URI "/something/", if you're at
|
||||
// "/something" then treat it as if you were at "/something/" (i.e.,
|
||||
// with the trailing slash). It's a bit ambiguous because we don't know
|
||||
// whether the server would return the same page whether or not the
|
||||
// slash is present, but ASP.NET Core at least does by default when
|
||||
// using PathBase.
|
||||
return uri.Substring(_baseUri.OriginalString.Length - 1);
|
||||
}
|
||||
|
||||
var message = $"The URI '{uri}' is not contained by the base URI '{_baseUri}'.";
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
|
||||
internal static string NormalizeBaseUri(string baseUri)
|
||||
{
|
||||
var lastSlashIndex = baseUri.LastIndexOf('/');
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
baseUri = baseUri.Substring(0, lastSlashIndex + 1);
|
||||
}
|
||||
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <see cref="LocationChanged"/> event with the current URI value.
|
||||
/// </summary>
|
||||
protected void NotifyLocationChanged(bool isInterceptedLink)
|
||||
{
|
||||
_locationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isInterceptedLink));
|
||||
}
|
||||
|
||||
private void AssertInitialized()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
EnsureInitialized();
|
||||
}
|
||||
|
||||
if (!_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException($"'{GetType().Name}' has not been initialized.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int length)
|
||||
{
|
||||
if (uri.StartsWith(baseUri.OriginalString, StringComparison.Ordinal))
|
||||
{
|
||||
// The absolute URI must be of the form "{baseUri}something" (where
|
||||
// baseUri ends with a slash), and from that we return "something"
|
||||
length = baseUri.OriginalString.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
var hashIndex = uri.IndexOf('#');
|
||||
var uriWithoutHash = hashIndex < 0 ? uri : uri.Substring(0, hashIndex);
|
||||
if ($"{uriWithoutHash}/".Equals(baseUri.OriginalString, StringComparison.Ordinal))
|
||||
{
|
||||
// Special case: for the base URI "/something/", if you're at
|
||||
// "/something" then treat it as if you were at "/something/" (i.e.,
|
||||
// with the trailing slash). It's a bit ambiguous because we don't know
|
||||
// whether the server would return the same page whether or not the
|
||||
// slash is present, but ASP.NET Core at least does by default when
|
||||
// using PathBase.
|
||||
length = baseUri.OriginalString.Length - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void Validate(Uri baseUri, string uri)
|
||||
{
|
||||
if (baseUri == null || uri == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetLengthOfBaseUriPrefix(baseUri, uri, out _))
|
||||
{
|
||||
var message = $"The URI '{uri}' is not contained by the base URI '{baseUri}'.";
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional interface for <see cref="NavigationManager" /> implementations that must be initialized
|
||||
/// by the host.
|
||||
/// </summary>
|
||||
public interface IHostEnvironmentNavigationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="NavigationManager" />.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">The base URI.</param>
|
||||
/// <param name="uri">The absolute URI.</param>
|
||||
void Initialize(string baseUri, string uri);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ using System;
|
|||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="EventArgs" /> for <see cref="IUriHelper.OnLocationChanged" />.
|
||||
/// <see cref="EventArgs" /> for <see cref="NavigationManager.LocationChanged" />.
|
||||
/// </summary>
|
||||
public readonly struct LocationChangedEventArgs
|
||||
public class LocationChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="LocationChangedEventArgs" />.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
bool _navigationInterceptionEnabled;
|
||||
ILogger<Router> _logger;
|
||||
|
||||
[Inject] private IUriHelper UriHelper { get; set; }
|
||||
[Inject] private NavigationManager NavigationManager { get; set; }
|
||||
|
||||
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
||||
|
||||
|
|
@ -60,9 +60,9 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
{
|
||||
_logger = LoggerFactory.CreateLogger<Router>();
|
||||
_renderHandle = renderHandle;
|
||||
_baseUri = UriHelper.GetBaseUri();
|
||||
_locationAbsolute = UriHelper.GetAbsoluteUri();
|
||||
UriHelper.OnLocationChanged += OnLocationChanged;
|
||||
_baseUri = NavigationManager.BaseUri;
|
||||
_locationAbsolute = NavigationManager.Uri;
|
||||
NavigationManager.LocationChanged += OnLocationChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
UriHelper.OnLocationChanged -= OnLocationChanged;
|
||||
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
private string StringUntilAny(string str, char[] chars)
|
||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
|
||||
private void Refresh(bool isNavigationIntercepted)
|
||||
{
|
||||
var locationPath = UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute);
|
||||
var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute);
|
||||
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
||||
var context = new RouteContext(locationPath);
|
||||
Routes.Route(context);
|
||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
else
|
||||
{
|
||||
Log.NavigatingToExternalUri(_logger, _locationAbsolute, locationPath, _baseUri);
|
||||
UriHelper.NavigateTo(_locationAbsolute, forceLoad: true);
|
||||
NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,230 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for <see cref="IUriHelper"/> implementations.
|
||||
/// </summary>
|
||||
public abstract class UriHelperBase : IUriHelper
|
||||
{
|
||||
private EventHandler<LocationChangedEventArgs> _onLocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<LocationChangedEventArgs> OnLocationChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
AssertInitialized();
|
||||
_onLocationChanged += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onLocationChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// For the baseUri it's worth storing both the string form and Uri form and
|
||||
// keeping them in sync. These are always represented as absolute URIs with
|
||||
// a trailing slash.
|
||||
private Uri _baseUri;
|
||||
private string _baseUriString;
|
||||
|
||||
// The URI. Always represented an absolute URI.
|
||||
private string _uri;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||
public void NavigateTo(string uri)
|
||||
{
|
||||
NavigateTo(uri, forceLoad: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||
/// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
|
||||
public void NavigateTo(string uri, bool forceLoad)
|
||||
{
|
||||
AssertInitialized();
|
||||
NavigateToCore(uri, forceLoad);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified URI.
|
||||
/// </summary>
|
||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||
/// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
|
||||
protected abstract void NavigateToCore(string uri, bool forceLoad);
|
||||
|
||||
/// <summary>
|
||||
/// Called to initialize BaseURI and current URI before these values are used for the first time.
|
||||
/// Override this method to dynamically calculate these values.
|
||||
/// </summary>
|
||||
public virtual void InitializeState(string uriAbsolute, string baseUriAbsolute)
|
||||
{
|
||||
if (uriAbsolute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uriAbsolute));
|
||||
}
|
||||
|
||||
if (baseUriAbsolute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(baseUriAbsolute));
|
||||
}
|
||||
|
||||
if (_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException($"'{typeof(UriHelperBase).Name}' already initialized.");
|
||||
}
|
||||
_isInitialized = true;
|
||||
|
||||
SetAbsoluteUri(uriAbsolute);
|
||||
SetAbsoluteBaseUri(baseUriAbsolute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows derived classes to lazyly self initialize. It does nothing unless overriden.
|
||||
/// </summary>
|
||||
protected virtual void EnsureInitialized()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current absolute URI.
|
||||
/// </summary>
|
||||
/// <returns>The current absolute URI.</returns>
|
||||
public string GetAbsoluteUri()
|
||||
{
|
||||
AssertInitialized();
|
||||
return _uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base URI (with trailing slash) that can be prepended before relative URI paths to
|
||||
/// produce an absolute URI. Typically this corresponds to the 'href' attribute on the
|
||||
/// document's <base> element.
|
||||
/// </summary>
|
||||
/// <returns>The URI prefix, which has a trailing slash.</returns>
|
||||
public virtual string GetBaseUri()
|
||||
{
|
||||
AssertInitialized();
|
||||
return _baseUriString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URI into an absolute one (by resolving it
|
||||
/// relative to the current absolute URI).
|
||||
/// </summary>
|
||||
/// <param name="href">The relative URI.</param>
|
||||
/// <returns>The absolute URI.</returns>
|
||||
public Uri ToAbsoluteUri(string href)
|
||||
{
|
||||
AssertInitialized();
|
||||
return new Uri(_baseUri, href);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a base URI (e.g., one previously returned by <see cref="GetBaseUri"/>),
|
||||
/// converts an absolute URI into one relative to the base URI prefix.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">
|
||||
/// The base URI prefix (e.g., previously returned by <see cref="GetBaseUri"/>).
|
||||
/// </param>
|
||||
/// <param name="locationAbsolute">An absolute URI that is within the space of the base URI.</param>
|
||||
/// <returns>A relative URI path.</returns>
|
||||
public string ToBaseRelativePath(string baseUri, string locationAbsolute)
|
||||
{
|
||||
if (locationAbsolute.StartsWith(baseUri, StringComparison.Ordinal))
|
||||
{
|
||||
// The absolute URI must be of the form "{baseUri}something" (where
|
||||
// baseUri ends with a slash), and from that we return "something"
|
||||
return locationAbsolute.Substring(baseUri.Length);
|
||||
}
|
||||
|
||||
var hashIndex = locationAbsolute.IndexOf('#');
|
||||
var locationAbsoluteNoHash = hashIndex < 0 ? locationAbsolute : locationAbsolute.Substring(0, hashIndex);
|
||||
if ($"{locationAbsoluteNoHash}/".Equals(baseUri, StringComparison.Ordinal))
|
||||
{
|
||||
// Special case: for the base URI "/something/", if you're at
|
||||
// "/something" then treat it as if you were at "/something/" (i.e.,
|
||||
// with the trailing slash). It's a bit ambiguous because we don't know
|
||||
// whether the server would return the same page whether or not the
|
||||
// slash is present, but ASP.NET Core at least does by default when
|
||||
// using PathBase.
|
||||
return locationAbsolute.Substring(baseUri.Length - 1);
|
||||
}
|
||||
|
||||
var message = $"The URI '{locationAbsolute}' is not contained by the base URI '{baseUri}'.";
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the URI to the provided value.
|
||||
/// </summary>
|
||||
/// <param name="uri">The URI. Must be an absolute URI.</param>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="SetAbsoluteUri(string)"/> does not trigger <see cref="OnLocationChanged"/>.
|
||||
/// </remarks>
|
||||
protected void SetAbsoluteUri(string uri)
|
||||
{
|
||||
_uri = uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the base URI to the provided value (after normalization).
|
||||
/// </summary>
|
||||
/// <param name="baseUri">The base URI. Must be an absolute URI.</param>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="SetAbsoluteBaseUri(string)"/> does not trigger <see cref="OnLocationChanged"/>.
|
||||
/// </remarks>
|
||||
protected void SetAbsoluteBaseUri(string baseUri)
|
||||
{
|
||||
if (baseUri != null)
|
||||
{
|
||||
var lastSlashIndex = baseUri.LastIndexOf('/');
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
baseUri = baseUri.Substring(0, lastSlashIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
_baseUriString = baseUri ?? "/";
|
||||
_baseUri = new Uri(_baseUriString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <see cref="OnLocationChanged"/> event with the current URI value.
|
||||
/// </summary>
|
||||
protected void TriggerOnLocationChanged(bool isinterceptedLink)
|
||||
{
|
||||
_onLocationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isinterceptedLink));
|
||||
}
|
||||
|
||||
private void AssertInitialized()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
EnsureInitialized();
|
||||
}
|
||||
|
||||
if (!_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException($"'{GetType().Name}' has not been initialized.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,10 @@
|
|||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services.Test
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public class WebAssemblyUriHelperTest
|
||||
public class NavigationManagerTest
|
||||
{
|
||||
private WebAssemblyUriHelper _uriHelper = new WebAssemblyUriHelper();
|
||||
|
||||
[Theory]
|
||||
[InlineData("scheme://host/", "scheme://host/")]
|
||||
[InlineData("scheme://host:123/", "scheme://host:123/")]
|
||||
|
|
@ -18,7 +16,7 @@ namespace Microsoft.AspNetCore.Blazor.Services.Test
|
|||
[InlineData("scheme://host/path/page?query=string&another=here", "scheme://host/path/")]
|
||||
public void ComputesCorrectBaseUri(string baseUri, string expectedResult)
|
||||
{
|
||||
var actualResult = WebAssemblyUriHelper.ToBaseUri(baseUri);
|
||||
var actualResult = NavigationManager.NormalizeBaseUri(baseUri);
|
||||
Assert.Equal(expectedResult, actualResult);
|
||||
}
|
||||
|
||||
|
|
@ -32,9 +30,11 @@ namespace Microsoft.AspNetCore.Blazor.Services.Test
|
|||
[InlineData("scheme://host/path/", "scheme://host/path#hash", "#hash")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path/#hash", "#hash")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/path/more#hash", "more#hash")]
|
||||
public void ComputesCorrectValidBaseRelativePaths(string baseUri, string absoluteUri, string expectedResult)
|
||||
public void ComputesCorrectValidBaseRelativePaths(string baseUri, string uri, string expectedResult)
|
||||
{
|
||||
var actualResult = _uriHelper.ToBaseRelativePath(baseUri, absoluteUri);
|
||||
var navigationManager = new TestNavigationManager(baseUri);
|
||||
|
||||
var actualResult = navigationManager.ToBaseRelativePath(uri);
|
||||
Assert.Equal(expectedResult, actualResult);
|
||||
}
|
||||
|
||||
|
|
@ -42,16 +42,49 @@ namespace Microsoft.AspNetCore.Blazor.Services.Test
|
|||
[InlineData("scheme://host/", "otherscheme://host/")]
|
||||
[InlineData("scheme://host/", "scheme://otherhost/")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/")]
|
||||
public void ThrowsForInvalidBaseRelativePaths(string baseUri, string absoluteUri)
|
||||
public void Uri_ThrowsForInvalidBaseRelativePaths(string baseUri, string absoluteUri)
|
||||
{
|
||||
var navigationManager = new TestNavigationManager(baseUri);
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
_uriHelper.ToBaseRelativePath(baseUri, absoluteUri);
|
||||
navigationManager.ToBaseRelativePath(absoluteUri);
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
$"The URI '{absoluteUri}' is not contained by the base URI '{baseUri}'.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("scheme://host/", "otherscheme://host/")]
|
||||
[InlineData("scheme://host/", "scheme://otherhost/")]
|
||||
[InlineData("scheme://host/path/", "scheme://host/")]
|
||||
public void ToBaseRelativePath_ThrowsForInvalidBaseRelativePaths(string baseUri, string absoluteUri)
|
||||
{
|
||||
var navigationManager = new TestNavigationManager(baseUri);
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
navigationManager.ToBaseRelativePath(absoluteUri);
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
$"The URI '{absoluteUri}' is not contained by the base URI '{baseUri}'.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
private class TestNavigationManager : NavigationManager
|
||||
{
|
||||
public TestNavigationManager(string baseUri = null, string uri = null)
|
||||
{
|
||||
Initialize(baseUri ?? "http://example.com/", uri ?? "http://example.com/welcome-page");
|
||||
}
|
||||
|
||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,15 +51,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public virtual System.Threading.Tasks.Task OnConnectionDownAsync(Microsoft.AspNetCore.Components.Server.Circuits.Circuit circuit, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||
public virtual System.Threading.Tasks.Task OnConnectionUpAsync(Microsoft.AspNetCore.Components.Server.Circuits.Circuit circuit, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||
}
|
||||
public partial class RemoteUriHelper : Microsoft.AspNetCore.Components.UriHelperBase
|
||||
{
|
||||
public RemoteUriHelper(Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Components.Server.Circuits.RemoteUriHelper> logger) { }
|
||||
public bool HasAttachedJSRuntime { get { throw null; } }
|
||||
public override void InitializeState(string uriAbsolute, string baseUriAbsolute) { }
|
||||
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public abstract CircuitHost CreateCircuitHost(
|
||||
HttpContext httpContext,
|
||||
CircuitClientProxy client,
|
||||
string uriAbsolute,
|
||||
string baseUriAbsolute,
|
||||
string baseUri,
|
||||
string uri,
|
||||
ClaimsPrincipal user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,31 +17,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
internal class CircuitHost : IAsyncDisposable
|
||||
{
|
||||
private static readonly AsyncLocal<CircuitHost> _current = new AsyncLocal<CircuitHost>();
|
||||
private readonly SemaphoreSlim HandlerLock = new SemaphoreSlim(1);
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly CircuitHandler[] _circuitHandlers;
|
||||
private readonly ILogger _logger;
|
||||
private bool _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="Circuit"/>, if any.
|
||||
/// </summary>
|
||||
public static CircuitHost Current => _current.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current <see cref="Circuits.Circuit"/>.
|
||||
/// </summary>
|
||||
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store the circuit
|
||||
/// and other related values such as the <see cref="IJSRuntime"/> and <see cref="Renderer"/>
|
||||
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store related values such as the
|
||||
/// <see cref="IJSRuntime"/> and <see cref="Renderer"/>
|
||||
/// in the local execution context. Application code should not need to call this method,
|
||||
/// it is primarily used by the Server-Side Components infrastructure.
|
||||
/// </remarks>
|
||||
public static void SetCurrentCircuitHost(CircuitHost circuitHost)
|
||||
{
|
||||
_current.Value = circuitHost ?? throw new ArgumentNullException(nameof(circuitHost));
|
||||
if (circuitHost is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(circuitHost));
|
||||
}
|
||||
|
||||
JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime);
|
||||
RendererRegistry.SetCurrentRendererRegistry(circuitHost.RendererRegistry);
|
||||
|
|
@ -232,6 +229,38 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw
|
||||
// however, we still want this to get logged if we do.
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnLocationChangedAsync(string uri, bool intercepted)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertInitialized();
|
||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
Log.LocationChange(_logger, CircuitId, uri);
|
||||
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
navigationManager.NotifyLocationChanged(uri, intercepted);
|
||||
Log.LocationChangeSucceeded(_logger, CircuitId, uri);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// It's up to the NavigationManager implementation to validate the URI.
|
||||
//
|
||||
// Note that it's also possible that setting the URI could cause a failure in code that listens
|
||||
// to NavigationManager.LocationChanged.
|
||||
//
|
||||
// In either case, a well-behaved client will not send invalid URIs, and we don't really
|
||||
// want to continue processing with the circuit if setting the URI failed inside application
|
||||
// code. The safest thing to do is consider it a critical failure since URI is global state,
|
||||
// and a failure means that an update to global state was partially applied.
|
||||
Log.LocationChangeFailed(_logger, CircuitId, uri, ex);
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -384,6 +413,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
private static readonly Action<ILogger, Exception> _dispatchEventFailedToParseEventDescriptor;
|
||||
private static readonly Action<ILogger, string, Exception> _dispatchEventFailedToDispatchEvent;
|
||||
private static readonly Action<ILogger, Exception> _dispatchEventThroughJSInterop;
|
||||
private static readonly Action<ILogger, string, string, Exception> _locationChange;
|
||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeSucceeded;
|
||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
|
||||
|
||||
private static class EventIds
|
||||
{
|
||||
|
|
@ -401,6 +433,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public static readonly EventId EndInvokeJSFailed = new EventId(111, "EndInvokeJSFailed");
|
||||
public static readonly EventId EndInvokeJSSucceeded = new EventId(112, "EndInvokeJSSucceeded");
|
||||
public static readonly EventId DispatchEventThroughJSInterop = new EventId(113, "DispatchEventThroughJSInterop");
|
||||
public static readonly EventId LocationChange = new EventId(114, "LocationChange");
|
||||
public static readonly EventId LocationChangeSucceded = new EventId(115, "LocationChangeSucceeded");
|
||||
public static readonly EventId LocationChangeFailed = new EventId(116, "LocationChangeFailed");
|
||||
}
|
||||
|
||||
static Log()
|
||||
|
|
@ -474,6 +509,21 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
LogLevel.Debug,
|
||||
EventIds.DispatchEventThroughJSInterop,
|
||||
"There was an intent to dispatch a browser event through JS interop.");
|
||||
|
||||
_locationChange = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChange,
|
||||
"Location changing to {URI} in {CircuitId}.");
|
||||
|
||||
_locationChangeSucceeded = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChangeSucceded,
|
||||
"Location change to {URI} in {CircuitId} succeded.");
|
||||
|
||||
_locationChangeFailed = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChangeFailed,
|
||||
"Location change to {URI} in {CircuitId} failed.");
|
||||
}
|
||||
|
||||
public static void UnhandledExceptionInvokingCircuitHandler(ILogger logger, CircuitHandler handler, string handlerMethod, Exception exception)
|
||||
|
|
@ -521,8 +571,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
}
|
||||
}
|
||||
|
||||
public static void DispatchEventTroughJSInterop(ILogger logger) =>
|
||||
_dispatchEventThroughJSInterop(logger, null);
|
||||
public static void DispatchEventTroughJSInterop(ILogger logger) => _dispatchEventThroughJSInterop(logger, null);
|
||||
|
||||
public static void LocationChange(ILogger logger, string circuitId, string uri) => _locationChange(logger, circuitId, uri, null);
|
||||
|
||||
public static void LocationChangeSucceeded(ILogger logger, string circuitId, string uri) => _locationChangeSucceeded(logger, circuitId, uri, null);
|
||||
|
||||
public static void LocationChangeFailed(ILogger logger, string circuitId, string uri, Exception exception) => _locationChangeFailed(logger, circuitId, uri, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
|
|
@ -38,8 +39,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public override CircuitHost CreateCircuitHost(
|
||||
HttpContext httpContext,
|
||||
CircuitClientProxy client,
|
||||
string uriAbsolute,
|
||||
string baseUriAbsolute,
|
||||
string baseUri,
|
||||
string uri,
|
||||
ClaimsPrincipal user)
|
||||
{
|
||||
var components = ResolveComponentMetadata(httpContext, client);
|
||||
|
|
@ -51,20 +52,25 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
jsRuntime.Initialize(client);
|
||||
componentContext.Initialize(client);
|
||||
|
||||
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
||||
var authenticationStateProvider = scope.ServiceProvider.GetService<AuthenticationStateProvider>() as IHostEnvironmentAuthenticationStateProvider;
|
||||
if (authenticationStateProvider != null)
|
||||
{
|
||||
var authenticationState = new AuthenticationState(httpContext.User); // TODO: Get this from the hub connection context instead
|
||||
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
|
||||
}
|
||||
|
||||
var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService<NavigationManager>();
|
||||
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
||||
if (client.Connected)
|
||||
{
|
||||
uriHelper.AttachJsRuntime(jsRuntime);
|
||||
uriHelper.InitializeState(
|
||||
uriAbsolute,
|
||||
baseUriAbsolute);
|
||||
navigationManager.AttachJsRuntime(jsRuntime);
|
||||
navigationManager.Initialize(baseUri, uri);
|
||||
|
||||
navigationInterception.AttachJSRuntime(jsRuntime);
|
||||
}
|
||||
else
|
||||
{
|
||||
uriHelper.InitializeState(uriAbsolute, baseUriAbsolute);
|
||||
navigationManager.Initialize(baseUri, uri);
|
||||
}
|
||||
|
||||
var rendererRegistry = new RendererRegistry();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.JSInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserUriHelperInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,26 +2,26 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserUriHelperInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
/// <summary>
|
||||
/// A Server-Side Components implementation of <see cref="IUriHelper"/>.
|
||||
/// A Server-Side Blazor implementation of <see cref="NavigationManager"/>.
|
||||
/// </summary>
|
||||
public class RemoteUriHelper : UriHelperBase
|
||||
internal class RemoteNavigationManager : NavigationManager, IHostEnvironmentNavigationManager
|
||||
{
|
||||
private readonly ILogger<RemoteUriHelper> _logger;
|
||||
private readonly ILogger<RemoteNavigationManager> _logger;
|
||||
private IJSRuntime _jsRuntime;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RemoteUriHelper"/> instance.
|
||||
/// Creates a new <see cref="RemoteNavigationManager"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
|
||||
public RemoteUriHelper(ILogger<RemoteUriHelper> logger)
|
||||
public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -32,21 +32,21 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public bool HasAttachedJSRuntime => _jsRuntime != null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="RemoteUriHelper"/>.
|
||||
/// Initializes the <see cref="NavigationManager" />.
|
||||
/// </summary>
|
||||
/// <param name="uriAbsolute">The absolute URI of the current page.</param>
|
||||
/// <param name="baseUriAbsolute">The absolute base URI of the current page.</param>
|
||||
public override void InitializeState(string uriAbsolute, string baseUriAbsolute)
|
||||
/// <param name="baseUri">The base URI.</param>
|
||||
/// <param name="uri">The absolute URI.</param>
|
||||
public new void Initialize(string baseUri, string uri)
|
||||
{
|
||||
base.InitializeState(uriAbsolute, baseUriAbsolute);
|
||||
TriggerOnLocationChanged(isinterceptedLink: false);
|
||||
base.Initialize(baseUri, uri);
|
||||
NotifyLocationChanged(isInterceptedLink: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="RemoteUriHelper"/>.
|
||||
/// Initializes the <see cref="RemoteNavigationManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The <see cref="IJSRuntime"/> to use for interoperability.</param>
|
||||
internal void AttachJsRuntime(IJSRuntime jsRuntime)
|
||||
public void AttachJsRuntime(IJSRuntime jsRuntime)
|
||||
{
|
||||
if (_jsRuntime != null)
|
||||
{
|
||||
|
|
@ -54,31 +54,14 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
}
|
||||
|
||||
_jsRuntime = jsRuntime;
|
||||
|
||||
_jsRuntime.InvokeAsync<object>(
|
||||
Interop.ListenForNavigationEvents,
|
||||
typeof(RemoteUriHelper).Assembly.GetName().Name,
|
||||
nameof(NotifyLocationChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink)
|
||||
public void NotifyLocationChanged(string uri, bool intercepted)
|
||||
{
|
||||
var circuit = CircuitHost.Current;
|
||||
if (circuit == null)
|
||||
{
|
||||
var message = $"{nameof(NotifyLocationChanged)} called without a circuit.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
Log.ReceivedLocationChangedNotification(_logger, uri, intercepted);
|
||||
|
||||
var uriHelper = (RemoteUriHelper)circuit.Services.GetRequiredService<IUriHelper>();
|
||||
Log.ReceivedLocationChangedNotification(uriHelper._logger, uriAbsolute, isInterceptedLink);
|
||||
|
||||
uriHelper.SetAbsoluteUri(uriAbsolute);
|
||||
uriHelper.TriggerOnLocationChanged(isInterceptedLink);
|
||||
Uri = uri;
|
||||
NotifyLocationChanged(intercepted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
/// <summary>
|
||||
/// Intended for framework use only. Applications should not call this method directly.
|
||||
/// </summary>
|
||||
public string StartCircuit(string uriAbsolute, string baseUriAbsolute)
|
||||
public string StartCircuit(string baseUri, string uri)
|
||||
{
|
||||
if (CircuitHost != null)
|
||||
{
|
||||
|
|
@ -125,8 +125,8 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
var circuitHost = _circuitFactory.CreateCircuitHost(
|
||||
Context.GetHttpContext(),
|
||||
circuitClient,
|
||||
uriAbsolute,
|
||||
baseUriAbsolute,
|
||||
baseUri,
|
||||
uri,
|
||||
Context.User);
|
||||
|
||||
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
||||
|
|
@ -224,6 +224,18 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
CircuitHost.Renderer.OnRenderCompleted(renderId, errorMessageOrNull);
|
||||
}
|
||||
|
||||
public void OnLocationChanged(string uri, bool intercepted)
|
||||
{
|
||||
if (CircuitHost == null)
|
||||
{
|
||||
Log.CircuitHostNotInitialized(_logger);
|
||||
NotifyClientError(Clients.Caller, "Circuit not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
_ = CircuitHost.OnLocationChangedAsync(uri, intercepted);
|
||||
}
|
||||
|
||||
private async void CircuitHost_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var circuitHost = (CircuitHost)sender;
|
||||
|
|
@ -275,10 +287,10 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, "CircuitAlreadyInitialized"), "The circuit host '{CircuitId}' has already been initialized");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _circuitHostNotInitialized =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, "CircuitHostNotInitialized"), "Call to '{CallSite}' received before the circuit host initialization.");
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, "CircuitHostNotInitialized"), "Call to '{CallSite}' received before the circuit host initialization");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _circuitTerminatedGracefully =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully.");
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully");
|
||||
|
||||
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
services.TryAddSingleton<CircuitRegistry>();
|
||||
|
||||
// Standard razor component services implementations
|
||||
// Standard blazor hosting services implementations
|
||||
//
|
||||
// These intentionally replace the non-interactive versions included in MVC.
|
||||
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
||||
services.AddScoped<NavigationManager, RemoteNavigationManager>();
|
||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -68,6 +68,11 @@ async function initializeConnection(options: BlazorOptions, logger: Logger): Pro
|
|||
return connection.send('DispatchBrowserEvent', JSON.stringify(descriptor), JSON.stringify(args));
|
||||
});
|
||||
|
||||
// Configure navigation via SignalR
|
||||
window['Blazor']._internal.navigationManager.listenForNavigationEvents((uri: string, intercepted: boolean): Promise<void> => {
|
||||
return connection.send('OnLocationChanged', uri, intercepted);
|
||||
});
|
||||
|
||||
connection.on('JS.BeginInvokeJS', DotNet.jsCallDispatcher.beginInvokeJSFromDotNet);
|
||||
connection.on('JS.EndInvokeDotNet', (args: string) => DotNet.jsCallDispatcher.endInvokeDotNetFromJS(...(JSON.parse(args) as [string, boolean, unknown])));
|
||||
connection.on('JS.RenderBatch', (browserRendererId: number, batchId: number, batchData: Uint8Array) => {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,16 @@ async function boot(options?: any): Promise<void> {
|
|||
renderBatch(browserRendererId, new SharedMemoryRenderBatch(batchAddress));
|
||||
};
|
||||
|
||||
// Configure navigation via JS Interop
|
||||
window['Blazor']._internal.navigationManager.listenForNavigationEvents(async (uri: string, intercepted: boolean): Promise<void> => {
|
||||
await DotNet.invokeMethodAsync(
|
||||
'Microsoft.AspNetCore.Blazor',
|
||||
'NotifyLocationChanged',
|
||||
uri,
|
||||
intercepted
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch the boot JSON file
|
||||
const bootConfig = await fetchBootConfigAsync();
|
||||
const embeddedResourcesPromise = loadEmbeddedResourcesAsync(bootConfig);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { navigateTo, internalFunctions as uriHelperInternalFunctions } from './Services/UriHelper';
|
||||
import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
|
||||
import { internalFunctions as httpInternalFunctions } from './Services/Http';
|
||||
import { attachRootComponentToElement } from './Rendering/Renderer';
|
||||
|
||||
|
|
@ -9,6 +9,6 @@ window['Blazor'] = {
|
|||
_internal: {
|
||||
attachRootComponentToElement,
|
||||
http: httpInternalFunctions,
|
||||
uriHelper: uriHelperInternalFunctions,
|
||||
navigationManager: navigationManagerInternalFunctions,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { internalFunctions as uriHelperFunctions } from '../../Services/UriHelper';
|
||||
import { internalFunctions as navigationManagerFunctions } from '../../Services/NavigationManager';
|
||||
|
||||
export class CircuitDescriptor {
|
||||
public circuitId: string;
|
||||
|
|
@ -13,7 +13,7 @@ export class CircuitDescriptor {
|
|||
}
|
||||
|
||||
export async function startCircuit(connection: signalR.HubConnection): Promise<CircuitDescriptor> {
|
||||
const result = await connection.invoke<string>('StartCircuit', uriHelperFunctions.getLocationHref(), uriHelperFunctions.getBaseURI());
|
||||
const result = await connection.invoke<string>('StartCircuit', navigationManagerFunctions.getBaseURI(), navigationManagerFunctions.getLocationHref());
|
||||
if (result) {
|
||||
return new CircuitDescriptor(result);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ let hasRegisteredNavigationInterception = false;
|
|||
let hasRegisteredNavigationEventListeners = false;
|
||||
|
||||
// Will be initialized once someone registers
|
||||
let notifyLocationChangedCallback: { assemblyName: string; functionName: string } | null = null;
|
||||
let notifyLocationChangedCallback: ((uri: string, intercepted: boolean) => Promise<void>) | null = null;
|
||||
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
export const internalFunctions = {
|
||||
|
|
@ -16,12 +16,12 @@ export const internalFunctions = {
|
|||
getLocationHref: () => location.href,
|
||||
};
|
||||
|
||||
function listenForNavigationEvents(assemblyName: string, functionName: string) {
|
||||
function listenForNavigationEvents(callback: (uri: string, intercepted: boolean) => Promise<void>) {
|
||||
if (hasRegisteredNavigationEventListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyLocationChangedCallback = { assemblyName, functionName };
|
||||
notifyLocationChangedCallback = callback;
|
||||
|
||||
hasRegisteredNavigationEventListeners = true;
|
||||
window.addEventListener('popstate', () => notifyLocationChanged(false));
|
||||
|
|
@ -95,12 +95,7 @@ function performInternalNavigation(absoluteInternalHref: string, interceptedLink
|
|||
|
||||
async function notifyLocationChanged(interceptedLink: boolean) {
|
||||
if (notifyLocationChangedCallback) {
|
||||
await DotNet.invokeMethodAsync(
|
||||
notifyLocationChangedCallback.assemblyName,
|
||||
notifyLocationChangedCallback.functionName,
|
||||
location.href,
|
||||
interceptedLink
|
||||
);
|
||||
await notifyLocationChangedCallback(location.href, interceptedLink);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,11 +4,9 @@
|
|||
namespace Microsoft.AspNetCore.Components.Web
|
||||
{
|
||||
// Shared interop constants
|
||||
internal static class BrowserUriHelperInterop
|
||||
internal static class BrowserNavigationManagerInterop
|
||||
{
|
||||
private static readonly string Prefix = "Blazor._internal.uriHelper.";
|
||||
|
||||
public static readonly string ListenForNavigationEvents = Prefix + "listenForNavigationEvents";
|
||||
private static readonly string Prefix = "Blazor._internal.navigationManager.";
|
||||
|
||||
public static readonly string EnableNavigationInterception = Prefix + "enableNavigationInterception";
|
||||
|
||||
|
|
@ -51,13 +51,13 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
[Parameter]
|
||||
public NavLinkMatch Match { get; set; }
|
||||
|
||||
[Inject] private IUriHelper UriHelper { get; set; }
|
||||
[Inject] private NavigationManager NavigationManger { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// We'll consider re-rendering on each location change
|
||||
UriHelper.OnLocationChanged += OnLocationChanged;
|
||||
NavigationManger.LocationChanged += OnLocationChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -70,8 +70,8 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
href = Convert.ToString(obj);
|
||||
}
|
||||
|
||||
_hrefAbsolute = href == null ? null : UriHelper.ToAbsoluteUri(href).AbsoluteUri;
|
||||
_isActive = ShouldMatch(UriHelper.GetAbsoluteUri());
|
||||
_hrefAbsolute = href == null ? null : NavigationManger.ToAbsoluteUri(href).AbsoluteUri;
|
||||
_isActive = ShouldMatch(NavigationManger.Uri);
|
||||
|
||||
_class = (string)null;
|
||||
if (AdditionalAttributes != null && AdditionalAttributes.TryGetValue("class", out obj))
|
||||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
public void Dispose()
|
||||
{
|
||||
// To avoid leaking memory, it's important to detach any event handlers in Dispose()
|
||||
UriHelper.OnLocationChanged -= OnLocationChanged;
|
||||
NavigationManger.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
private void UpdateCssClass()
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
// Act
|
||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"StartCircuit",
|
||||
baseUri.GetLeftPart(UriPartial.Authority),
|
||||
baseUri));
|
||||
baseUri,
|
||||
baseUri.GetLeftPart(UriPartial.Authority)));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'BeginInvokeDotNetFromJS' received before the circuit host initialization."));
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'BeginInvokeDotNetFromJS' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'EndInvokeJSFromDotNet' received before the circuit host initialization."));
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'EndInvokeJSFromDotNet' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'DispatchBrowserEvent' received before the circuit host initialization."));
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'DispatchBrowserEvent' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -162,7 +162,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'OnRenderCompleted' received before the circuit host initialization."));
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'OnRenderCompleted' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CannotInvokeOnLocationChangedBeforeInitialization()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = "Circuit not initialized.";
|
||||
var rootUri = _serverFixture.RootUri;
|
||||
var baseUri = new Uri(rootUri, "/subdir");
|
||||
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false, connectAutomatically: false));
|
||||
Assert.Empty(Batches);
|
||||
|
||||
// Act
|
||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"OnLocationChanged",
|
||||
baseUri.AbsoluteUri,
|
||||
false));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'OnLocationChanged' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -239,13 +239,15 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
// Act
|
||||
await Client.ClickAsync("triggerjsinterop-malformed");
|
||||
|
||||
Assert.Single(interopCalls, (4, "sendMalformedCallbackReturn", (string)null));
|
||||
var call = interopCalls.FirstOrDefault(call => call.identifier == "sendMalformedCallbackReturn");
|
||||
Assert.NotEqual(default, call);
|
||||
|
||||
var id = call.id;
|
||||
await Client.HubConnection.InvokeAsync(
|
||||
"EndInvokeJSFromDotNet",
|
||||
4,
|
||||
id,
|
||||
true,
|
||||
"[4, true, \"{\"]");
|
||||
$"[{id}, true, \"{{\"]");
|
||||
|
||||
var text = Assert.Single(
|
||||
Client.FindElementById("errormessage-malformed").Children.OfType<TextNode>(),
|
||||
|
|
@ -264,16 +266,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
||||
var logEvents = new List<(LogLevel logLevel, string)>();
|
||||
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
|
||||
|
||||
// Act
|
||||
await Client.ClickAsync("triggerjsinterop-malformed");
|
||||
|
||||
Assert.Single(interopCalls, (4, "sendMalformedCallbackReturn", (string)null));
|
||||
var call = interopCalls.FirstOrDefault(call => call.identifier == "sendMalformedCallbackReturn");
|
||||
Assert.NotEqual(default, call);
|
||||
|
||||
var id = call.id;
|
||||
await Client.HubConnection.InvokeAsync(
|
||||
"EndInvokeJSFromDotNet",
|
||||
4,
|
||||
id,
|
||||
true,
|
||||
"[4, true, }");
|
||||
$"[{id}, true, }}");
|
||||
|
||||
// A completely malformed payload like the one above never gets to the application.
|
||||
Assert.Single(
|
||||
|
|
@ -285,9 +290,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
await Client.ClickAsync("triggerjsinterop-success");
|
||||
await Client.HubConnection.InvokeAsync(
|
||||
"EndInvokeJSFromDotNet",
|
||||
5,
|
||||
id++,
|
||||
true,
|
||||
"[5, true, null]");
|
||||
$"[{id}, true, null]");
|
||||
|
||||
Assert.Single(
|
||||
Client.FindElementById("errormessage-success").Children.OfType<TextNode>(),
|
||||
|
|
@ -298,9 +303,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
await Client.ClickAsync("triggerjsinterop-failure");
|
||||
await Client.HubConnection.InvokeAsync(
|
||||
"EndInvokeJSFromDotNet",
|
||||
6,
|
||||
id++,
|
||||
false,
|
||||
"[6, false, \"There was an error invoking sendFailureCallbackReturn\"]");
|
||||
$"[{id}, false, \"There was an error invoking sendFailureCallbackReturn\"]");
|
||||
|
||||
Assert.Single(
|
||||
Client.FindElementById("errormessage-failure").Children.OfType<TextNode>(),
|
||||
|
|
@ -534,7 +539,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Assert.Equal(2, batches.Count);
|
||||
}
|
||||
|
||||
private (List<(int, string, string)>, List<string>, List<(int, int, byte[])>) ConfigureClient()
|
||||
private (List<(int id, string identifier, string args)>, List<string>, List<(int, int, byte[])>) ConfigureClient()
|
||||
{
|
||||
var interopCalls = new List<(int, string, string)>();
|
||||
Client.JSInterop += (int arg1, string arg2, string arg3) => interopCalls.Add((arg1, arg2, arg3));
|
||||
|
|
|
|||
|
|
@ -369,9 +369,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void UsingUriHelperWithoutRouterWorks()
|
||||
public void UsingNavigationManagerWithoutRouterWorks()
|
||||
{
|
||||
var app = MountTestComponent<UriHelperComponent>();
|
||||
var app = MountTestComponent<NavigationManagerComponent>();
|
||||
var initialUrl = Browser.Url;
|
||||
|
||||
Browser.Equal(Browser.Url, () => app.FindElement(By.Id("test-info")).Text);
|
||||
|
|
@ -387,7 +387,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
[Fact]
|
||||
public void UriHelperCanReadAbsoluteUriIncludingHash()
|
||||
{
|
||||
var app = MountTestComponent<UriHelperComponent>();
|
||||
var app = MountTestComponent<NavigationManagerComponent>();
|
||||
Browser.Equal(Browser.Url, () => app.FindElement(By.Id("test-info")).Text);
|
||||
|
||||
var uri = "/mytestpath?my=query&another#some/hash?tokens";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@inject IUriHelper UriHelper
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@*
|
||||
This router is independent of any other router that may exist within the same project.
|
||||
|
|
@ -27,11 +27,11 @@
|
|||
{
|
||||
// Start at AuthHome, not at any other component in the same app that happens to
|
||||
// register itself for the route ""
|
||||
var absoluteUriPath = new Uri(UriHelper.GetAbsoluteUri()).GetLeftPart(UriPartial.Path);
|
||||
var relativeUri = UriHelper.ToBaseRelativePath(UriHelper.GetBaseUri(), absoluteUriPath);
|
||||
var absoluteUriPath = new Uri(NavigationManager.Uri).GetLeftPart(UriPartial.Path);
|
||||
var relativeUri = NavigationManager.ToBaseRelativePath(absoluteUriPath);
|
||||
if (relativeUri == string.Empty)
|
||||
{
|
||||
UriHelper.NavigateTo("AuthHome");
|
||||
NavigationManager.NavigateTo("AuthHome");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@inject IUriHelper UriHelper
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<h3>Select your language</h3>
|
||||
<select @onchange="@OnSelected" id="culture-selector">
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
void OnSelected(ChangeEventArgs e)
|
||||
{
|
||||
// Included fragment to preserve choice of Blazor client or server.
|
||||
var redirect = new Uri(UriHelper.GetAbsoluteUri()).GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped);
|
||||
var redirect = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped);
|
||||
var query = $"?culture={Uri.EscapeDataString((string)e.Value)}&redirectUri={redirect}";
|
||||
UriHelper.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
|
||||
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@
|
|||
<option value="BasicTestApp.ReliabilityComponent">Server reliability component</option>
|
||||
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
|
||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
||||
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
||||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@page "/prerendered-redirection"
|
||||
@inject IUriHelper UriHelper
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@{
|
||||
throw new InvalidOperationException("The rendering logic should never be executed");
|
||||
|
|
@ -8,9 +8,9 @@
|
|||
@code {
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
var uri = UriHelper.GetAbsoluteUri();
|
||||
var uri = NavigationManager.Uri;
|
||||
var destination = uri.Substring(uri.IndexOf("?destination=") + 13);
|
||||
UriHelper.NavigateTo(destination);
|
||||
NavigationManager.NavigateTo(destination);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@inject Microsoft.AspNetCore.Components.IUriHelper uriHelper
|
||||
@inject NavigationManager NavigationManager
|
||||
<style type="text/css">
|
||||
a.active {
|
||||
background-color: yellow;
|
||||
|
|
@ -22,11 +22,11 @@
|
|||
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
|
||||
</ul>
|
||||
|
||||
<button id="do-navigation" @onclick=@(x => uriHelper.NavigateTo("Other"))>
|
||||
<button id="do-navigation" @onclick=@(x => NavigationManager.NavigateTo("Other"))>
|
||||
Programmatic navigation
|
||||
</button>
|
||||
|
||||
<button id="do-navigation-forced" @onclick=@(x => uriHelper.NavigateTo("Other", true))>
|
||||
<button id="do-navigation-forced" @onclick=@(x => NavigationManager.NavigateTo("Other", true))>
|
||||
Programmatic navigation with force-load
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
@page "/LongPage1"
|
||||
@inject IUriHelper UriHelper
|
||||
@inject NavigationManager NavigationManager
|
||||
<div id="test-info">This is a long page you can scroll.</div>
|
||||
|
||||
<div style="border: 2px dashed red; margin: 1rem; padding: 1rem; height: 1500px;">
|
||||
Scroll past me to find the links
|
||||
</div>
|
||||
|
||||
<button id="go-to-longpage2" @onclick="@(() => UriHelper.NavigateTo("LongPage2"))">
|
||||
<button id="go-to-longpage2" @onclick="@(() => NavigationManager.NavigateTo("LongPage2"))">
|
||||
Navigate programmatically to long page 2
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject Microsoft.JSInterop.IJSRuntime JSRuntime
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@implements IDisposable
|
||||
|
||||
<button @onclick="Navigate">Navigate</button>
|
||||
|
||||
<span id="test-info">@UrlLocation</span>
|
||||
|
||||
@code{
|
||||
string UrlLocation;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UrlLocation = NavigationManager.Uri;
|
||||
NavigationManager.LocationChanged += OnLocationChanged;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
void OnLocationChanged(object sender, LocationChangedEventArgs e)
|
||||
{
|
||||
UrlLocation = NavigationManager.Uri;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async Task Navigate()
|
||||
{
|
||||
await JSRuntime.InvokeAsync<object>("navigationManagerNavigate");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
@inject IUriHelper UriHelper
|
||||
@inject Microsoft.JSInterop.IJSRuntime JSRuntime
|
||||
|
||||
<button @onclick="Navigate">Navigate</button>
|
||||
|
||||
<span id="test-info">@UrlLocation</span>
|
||||
|
||||
@code{
|
||||
string UrlLocation;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UrlLocation = UriHelper.GetAbsoluteUri();
|
||||
UriHelper.OnLocationChanged += (_, __) =>
|
||||
{
|
||||
UrlLocation = UriHelper.GetAbsoluteUri();
|
||||
StateHasChanged();
|
||||
};
|
||||
}
|
||||
|
||||
async Task Navigate()
|
||||
{
|
||||
await JSRuntime.InvokeAsync<object>("uriHelperNavigate");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
@page "/show-uri"
|
||||
@inject IUriHelper UriHelper
|
||||
The current URL is <strong>@UriHelper.GetAbsoluteUri()</strong>
|
||||
@inject NavigationManager NavigationManager
|
||||
The current URL is <strong>@NavigationManager.Uri</strong>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
return element.value;
|
||||
}
|
||||
|
||||
function uriHelperNavigate() {
|
||||
function navigationManagerNavigate() {
|
||||
Blazor.navigateTo('/subdir/some-path');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ namespace Ignitor
|
|||
else
|
||||
{
|
||||
await ExpectRenderBatch(
|
||||
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", new Uri(uri.GetLeftPart(UriPartial.Authority)), uri),
|
||||
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, new Uri(uri.GetLeftPart(UriPartial.Authority))),
|
||||
TimeSpan.FromSeconds(10));
|
||||
return CircuitId != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// Component prerendering
|
||||
//
|
||||
services.TryAddScoped<StaticComponentRenderer>();
|
||||
services.TryAddScoped<IUriHelper, HttpUriHelper>();
|
||||
services.TryAddScoped<NavigationManager, HttpNavigationManager>();
|
||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
services.TryAddScoped<IComponentContext, UnsupportedComponentContext>();
|
||||
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal class HttpUriHelper : UriHelperBase
|
||||
internal class HttpNavigationManager : NavigationManager, IHostEnvironmentNavigationManager
|
||||
{
|
||||
void IHostEnvironmentNavigationManager.Initialize(string baseUri, string uri) => Initialize(baseUri, uri);
|
||||
|
||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||
{
|
||||
throw new NavigationException(uri);
|
||||
|
|
@ -7,6 +7,7 @@ using System.Text.Encodings.Web;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
|
@ -77,8 +78,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
|||
authenticationStateProvider.SetAuthenticationState(Task.FromResult(authenticationState));
|
||||
}
|
||||
|
||||
var helper = (UriHelperBase)httpContext.RequestServices.GetRequiredService<IUriHelper>();
|
||||
helper.InitializeState(GetFullUri(httpContext.Request), GetContextBaseUri(httpContext.Request));
|
||||
var navigationManager = (IHostEnvironmentNavigationManager)httpContext.RequestServices.GetRequiredService<NavigationManager>();
|
||||
navigationManager?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
|
|||
private string GetContextBaseUri(HttpRequest request)
|
||||
{
|
||||
var result = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase);
|
||||
|
||||
|
||||
// PathBase may be "/" or "/some/thing", but to be a well-formed base URI
|
||||
// it has to end with a trailing slash
|
||||
return result.EndsWith("/") ? result : result += "/";
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(HtmlEncoder.Default);
|
||||
services.AddSingleton<IJSRuntime,UnsupportedJavaScriptRuntime>();
|
||||
services.AddSingleton<IUriHelper,HttpUriHelper>();
|
||||
services.AddSingleton<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
services.AddSingleton<NavigationManager, HttpNavigationManager>();
|
||||
services.AddSingleton<StaticComponentRenderer>();
|
||||
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
|
||||
|
||||
|
|
@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
|
||||
private class RedirectComponent : ComponentBase
|
||||
{
|
||||
[Inject] IUriHelper UriHelper { get; set; }
|
||||
[Inject] NavigationManager NavigationManager { get; set; }
|
||||
|
||||
[Parameter] public string RedirectUri { get; set; }
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
|||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UriHelper.NavigateTo(RedirectUri, Force);
|
||||
NavigationManager.NavigateTo(RedirectUri, Force);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
@inject IUriHelper Helper
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@functions{
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Helper.NavigateTo("/navigation-redirect");
|
||||
NavigationManager.NavigateTo("/navigation-redirect");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue