Defer link interception until Router is initialized (#10062)
* Defer link interception until Router is initialized Fixes https://github.com/aspnet/AspNetCore/issues/9834
This commit is contained in:
parent
64152c9180
commit
b9546df5d4
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Blazor.Services
|
|||
protected override void EnsureInitialized() { }
|
||||
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri) { }
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Components.Builder
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Blazor.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
|
|
@ -90,6 +91,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
|||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
||||
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
|
||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||
services.AddSingleton<HttpClient>(s =>
|
||||
{
|
||||
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Interop = Microsoft.AspNetCore.Components.Browser.BrowserUriHelperInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
internal sealed class WebAssemblyNavigationInterception : INavigationInterception
|
||||
{
|
||||
public static readonly WebAssemblyNavigationInterception Instance = new WebAssemblyNavigationInterception();
|
||||
|
||||
public Task EnableNavigationInterceptionAsync()
|
||||
{
|
||||
WebAssemblyJSRuntime.Instance.Invoke<object>(Interop.EnableNavigationInterception);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Blazor.Services
|
|||
protected override void EnsureInitialized()
|
||||
{
|
||||
WebAssemblyJSRuntime.Instance.Invoke<object>(
|
||||
Interop.EnableNavigationInterception,
|
||||
Interop.ListenForNavigationEvents,
|
||||
typeof(WebAssemblyUriHelper).Assembly.GetName().Name,
|
||||
nameof(NotifyLocationChanged));
|
||||
|
||||
|
|
@ -54,10 +54,10 @@ namespace Microsoft.AspNetCore.Blazor.Services
|
|||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri)
|
||||
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink)
|
||||
{
|
||||
Instance.SetAbsoluteUri(newAbsoluteUri);
|
||||
Instance.TriggerOnLocationChanged();
|
||||
Instance.TriggerOnLocationChanged(isInterceptedLink);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -14885,62 +14885,76 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__webpack_require__(/*! @dotnet/jsinterop */ "../node_modules/@dotnet/jsinterop/dist/Microsoft.JSInterop.js");
|
||||
var hasRegisteredEventListeners = false;
|
||||
var hasRegisteredNavigationInterception = false;
|
||||
var hasRegisteredNavigationEventListeners = false;
|
||||
// Will be initialized once someone registers
|
||||
var notifyLocationChangedCallback = null;
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
exports.internalFunctions = {
|
||||
listenForNavigationEvents: listenForNavigationEvents,
|
||||
enableNavigationInterception: enableNavigationInterception,
|
||||
navigateTo: navigateTo,
|
||||
getBaseURI: function () { return document.baseURI; },
|
||||
getLocationHref: function () { return location.href; },
|
||||
};
|
||||
function enableNavigationInterception(assemblyName, functionName) {
|
||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
||||
function listenForNavigationEvents(assemblyName, functionName) {
|
||||
if (hasRegisteredNavigationEventListeners) {
|
||||
return;
|
||||
}
|
||||
notifyLocationChangedCallback = { assemblyName: assemblyName, functionName: functionName };
|
||||
hasRegisteredEventListeners = true;
|
||||
hasRegisteredNavigationEventListeners = true;
|
||||
window.addEventListener('popstate', function () { return notifyLocationChanged(false); });
|
||||
}
|
||||
function enableNavigationInterception() {
|
||||
if (hasRegisteredNavigationInterception) {
|
||||
return;
|
||||
}
|
||||
hasRegisteredNavigationInterception = true;
|
||||
document.addEventListener('click', function (event) {
|
||||
if (event.button !== 0 || eventHasSpecialKey(event)) {
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
return;
|
||||
}
|
||||
// Intercept clicks on all <a> elements where the href is within the <base href> URI space
|
||||
// We must explicitly check if it has an 'href' attribute, because if it doesn't, the result might be null or an empty string depending on the browser
|
||||
var anchorTarget = findClosestAncestor(event.target, 'A');
|
||||
var hrefAttributeName = 'href';
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||
var absoluteHref = toAbsoluteUri(href);
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||
var targetAttributeValue = anchorTarget.getAttribute('target');
|
||||
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
||||
if (!opensInSameFrame) {
|
||||
return;
|
||||
}
|
||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||
var absoluteHref = toAbsoluteUri(href);
|
||||
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||
event.preventDefault();
|
||||
performInternalNavigation(absoluteHref);
|
||||
performInternalNavigation(absoluteHref, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
window.addEventListener('popstate', handleInternalNavigation);
|
||||
}
|
||||
function navigateTo(uri, forceLoad) {
|
||||
var absoluteUri = toAbsoluteUri(uri);
|
||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||
performInternalNavigation(absoluteUri);
|
||||
performInternalNavigation(absoluteUri, false);
|
||||
}
|
||||
else {
|
||||
location.href = uri;
|
||||
}
|
||||
}
|
||||
exports.navigateTo = navigateTo;
|
||||
function performInternalNavigation(absoluteInternalHref) {
|
||||
function performInternalNavigation(absoluteInternalHref, interceptedLink) {
|
||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||
handleInternalNavigation();
|
||||
notifyLocationChanged(interceptedLink);
|
||||
}
|
||||
function handleInternalNavigation() {
|
||||
function notifyLocationChanged(interceptedLink) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
if (!notifyLocationChangedCallback) return [3 /*break*/, 2];
|
||||
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href)];
|
||||
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href, interceptedLink)];
|
||||
case 1:
|
||||
_a.sent();
|
||||
_a.label = 2;
|
||||
|
|
|
|||
|
|
@ -2450,62 +2450,76 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__webpack_require__(/*! @dotnet/jsinterop */ "../node_modules/@dotnet/jsinterop/dist/Microsoft.JSInterop.js");
|
||||
var hasRegisteredEventListeners = false;
|
||||
var hasRegisteredNavigationInterception = false;
|
||||
var hasRegisteredNavigationEventListeners = false;
|
||||
// Will be initialized once someone registers
|
||||
var notifyLocationChangedCallback = null;
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
exports.internalFunctions = {
|
||||
listenForNavigationEvents: listenForNavigationEvents,
|
||||
enableNavigationInterception: enableNavigationInterception,
|
||||
navigateTo: navigateTo,
|
||||
getBaseURI: function () { return document.baseURI; },
|
||||
getLocationHref: function () { return location.href; },
|
||||
};
|
||||
function enableNavigationInterception(assemblyName, functionName) {
|
||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
||||
function listenForNavigationEvents(assemblyName, functionName) {
|
||||
if (hasRegisteredNavigationEventListeners) {
|
||||
return;
|
||||
}
|
||||
notifyLocationChangedCallback = { assemblyName: assemblyName, functionName: functionName };
|
||||
hasRegisteredEventListeners = true;
|
||||
hasRegisteredNavigationEventListeners = true;
|
||||
window.addEventListener('popstate', function () { return notifyLocationChanged(false); });
|
||||
}
|
||||
function enableNavigationInterception() {
|
||||
if (hasRegisteredNavigationInterception) {
|
||||
return;
|
||||
}
|
||||
hasRegisteredNavigationInterception = true;
|
||||
document.addEventListener('click', function (event) {
|
||||
if (event.button !== 0 || eventHasSpecialKey(event)) {
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
return;
|
||||
}
|
||||
// Intercept clicks on all <a> elements where the href is within the <base href> URI space
|
||||
// We must explicitly check if it has an 'href' attribute, because if it doesn't, the result might be null or an empty string depending on the browser
|
||||
var anchorTarget = findClosestAncestor(event.target, 'A');
|
||||
var hrefAttributeName = 'href';
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||
var absoluteHref = toAbsoluteUri(href);
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||
var targetAttributeValue = anchorTarget.getAttribute('target');
|
||||
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
||||
if (!opensInSameFrame) {
|
||||
return;
|
||||
}
|
||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||
var absoluteHref = toAbsoluteUri(href);
|
||||
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||
event.preventDefault();
|
||||
performInternalNavigation(absoluteHref);
|
||||
performInternalNavigation(absoluteHref, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
window.addEventListener('popstate', handleInternalNavigation);
|
||||
}
|
||||
function navigateTo(uri, forceLoad) {
|
||||
var absoluteUri = toAbsoluteUri(uri);
|
||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||
performInternalNavigation(absoluteUri);
|
||||
performInternalNavigation(absoluteUri, false);
|
||||
}
|
||||
else {
|
||||
location.href = uri;
|
||||
}
|
||||
}
|
||||
exports.navigateTo = navigateTo;
|
||||
function performInternalNavigation(absoluteInternalHref) {
|
||||
function performInternalNavigation(absoluteInternalHref, interceptedLink) {
|
||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||
handleInternalNavigation();
|
||||
notifyLocationChanged(interceptedLink);
|
||||
}
|
||||
function handleInternalNavigation() {
|
||||
function notifyLocationChanged(interceptedLink) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
if (!notifyLocationChangedCallback) return [3 /*break*/, 2];
|
||||
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href)];
|
||||
return [4 /*yield*/, DotNet.invokeMethodAsync(notifyLocationChangedCallback.assemblyName, notifyLocationChangedCallback.functionName, location.href, interceptedLink)];
|
||||
case 1:
|
||||
_a.sent();
|
||||
_a.label = 2;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,6 +5,7 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "yarn run build:debug && yarn run build:production",
|
||||
"build:debug": "cd src && webpack --mode development --config ./webpack.config.js",
|
||||
"build:production": "cd src && webpack --mode production --config ./webpack.config.js",
|
||||
"test": "jest"
|
||||
|
|
|
|||
|
|
@ -1,69 +1,88 @@
|
|||
import '@dotnet/jsinterop';
|
||||
|
||||
let hasRegisteredEventListeners = false;
|
||||
let hasRegisteredNavigationInterception = false;
|
||||
let hasRegisteredNavigationEventListeners = false;
|
||||
|
||||
// Will be initialized once someone registers
|
||||
let notifyLocationChangedCallback: { assemblyName: string; functionName: string } | null = null;
|
||||
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
export const internalFunctions = {
|
||||
listenForNavigationEvents,
|
||||
enableNavigationInterception,
|
||||
navigateTo,
|
||||
getBaseURI: () => document.baseURI,
|
||||
getLocationHref: () => location.href,
|
||||
};
|
||||
|
||||
function enableNavigationInterception(assemblyName: string, functionName: string) {
|
||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
||||
function listenForNavigationEvents(assemblyName: string, functionName: string) {
|
||||
if (hasRegisteredNavigationEventListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyLocationChangedCallback = { assemblyName, functionName };
|
||||
hasRegisteredEventListeners = true;
|
||||
|
||||
hasRegisteredNavigationEventListeners = true;
|
||||
window.addEventListener('popstate', () => notifyLocationChanged(false));
|
||||
}
|
||||
|
||||
function enableNavigationInterception() {
|
||||
if (hasRegisteredNavigationInterception) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasRegisteredNavigationInterception = true;
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
if (event.button !== 0 || eventHasSpecialKey(event)) {
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
return;
|
||||
}
|
||||
|
||||
// Intercept clicks on all <a> elements where the href is within the <base href> URI space
|
||||
// We must explicitly check if it has an 'href' attribute, because if it doesn't, the result might be null or an empty string depending on the browser
|
||||
const anchorTarget = findClosestAncestor(event.target as Element | null, 'A') as HTMLAnchorElement;
|
||||
const hrefAttributeName = 'href';
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
||||
const href = anchorTarget.getAttribute(hrefAttributeName)!;
|
||||
const absoluteHref = toAbsoluteUri(href);
|
||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||
const targetAttributeValue = anchorTarget.getAttribute('target');
|
||||
const opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||
if (!opensInSameFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
||||
const href = anchorTarget.getAttribute(hrefAttributeName)!;
|
||||
const absoluteHref = toAbsoluteUri(href);
|
||||
|
||||
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||
event.preventDefault();
|
||||
performInternalNavigation(absoluteHref);
|
||||
performInternalNavigation(absoluteHref, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', handleInternalNavigation);
|
||||
}
|
||||
|
||||
export function navigateTo(uri: string, forceLoad: boolean) {
|
||||
const absoluteUri = toAbsoluteUri(uri);
|
||||
|
||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||
performInternalNavigation(absoluteUri);
|
||||
performInternalNavigation(absoluteUri, false);
|
||||
} else {
|
||||
location.href = uri;
|
||||
}
|
||||
}
|
||||
|
||||
function performInternalNavigation(absoluteInternalHref: string) {
|
||||
function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) {
|
||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||
handleInternalNavigation();
|
||||
notifyLocationChanged(interceptedLink);
|
||||
}
|
||||
|
||||
async function handleInternalNavigation() {
|
||||
async function notifyLocationChanged(interceptedLink: boolean) {
|
||||
if (notifyLocationChangedCallback) {
|
||||
await DotNet.invokeMethodAsync(
|
||||
notifyLocationChangedCallback.assemblyName,
|
||||
notifyLocationChangedCallback.functionName,
|
||||
location.href
|
||||
location.href,
|
||||
interceptedLink
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Components.Browser
|
|||
{
|
||||
private static readonly string Prefix = "Blazor._internal.uriHelper.";
|
||||
|
||||
public static readonly string ListenForNavigationEvents = Prefix + "listenForNavigationEvents";
|
||||
|
||||
public static readonly string EnableNavigationInterception = Prefix + "enableNavigationInterception";
|
||||
|
||||
public static readonly string GetLocationHref = Prefix + "getLocationHref";
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
}
|
||||
public partial interface IUriHelper
|
||||
{
|
||||
event System.EventHandler<string> OnLocationChanged;
|
||||
event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged;
|
||||
string GetAbsoluteUri();
|
||||
string GetBaseUri();
|
||||
void NavigateTo(string uri);
|
||||
|
|
@ -599,7 +599,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IUriHelper
|
||||
{
|
||||
protected UriHelperBase() { }
|
||||
public event System.EventHandler<string> OnLocationChanged { add { } remove { } }
|
||||
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; }
|
||||
|
|
@ -611,7 +611,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
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() { }
|
||||
protected void TriggerOnLocationChanged(bool isinterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Components.Forms
|
||||
|
|
@ -849,6 +849,19 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
}
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
public partial interface INavigationInterception
|
||||
{
|
||||
System.Threading.Tasks.Task EnableNavigationInterceptionAsync();
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct LocationChangedEventArgs
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public LocationChangedEventArgs(string location, bool isNavigationIntercepted) { throw null; }
|
||||
public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public enum NavLinkMatch
|
||||
{
|
||||
Prefix = 0,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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
|
||||
{
|
||||
|
|
@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
/// </summary>
|
||||
event EventHandler<string> OnLocationChanged;
|
||||
event EventHandler<LocationChangedEventArgs> OnLocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URI into an absolute one (by resolving it
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract to setup navigation interception on the client.
|
||||
/// </summary>
|
||||
public interface INavigationInterception
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables navigation interception on the client.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task" /> that represents the asynchronous operation.</returns>
|
||||
Task EnableNavigationInterceptionAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="EventArgs" /> for <see cref="IUriHelper.OnLocationChanged" />.
|
||||
/// </summary>
|
||||
public readonly struct LocationChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="LocationChangedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
/// <param name="isNavigationIntercepted">A value that determines if navigation for the link was intercepted.</param>
|
||||
public LocationChangedEventArgs(string location, bool isNavigationIntercepted)
|
||||
{
|
||||
Location = location;
|
||||
IsNavigationIntercepted = isNavigationIntercepted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the changed location.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if navigation for the link was intercepted.
|
||||
/// </summary>
|
||||
public bool IsNavigationIntercepted { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
// 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 Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Routing
|
||||
{
|
||||
|
|
@ -83,11 +82,11 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
UriHelper.OnLocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object sender, string newUriAbsolute)
|
||||
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
||||
{
|
||||
// We could just re-render always, but for this component we know the
|
||||
// only relevant state change is to the _isActive property.
|
||||
var shouldBeActiveNow = ShouldMatch(newUriAbsolute);
|
||||
var shouldBeActiveNow = ShouldMatch(args.Location);
|
||||
if (shouldBeActiveNow != _isActive)
|
||||
{
|
||||
_isActive = shouldBeActiveNow;
|
||||
|
|
|
|||
|
|
@ -14,16 +14,21 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
/// A component that displays whichever other component corresponds to the
|
||||
/// current navigation location.
|
||||
/// </summary>
|
||||
public class Router : IComponent, IDisposable
|
||||
public class Router : IComponent, IHandleAfterRender, IDisposable
|
||||
{
|
||||
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
||||
|
||||
RenderHandle _renderHandle;
|
||||
string _baseUri;
|
||||
string _locationAbsolute;
|
||||
bool _navigationInterceptionEnabled;
|
||||
|
||||
[Inject] private IUriHelper UriHelper { get; set; }
|
||||
|
||||
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
||||
|
||||
[Inject] private IComponentContext ComponentContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assembly that should be searched, along with its referenced
|
||||
/// assemblies, for components matching the URI.
|
||||
|
|
@ -33,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
/// <summary>
|
||||
/// Gets or sets the type of the component that should be used as a fallback when no match is found for the requested route.
|
||||
/// </summary>
|
||||
[Parameter] public Type FallbackComponent { get; private set; }
|
||||
[Parameter] public RenderFragment NotFoundContent { get; private set; }
|
||||
|
||||
private RouteTable Routes { get; set; }
|
||||
|
||||
|
|
@ -52,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
parameters.SetParameterProperties(this);
|
||||
var types = ComponentResolver.ResolveComponents(AppAssembly);
|
||||
Routes = RouteTable.Create(types);
|
||||
Refresh();
|
||||
Refresh(isNavigationIntercepted: false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -79,41 +84,57 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
private void Refresh(bool isNavigationIntercepted)
|
||||
{
|
||||
var locationPath = UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute);
|
||||
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
||||
var context = new RouteContext(locationPath);
|
||||
Routes.Route(context);
|
||||
|
||||
if (context.Handler == null)
|
||||
if (context.Handler != null)
|
||||
{
|
||||
if (FallbackComponent != null)
|
||||
if (!typeof(IComponent).IsAssignableFrom(context.Handler))
|
||||
{
|
||||
context.Handler = FallbackComponent;
|
||||
throw new InvalidOperationException($"The type {context.Handler.FullName} " +
|
||||
$"does not implement {typeof(IComponent).FullName}.");
|
||||
}
|
||||
|
||||
_renderHandle.Render(builder => Render(builder, context.Handler, context.Parameters));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isNavigationIntercepted && NotFoundContent != null)
|
||||
{
|
||||
// We did not find a Component that matches the route.
|
||||
// Only show the NotFoundContent if the application developer programatically got us here i.e we did not
|
||||
// intercept the navigation. In all other cases, force a browser navigation since this could be non-Blazor content.
|
||||
_renderHandle.Render(NotFoundContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with a route for '/{locationPath}', and no fallback is defined.");
|
||||
UriHelper.NavigateTo(_locationAbsolute, forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeof(IComponent).IsAssignableFrom(context.Handler))
|
||||
{
|
||||
throw new InvalidOperationException($"The type {context.Handler.FullName} " +
|
||||
$"does not implement {typeof(IComponent).FullName}.");
|
||||
}
|
||||
|
||||
_renderHandle.Render(builder => Render(builder, context.Handler, context.Parameters));
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object sender, string newAbsoluteUri)
|
||||
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
||||
{
|
||||
_locationAbsolute = newAbsoluteUri;
|
||||
if (_renderHandle.IsInitialized)
|
||||
_locationAbsolute = args.Location;
|
||||
if (_renderHandle.IsInitialized && Routes != null)
|
||||
{
|
||||
Refresh();
|
||||
Refresh(args.IsNavigationIntercepted);
|
||||
}
|
||||
}
|
||||
|
||||
Task IHandleAfterRender.OnAfterRenderAsync()
|
||||
{
|
||||
if (!_navigationInterceptionEnabled && ComponentContext.IsConnected)
|
||||
{
|
||||
_navigationInterceptionEnabled = true;
|
||||
return NavigationInterception.EnableNavigationInterceptionAsync();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 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
|
||||
{
|
||||
|
|
@ -10,12 +11,12 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
public abstract class UriHelperBase : IUriHelper
|
||||
{
|
||||
private EventHandler<string> _onLocationChanged;
|
||||
private EventHandler<LocationChangedEventArgs> _onLocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<string> OnLocationChanged
|
||||
public event EventHandler<LocationChangedEventArgs> OnLocationChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
|
|
@ -205,9 +206,9 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// <summary>
|
||||
/// Triggers the <see cref="OnLocationChanged"/> event with the current URI value.
|
||||
/// </summary>
|
||||
protected void TriggerOnLocationChanged()
|
||||
protected void TriggerOnLocationChanged(bool isinterceptedLink)
|
||||
{
|
||||
_onLocationChanged?.Invoke(this, _uri);
|
||||
_onLocationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isinterceptedLink));
|
||||
}
|
||||
|
||||
private void AssertInitialized()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
|
||||
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
||||
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
||||
"test\\testassets\\TestServer\\TestServer.csproj"
|
||||
"test\\testassets\\TestServer\\Components.TestServer.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
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) { }
|
||||
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink) { }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Components.Browser;
|
||||
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.JSInterop;
|
||||
|
|
@ -120,6 +121,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
uriHelper.AttachJsRuntime(JSRuntime);
|
||||
}
|
||||
|
||||
var navigationInterception = (RemoteNavigationInterception)Services.GetRequiredService<INavigationInterception>();
|
||||
if (!navigationInterception.HasAttachedJSRuntime)
|
||||
{
|
||||
navigationInterception.AttachJSRuntime(JSRuntime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Text.Encodings.Web;
|
|||
using Microsoft.AspNetCore.Components.Browser;
|
||||
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -49,12 +50,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
||||
|
||||
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
||||
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
||||
if (client.Connected)
|
||||
{
|
||||
uriHelper.AttachJsRuntime(jsRuntime);
|
||||
uriHelper.InitializeState(
|
||||
uriAbsolute,
|
||||
baseUriAbsolute);
|
||||
|
||||
navigationInterception.AttachJSRuntime(jsRuntime);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.JSInterop;
|
||||
using Interop = Microsoft.AspNetCore.Components.Browser.BrowserUriHelperInterop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
internal sealed class RemoteNavigationInterception : INavigationInterception
|
||||
{
|
||||
private IJSRuntime _jsRuntime;
|
||||
|
||||
public void AttachJSRuntime(IJSRuntime jsRuntime)
|
||||
{
|
||||
if (HasAttachedJSRuntime)
|
||||
{
|
||||
throw new InvalidOperationException("JSRuntime has already been initialized.");
|
||||
}
|
||||
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public bool HasAttachedJSRuntime => _jsRuntime != null;
|
||||
|
||||
public async Task EnableNavigationInterceptionAsync()
|
||||
{
|
||||
if (!HasAttachedJSRuntime)
|
||||
{
|
||||
// We should generally never get here in the ordinary case. Router will only call this API once pre-rendering is complete.
|
||||
// This would guard any unusual usage of this API.
|
||||
throw new InvalidOperationException("Navigation commands can not be issued at this time. This is because the component is being " +
|
||||
"prerendered and the page has not yet loaded in the browser or because the circuit is currently disconnected. " +
|
||||
"Components must wrap any navigation calls in conditional logic to ensure those navigation calls are not " +
|
||||
"attempted during prerendering or while the client is disconnected.");
|
||||
}
|
||||
|
||||
await _jsRuntime.InvokeAsync<object>(Interop.EnableNavigationInterception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
/// </summary>
|
||||
public class RemoteUriHelper : UriHelperBase
|
||||
{
|
||||
private IJSRuntime _jsRuntime;
|
||||
private readonly ILogger<RemoteUriHelper> _logger;
|
||||
private IJSRuntime _jsRuntime;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RemoteUriHelper"/> instance.
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public override void InitializeState(string uriAbsolute, string baseUriAbsolute)
|
||||
{
|
||||
base.InitializeState(uriAbsolute, baseUriAbsolute);
|
||||
TriggerOnLocationChanged();
|
||||
TriggerOnLocationChanged(isinterceptedLink: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -52,11 +52,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
throw new InvalidOperationException("JavaScript runtime already initialized.");
|
||||
}
|
||||
|
||||
_jsRuntime = jsRuntime;
|
||||
|
||||
_jsRuntime.InvokeAsync<object>(
|
||||
Interop.EnableNavigationInterception,
|
||||
typeof(RemoteUriHelper).Assembly.GetName().Name,
|
||||
nameof(NotifyLocationChanged));
|
||||
Interop.ListenForNavigationEvents,
|
||||
typeof(RemoteUriHelper).Assembly.GetName().Name,
|
||||
nameof(NotifyLocationChanged));
|
||||
|
||||
_logger.LogDebug($"{nameof(RemoteUriHelper)} initialized.");
|
||||
}
|
||||
|
|
@ -65,7 +67,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
/// For framework use only.
|
||||
/// </summary>
|
||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||
public static void NotifyLocationChanged(string uriAbsolute)
|
||||
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink)
|
||||
{
|
||||
var circuit = CircuitHost.Current;
|
||||
if (circuit == null)
|
||||
|
|
@ -73,19 +75,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
var message = $"{nameof(NotifyLocationChanged)} called without a circuit.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
var uriHelper = (RemoteUriHelper)circuit.Services.GetRequiredService<IUriHelper>();
|
||||
|
||||
uriHelper.SetAbsoluteUri(uriAbsolute);
|
||||
|
||||
uriHelper._logger.LogDebug($"Location changed to '{uriAbsolute}'.");
|
||||
uriHelper.TriggerOnLocationChanged();
|
||||
uriHelper.TriggerOnLocationChanged(isInterceptedLink);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||
{
|
||||
_logger.LogDebug($"Log debug {uri} force load {forceLoad}.");
|
||||
_logger.LogDebug($"{uri} force load {forceLoad}.");
|
||||
|
||||
if (_jsRuntime == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Components.Server.BlazorPack;
|
||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||
|
|
@ -66,6 +67,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// These intentionally replace the non-interactive versions included in MVC.
|
||||
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||
services.AddScoped<AuthenticationStateProvider, FixedAuthenticationStateProvider>();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
|
|
@ -206,6 +204,5 @@ window.Blazor._internal.forceCloseConnection();");
|
|||
Browser.True(() => Browser.Manage().Logs.GetLog(LogType.Browser)
|
||||
.Any(l => l.Level == LogLevel.Info && l.Message.Contains("Connection disconnected.")));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,6 +291,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
AssertHighlightedLinks("Default with hash");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToNotAComponent()
|
||||
{
|
||||
SetUrlViaPushState("/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Not a component")).Click();
|
||||
Browser.Equal("Not a component!", () => Browser.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanNavigateProgrammatically()
|
||||
{
|
||||
|
|
@ -339,12 +349,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||
}
|
||||
|
||||
private void SetUrlViaPushState(string relativeUri)
|
||||
[Fact]
|
||||
public void UsingUriHelperWithoutRouterWorks()
|
||||
{
|
||||
var app = MountTestComponent<UriHelperComponent>();
|
||||
var initialUrl = Browser.Url;
|
||||
|
||||
Browser.Equal(Browser.Url, () => app.FindElement(By.Id("test-info")).Text);
|
||||
var uri = SetUrlViaPushState("/mytestpath");
|
||||
Browser.Equal(uri, () => app.FindElement(By.Id("test-info")).Text);
|
||||
|
||||
var jsExecutor = (IJavaScriptExecutor)Browser;
|
||||
jsExecutor.ExecuteScript("history.back()");
|
||||
|
||||
Browser.Equal(initialUrl, () => app.FindElement(By.Id("test-info")).Text);
|
||||
}
|
||||
|
||||
private string SetUrlViaPushState(string relativeUri)
|
||||
{
|
||||
var pathBaseWithoutHash = ServerPathBase.Split('#')[0];
|
||||
var jsExecutor = (IJavaScriptExecutor)Browser;
|
||||
var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}");
|
||||
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
|
||||
|
||||
return absoluteUri.AbsoluteUri;
|
||||
}
|
||||
|
||||
private void AssertHighlightedLinks(params string[] linkTexts)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
||||
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
||||
<option value="BasicTestApp.RouterTest.TestRouterWithoutNotFoundContent">Router without NotFoundContent</option>
|
||||
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>
|
||||
<option value="BasicTestApp.HtmlMixedChildContent">ChildContent Mixed Block</option>
|
||||
<option value="BasicTestApp.HtmlEncodedChildContent">ChildContent HTML Encoded Block</option>
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
||||
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
||||
</select>
|
||||
|
||||
@if (SelectedComponentType != null)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<div id="test-info">Oops, that component wasn't found!</div>
|
||||
|
|
@ -27,3 +27,6 @@
|
|||
</a>
|
||||
|
||||
<a href="/" target="_blank">Target (_blank)</a>
|
||||
|
||||
<a href="/subdir/NotAComponent.html">Not a component</a>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly FallbackComponent="typeof(Error404)" />
|
||||
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly>
|
||||
<NotFoundContent>
|
||||
<div id="test-info">Oops, that component wasn't found!</div>
|
||||
</NotFoundContent>
|
||||
</Router>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
@inject IUriHelper UriHelper
|
||||
@inject Microsoft.JSInterop.IJSRuntime JSRuntime
|
||||
|
||||
<button onclick="@Navigate">Navigate</button>
|
||||
|
||||
<span id="test-info">@UrlLocation</span>
|
||||
|
||||
@functions{
|
||||
string UrlLocation;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
base.OnInit();
|
||||
UrlLocation = UriHelper.GetAbsoluteUri();
|
||||
UriHelper.OnLocationChanged += (_, __) =>
|
||||
{
|
||||
UrlLocation = UriHelper.GetAbsoluteUri();
|
||||
StateHasChanged();
|
||||
};
|
||||
}
|
||||
|
||||
async Task Navigate()
|
||||
{
|
||||
await JSRuntime.InvokeAsync<object>("uriHelperNavigate");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
<div id="test-info">Not a component!</div>
|
||||
|
|
@ -19,6 +19,10 @@
|
|||
return element.value;
|
||||
}
|
||||
|
||||
function uriHelperNavigate() {
|
||||
Blazor.navigateTo('/subdir/some-path');
|
||||
}
|
||||
|
||||
(function () {
|
||||
// Load either blazor.webassembly.js or blazor.server.js depending
|
||||
// on the hash part of the URL. This is just to give a way for the
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@
|
|||
<span class="oi oi-list-rich" aria-hidden="true"></span> Error
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="links">
|
||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Links
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@page
|
||||
@page
|
||||
@using ComponentsApp.App
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -12,8 +12,7 @@
|
|||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<app>@(await Html.RenderComponentAsync<App>(new { Name="Guest" }))</app>
|
||||
<app>@(await Html.RenderComponentAsync<App>(new { Name = "Guest" }))</app>
|
||||
|
||||
<script src="_framework/blazor.server.js" autostart="false"></script>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using BasicTestApp;
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
|
|
@ -207,6 +208,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddScoped<StaticComponentRenderer>();
|
||||
services.TryAddScoped<IUriHelper, HttpUriHelper>();
|
||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
services.TryAddScoped<IComponentContext, UnsupportedComponentContext>();
|
||||
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
||||
|
||||
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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 Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal class UnsupportedComponentContext : IComponentContext
|
||||
{
|
||||
public bool IsConnected => false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal sealed class UnsupportedNavigationInterception : INavigationInterception
|
||||
{
|
||||
public Task EnableNavigationInterceptionAsync()
|
||||
{
|
||||
throw new InvalidOperationException("Navigation interception calls cannot be issued during server-side prerendering, because the page has not yet loaded in the browser. " +
|
||||
"Prerendered components must wrap any navigation interception calls in conditional logic to ensure those interop calls are not attempted during prerendering.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("\n<p>Hello world!</p>", "Greetings", content);
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("\n<p>Hello world!</p>", "Greetings", content);
|
||||
|
|
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components/routable");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("\nRouter component\n<p>Routed successfully</p>", "Routing", content);
|
||||
|
|
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components/routable");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("\nRouter component\n<p>Routed successfully</p>", "Routing", content);
|
||||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components/false");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("<p>Hello world!</p>", "Greetings", content, unwrap: true);
|
||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components/routable/false");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent("Router component\n<p>Routed successfully</p>", "Routing", content, unwrap: true);
|
||||
|
|
@ -194,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await client.GetAsync("http://localhost/components");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
AssertComponent(expectedHtml, "FetchData", content);
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<p>Route not found</p>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
@using Microsoft.AspNetCore.Components.Routing
|
||||
Router component
|
||||
<Router
|
||||
AppAssembly="System.Reflection.Assembly.GetAssembly(typeof(RouterContainer))"
|
||||
FallbackComponent="typeof(Fallback)">
|
||||
<Router AppAssembly="System.Reflection.Assembly.GetAssembly(typeof(RouterContainer))">
|
||||
<NotFoundContent>
|
||||
<p>Route not found</p>
|
||||
</NotFoundContent>
|
||||
</Router>
|
||||
|
|
|
|||
Loading…
Reference in New Issue