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 EnsureInitialized() { }
|
||||||
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
||||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||||
public static void NotifyLocationChanged(string newAbsoluteUri) { }
|
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Components.Builder
|
namespace Microsoft.AspNetCore.Components.Builder
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Microsoft.AspNetCore.Blazor.Services;
|
using Microsoft.AspNetCore.Blazor.Services;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
|
@ -90,6 +91,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||||
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
||||||
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
|
services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
|
||||||
|
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||||
services.AddSingleton<HttpClient>(s =>
|
services.AddSingleton<HttpClient>(s =>
|
||||||
{
|
{
|
||||||
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
|
// 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()
|
protected override void EnsureInitialized()
|
||||||
{
|
{
|
||||||
WebAssemblyJSRuntime.Instance.Invoke<object>(
|
WebAssemblyJSRuntime.Instance.Invoke<object>(
|
||||||
Interop.EnableNavigationInterception,
|
Interop.ListenForNavigationEvents,
|
||||||
typeof(WebAssemblyUriHelper).Assembly.GetName().Name,
|
typeof(WebAssemblyUriHelper).Assembly.GetName().Name,
|
||||||
nameof(NotifyLocationChanged));
|
nameof(NotifyLocationChanged));
|
||||||
|
|
||||||
|
|
@ -54,10 +54,10 @@ namespace Microsoft.AspNetCore.Blazor.Services
|
||||||
/// For framework use only.
|
/// For framework use only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||||
public static void NotifyLocationChanged(string newAbsoluteUri)
|
public static void NotifyLocationChanged(string newAbsoluteUri, bool isInterceptedLink)
|
||||||
{
|
{
|
||||||
Instance.SetAbsoluteUri(newAbsoluteUri);
|
Instance.SetAbsoluteUri(newAbsoluteUri);
|
||||||
Instance.TriggerOnLocationChanged();
|
Instance.TriggerOnLocationChanged(isInterceptedLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -14885,62 +14885,76 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
__webpack_require__(/*! @dotnet/jsinterop */ "../node_modules/@dotnet/jsinterop/dist/Microsoft.JSInterop.js");
|
__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
|
// Will be initialized once someone registers
|
||||||
var notifyLocationChangedCallback = null;
|
var notifyLocationChangedCallback = null;
|
||||||
// These are the functions we're making available for invocation from .NET
|
// These are the functions we're making available for invocation from .NET
|
||||||
exports.internalFunctions = {
|
exports.internalFunctions = {
|
||||||
|
listenForNavigationEvents: listenForNavigationEvents,
|
||||||
enableNavigationInterception: enableNavigationInterception,
|
enableNavigationInterception: enableNavigationInterception,
|
||||||
navigateTo: navigateTo,
|
navigateTo: navigateTo,
|
||||||
getBaseURI: function () { return document.baseURI; },
|
getBaseURI: function () { return document.baseURI; },
|
||||||
getLocationHref: function () { return location.href; },
|
getLocationHref: function () { return location.href; },
|
||||||
};
|
};
|
||||||
function enableNavigationInterception(assemblyName, functionName) {
|
function listenForNavigationEvents(assemblyName, functionName) {
|
||||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
if (hasRegisteredNavigationEventListeners) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notifyLocationChangedCallback = { assemblyName: assemblyName, functionName: functionName };
|
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) {
|
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
|
// 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
|
// 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 anchorTarget = findClosestAncestor(event.target, 'A');
|
||||||
var hrefAttributeName = 'href';
|
var hrefAttributeName = 'href';
|
||||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
|
||||||
var absoluteHref = toAbsoluteUri(href);
|
|
||||||
var targetAttributeValue = anchorTarget.getAttribute('target');
|
var targetAttributeValue = anchorTarget.getAttribute('target');
|
||||||
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
if (!opensInSameFrame) {
|
||||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
return;
|
||||||
|
}
|
||||||
|
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||||
|
var absoluteHref = toAbsoluteUri(href);
|
||||||
|
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
performInternalNavigation(absoluteHref);
|
performInternalNavigation(absoluteHref, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener('popstate', handleInternalNavigation);
|
|
||||||
}
|
}
|
||||||
function navigateTo(uri, forceLoad) {
|
function navigateTo(uri, forceLoad) {
|
||||||
var absoluteUri = toAbsoluteUri(uri);
|
var absoluteUri = toAbsoluteUri(uri);
|
||||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||||
performInternalNavigation(absoluteUri);
|
performInternalNavigation(absoluteUri, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
location.href = uri;
|
location.href = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.navigateTo = navigateTo;
|
exports.navigateTo = navigateTo;
|
||||||
function performInternalNavigation(absoluteInternalHref) {
|
function performInternalNavigation(absoluteInternalHref, interceptedLink) {
|
||||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||||
handleInternalNavigation();
|
notifyLocationChanged(interceptedLink);
|
||||||
}
|
}
|
||||||
function handleInternalNavigation() {
|
function notifyLocationChanged(interceptedLink) {
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
return __generator(this, function (_a) {
|
return __generator(this, function (_a) {
|
||||||
switch (_a.label) {
|
switch (_a.label) {
|
||||||
case 0:
|
case 0:
|
||||||
if (!notifyLocationChangedCallback) return [3 /*break*/, 2];
|
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:
|
case 1:
|
||||||
_a.sent();
|
_a.sent();
|
||||||
_a.label = 2;
|
_a.label = 2;
|
||||||
|
|
|
||||||
|
|
@ -2450,62 +2450,76 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
__webpack_require__(/*! @dotnet/jsinterop */ "../node_modules/@dotnet/jsinterop/dist/Microsoft.JSInterop.js");
|
__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
|
// Will be initialized once someone registers
|
||||||
var notifyLocationChangedCallback = null;
|
var notifyLocationChangedCallback = null;
|
||||||
// These are the functions we're making available for invocation from .NET
|
// These are the functions we're making available for invocation from .NET
|
||||||
exports.internalFunctions = {
|
exports.internalFunctions = {
|
||||||
|
listenForNavigationEvents: listenForNavigationEvents,
|
||||||
enableNavigationInterception: enableNavigationInterception,
|
enableNavigationInterception: enableNavigationInterception,
|
||||||
navigateTo: navigateTo,
|
navigateTo: navigateTo,
|
||||||
getBaseURI: function () { return document.baseURI; },
|
getBaseURI: function () { return document.baseURI; },
|
||||||
getLocationHref: function () { return location.href; },
|
getLocationHref: function () { return location.href; },
|
||||||
};
|
};
|
||||||
function enableNavigationInterception(assemblyName, functionName) {
|
function listenForNavigationEvents(assemblyName, functionName) {
|
||||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
if (hasRegisteredNavigationEventListeners) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notifyLocationChangedCallback = { assemblyName: assemblyName, functionName: functionName };
|
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) {
|
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
|
// 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
|
// 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 anchorTarget = findClosestAncestor(event.target, 'A');
|
||||||
var hrefAttributeName = 'href';
|
var hrefAttributeName = 'href';
|
||||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||||
var href = anchorTarget.getAttribute(hrefAttributeName);
|
|
||||||
var absoluteHref = toAbsoluteUri(href);
|
|
||||||
var targetAttributeValue = anchorTarget.getAttribute('target');
|
var targetAttributeValue = anchorTarget.getAttribute('target');
|
||||||
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
var opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
if (!opensInSameFrame) {
|
||||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
return;
|
||||||
|
}
|
||||||
|
var href = anchorTarget.getAttribute(hrefAttributeName);
|
||||||
|
var absoluteHref = toAbsoluteUri(href);
|
||||||
|
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
performInternalNavigation(absoluteHref);
|
performInternalNavigation(absoluteHref, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener('popstate', handleInternalNavigation);
|
|
||||||
}
|
}
|
||||||
function navigateTo(uri, forceLoad) {
|
function navigateTo(uri, forceLoad) {
|
||||||
var absoluteUri = toAbsoluteUri(uri);
|
var absoluteUri = toAbsoluteUri(uri);
|
||||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||||
performInternalNavigation(absoluteUri);
|
performInternalNavigation(absoluteUri, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
location.href = uri;
|
location.href = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.navigateTo = navigateTo;
|
exports.navigateTo = navigateTo;
|
||||||
function performInternalNavigation(absoluteInternalHref) {
|
function performInternalNavigation(absoluteInternalHref, interceptedLink) {
|
||||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||||
handleInternalNavigation();
|
notifyLocationChanged(interceptedLink);
|
||||||
}
|
}
|
||||||
function handleInternalNavigation() {
|
function notifyLocationChanged(interceptedLink) {
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
return __generator(this, function (_a) {
|
return __generator(this, function (_a) {
|
||||||
switch (_a.label) {
|
switch (_a.label) {
|
||||||
case 0:
|
case 0:
|
||||||
if (!notifyLocationChangedCallback) return [3 /*break*/, 2];
|
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:
|
case 1:
|
||||||
_a.sent();
|
_a.sent();
|
||||||
_a.label = 2;
|
_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": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "yarn run build:debug && yarn run build:production",
|
||||||
"build:debug": "cd src && webpack --mode development --config ./webpack.config.js",
|
"build:debug": "cd src && webpack --mode development --config ./webpack.config.js",
|
||||||
"build:production": "cd src && webpack --mode production --config ./webpack.config.js",
|
"build:production": "cd src && webpack --mode production --config ./webpack.config.js",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,88 @@
|
||||||
import '@dotnet/jsinterop';
|
import '@dotnet/jsinterop';
|
||||||
|
|
||||||
let hasRegisteredEventListeners = false;
|
let hasRegisteredNavigationInterception = false;
|
||||||
|
let hasRegisteredNavigationEventListeners = false;
|
||||||
|
|
||||||
// Will be initialized once someone registers
|
// Will be initialized once someone registers
|
||||||
let notifyLocationChangedCallback: { assemblyName: string; functionName: string } | null = null;
|
let notifyLocationChangedCallback: { assemblyName: string; functionName: string } | null = null;
|
||||||
|
|
||||||
// These are the functions we're making available for invocation from .NET
|
// These are the functions we're making available for invocation from .NET
|
||||||
export const internalFunctions = {
|
export const internalFunctions = {
|
||||||
|
listenForNavigationEvents,
|
||||||
enableNavigationInterception,
|
enableNavigationInterception,
|
||||||
navigateTo,
|
navigateTo,
|
||||||
getBaseURI: () => document.baseURI,
|
getBaseURI: () => document.baseURI,
|
||||||
getLocationHref: () => location.href,
|
getLocationHref: () => location.href,
|
||||||
};
|
};
|
||||||
|
|
||||||
function enableNavigationInterception(assemblyName: string, functionName: string) {
|
function listenForNavigationEvents(assemblyName: string, functionName: string) {
|
||||||
if (hasRegisteredEventListeners || assemblyName === undefined || functionName === undefined) {
|
if (hasRegisteredNavigationEventListeners) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyLocationChangedCallback = { assemblyName, functionName };
|
notifyLocationChangedCallback = { assemblyName, functionName };
|
||||||
hasRegisteredEventListeners = true;
|
|
||||||
|
hasRegisteredNavigationEventListeners = true;
|
||||||
|
window.addEventListener('popstate', () => notifyLocationChanged(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableNavigationInterception() {
|
||||||
|
if (hasRegisteredNavigationInterception) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRegisteredNavigationInterception = true;
|
||||||
|
|
||||||
document.addEventListener('click', event => {
|
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
|
// 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
|
// 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 anchorTarget = findClosestAncestor(event.target as Element | null, 'A') as HTMLAnchorElement;
|
||||||
const hrefAttributeName = 'href';
|
const hrefAttributeName = 'href';
|
||||||
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName) && event.button === 0) {
|
if (anchorTarget && anchorTarget.hasAttribute(hrefAttributeName)) {
|
||||||
const href = anchorTarget.getAttribute(hrefAttributeName)!;
|
|
||||||
const absoluteHref = toAbsoluteUri(href);
|
|
||||||
const targetAttributeValue = anchorTarget.getAttribute('target');
|
const targetAttributeValue = anchorTarget.getAttribute('target');
|
||||||
const opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
const opensInSameFrame = !targetAttributeValue || targetAttributeValue === '_self';
|
||||||
|
if (!opensInSameFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't stop ctrl/meta-click (etc) from opening links in new tabs/windows
|
const href = anchorTarget.getAttribute(hrefAttributeName)!;
|
||||||
if (isWithinBaseUriSpace(absoluteHref) && !eventHasSpecialKey(event) && opensInSameFrame) {
|
const absoluteHref = toAbsoluteUri(href);
|
||||||
|
|
||||||
|
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
performInternalNavigation(absoluteHref);
|
performInternalNavigation(absoluteHref, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('popstate', handleInternalNavigation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function navigateTo(uri: string, forceLoad: boolean) {
|
export function navigateTo(uri: string, forceLoad: boolean) {
|
||||||
const absoluteUri = toAbsoluteUri(uri);
|
const absoluteUri = toAbsoluteUri(uri);
|
||||||
|
|
||||||
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
|
||||||
performInternalNavigation(absoluteUri);
|
performInternalNavigation(absoluteUri, false);
|
||||||
} else {
|
} else {
|
||||||
location.href = uri;
|
location.href = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function performInternalNavigation(absoluteInternalHref: string) {
|
function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) {
|
||||||
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||||
handleInternalNavigation();
|
notifyLocationChanged(interceptedLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleInternalNavigation() {
|
async function notifyLocationChanged(interceptedLink: boolean) {
|
||||||
if (notifyLocationChangedCallback) {
|
if (notifyLocationChangedCallback) {
|
||||||
await DotNet.invokeMethodAsync(
|
await DotNet.invokeMethodAsync(
|
||||||
notifyLocationChangedCallback.assemblyName,
|
notifyLocationChangedCallback.assemblyName,
|
||||||
notifyLocationChangedCallback.functionName,
|
notifyLocationChangedCallback.functionName,
|
||||||
location.href
|
location.href,
|
||||||
|
interceptedLink
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Components.Browser
|
||||||
{
|
{
|
||||||
private static readonly string Prefix = "Blazor._internal.uriHelper.";
|
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 EnableNavigationInterception = Prefix + "enableNavigationInterception";
|
||||||
|
|
||||||
public static readonly string GetLocationHref = Prefix + "getLocationHref";
|
public static readonly string GetLocationHref = Prefix + "getLocationHref";
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
}
|
}
|
||||||
public partial interface IUriHelper
|
public partial interface IUriHelper
|
||||||
{
|
{
|
||||||
event System.EventHandler<string> OnLocationChanged;
|
event System.EventHandler<Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs> OnLocationChanged;
|
||||||
string GetAbsoluteUri();
|
string GetAbsoluteUri();
|
||||||
string GetBaseUri();
|
string GetBaseUri();
|
||||||
void NavigateTo(string uri);
|
void NavigateTo(string uri);
|
||||||
|
|
@ -599,7 +599,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IUriHelper
|
public abstract partial class UriHelperBase : Microsoft.AspNetCore.Components.IUriHelper
|
||||||
{
|
{
|
||||||
protected UriHelperBase() { }
|
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() { }
|
protected virtual void EnsureInitialized() { }
|
||||||
public string GetAbsoluteUri() { throw null; }
|
public string GetAbsoluteUri() { throw null; }
|
||||||
public virtual string GetBaseUri() { throw null; }
|
public virtual string GetBaseUri() { throw null; }
|
||||||
|
|
@ -611,7 +611,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
protected void SetAbsoluteUri(string uri) { }
|
protected void SetAbsoluteUri(string uri) { }
|
||||||
public System.Uri ToAbsoluteUri(string href) { throw null; }
|
public System.Uri ToAbsoluteUri(string href) { throw null; }
|
||||||
public string ToBaseRelativePath(string baseUri, string locationAbsolute) { throw null; }
|
public string ToBaseRelativePath(string baseUri, string locationAbsolute) { throw null; }
|
||||||
protected void TriggerOnLocationChanged() { }
|
protected void TriggerOnLocationChanged(bool isinterceptedLink) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Components.Forms
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
|
@ -849,6 +849,19 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Components.Routing
|
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
|
public enum NavLinkMatch
|
||||||
{
|
{
|
||||||
Prefix = 0,
|
Prefix = 0,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that fires when the navigation location has changed.
|
/// An event that fires when the navigation location has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<string> OnLocationChanged;
|
event EventHandler<LocationChangedEventArgs> OnLocationChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a relative URI into an absolute one (by resolving it
|
/// 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>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<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.
|
// 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.
|
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Routing
|
namespace Microsoft.AspNetCore.Components.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -83,11 +82,11 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
UriHelper.OnLocationChanged -= OnLocationChanged;
|
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
|
// We could just re-render always, but for this component we know the
|
||||||
// only relevant state change is to the _isActive property.
|
// only relevant state change is to the _isActive property.
|
||||||
var shouldBeActiveNow = ShouldMatch(newUriAbsolute);
|
var shouldBeActiveNow = ShouldMatch(args.Location);
|
||||||
if (shouldBeActiveNow != _isActive)
|
if (shouldBeActiveNow != _isActive)
|
||||||
{
|
{
|
||||||
_isActive = shouldBeActiveNow;
|
_isActive = shouldBeActiveNow;
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,21 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// A component that displays whichever other component corresponds to the
|
/// A component that displays whichever other component corresponds to the
|
||||||
/// current navigation location.
|
/// current navigation location.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Router : IComponent, IDisposable
|
public class Router : IComponent, IHandleAfterRender, IDisposable
|
||||||
{
|
{
|
||||||
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
||||||
|
|
||||||
RenderHandle _renderHandle;
|
RenderHandle _renderHandle;
|
||||||
string _baseUri;
|
string _baseUri;
|
||||||
string _locationAbsolute;
|
string _locationAbsolute;
|
||||||
|
bool _navigationInterceptionEnabled;
|
||||||
|
|
||||||
[Inject] private IUriHelper UriHelper { get; set; }
|
[Inject] private IUriHelper UriHelper { get; set; }
|
||||||
|
|
||||||
|
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
||||||
|
|
||||||
|
[Inject] private IComponentContext ComponentContext { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the assembly that should be searched, along with its referenced
|
/// Gets or sets the assembly that should be searched, along with its referenced
|
||||||
/// assemblies, for components matching the URI.
|
/// assemblies, for components matching the URI.
|
||||||
|
|
@ -33,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
[Parameter] public Type FallbackComponent { get; private set; }
|
[Parameter] public RenderFragment NotFoundContent { get; private set; }
|
||||||
|
|
||||||
private RouteTable Routes { get; set; }
|
private RouteTable Routes { get; set; }
|
||||||
|
|
||||||
|
|
@ -52,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
parameters.SetParameterProperties(this);
|
parameters.SetParameterProperties(this);
|
||||||
var types = ComponentResolver.ResolveComponents(AppAssembly);
|
var types = ComponentResolver.ResolveComponents(AppAssembly);
|
||||||
Routes = RouteTable.Create(types);
|
Routes = RouteTable.Create(types);
|
||||||
Refresh();
|
Refresh(isNavigationIntercepted: false);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,41 +84,57 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refresh()
|
private void Refresh(bool isNavigationIntercepted)
|
||||||
{
|
{
|
||||||
var locationPath = UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute);
|
var locationPath = UriHelper.ToBaseRelativePath(_baseUri, _locationAbsolute);
|
||||||
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);
|
||||||
var context = new RouteContext(locationPath);
|
var context = new RouteContext(locationPath);
|
||||||
Routes.Route(context);
|
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
|
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;
|
_locationAbsolute = args.Location;
|
||||||
if (_renderHandle.IsInitialized)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
{
|
{
|
||||||
|
|
@ -10,12 +11,12 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class UriHelperBase : IUriHelper
|
public abstract class UriHelperBase : IUriHelper
|
||||||
{
|
{
|
||||||
private EventHandler<string> _onLocationChanged;
|
private EventHandler<LocationChangedEventArgs> _onLocationChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that fires when the navigation location has changed.
|
/// An event that fires when the navigation location has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<string> OnLocationChanged
|
public event EventHandler<LocationChangedEventArgs> OnLocationChanged
|
||||||
{
|
{
|
||||||
add
|
add
|
||||||
{
|
{
|
||||||
|
|
@ -205,9 +206,9 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Triggers the <see cref="OnLocationChanged"/> event with the current URI value.
|
/// Triggers the <see cref="OnLocationChanged"/> event with the current URI value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void TriggerOnLocationChanged()
|
protected void TriggerOnLocationChanged(bool isinterceptedLink)
|
||||||
{
|
{
|
||||||
_onLocationChanged?.Invoke(this, _uri);
|
_onLocationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isinterceptedLink));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertInitialized()
|
private void AssertInitialized()
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
|
"test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
|
||||||
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
"test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
||||||
"test\\testassets\\TestContentPackage\\TestContentPackage.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) { }
|
public override void InitializeState(string uriAbsolute, string baseUriAbsolute) { }
|
||||||
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
protected override void NavigateToCore(string uri, bool forceLoad) { }
|
||||||
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
[Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")]
|
||||||
public static void NotifyLocationChanged(string uriAbsolute) { }
|
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Browser;
|
using Microsoft.AspNetCore.Components.Browser;
|
||||||
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
@ -120,6 +121,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
uriHelper.AttachJsRuntime(JSRuntime);
|
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;
|
||||||
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -49,12 +50,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
(authenticationStateProvider as FixedAuthenticationStateProvider)?.Initialize(httpContext.User);
|
||||||
|
|
||||||
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
var uriHelper = (RemoteUriHelper)scope.ServiceProvider.GetRequiredService<IUriHelper>();
|
||||||
|
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
||||||
if (client.Connected)
|
if (client.Connected)
|
||||||
{
|
{
|
||||||
uriHelper.AttachJsRuntime(jsRuntime);
|
uriHelper.AttachJsRuntime(jsRuntime);
|
||||||
uriHelper.InitializeState(
|
uriHelper.InitializeState(
|
||||||
uriAbsolute,
|
uriAbsolute,
|
||||||
baseUriAbsolute);
|
baseUriAbsolute);
|
||||||
|
|
||||||
|
navigationInterception.AttachJSRuntime(jsRuntime);
|
||||||
}
|
}
|
||||||
else
|
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>
|
/// </summary>
|
||||||
public class RemoteUriHelper : UriHelperBase
|
public class RemoteUriHelper : UriHelperBase
|
||||||
{
|
{
|
||||||
private IJSRuntime _jsRuntime;
|
|
||||||
private readonly ILogger<RemoteUriHelper> _logger;
|
private readonly ILogger<RemoteUriHelper> _logger;
|
||||||
|
private IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="RemoteUriHelper"/> instance.
|
/// 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)
|
public override void InitializeState(string uriAbsolute, string baseUriAbsolute)
|
||||||
{
|
{
|
||||||
base.InitializeState(uriAbsolute, baseUriAbsolute);
|
base.InitializeState(uriAbsolute, baseUriAbsolute);
|
||||||
TriggerOnLocationChanged();
|
TriggerOnLocationChanged(isinterceptedLink: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -52,11 +52,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("JavaScript runtime already initialized.");
|
throw new InvalidOperationException("JavaScript runtime already initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
|
|
||||||
_jsRuntime.InvokeAsync<object>(
|
_jsRuntime.InvokeAsync<object>(
|
||||||
Interop.EnableNavigationInterception,
|
Interop.ListenForNavigationEvents,
|
||||||
typeof(RemoteUriHelper).Assembly.GetName().Name,
|
typeof(RemoteUriHelper).Assembly.GetName().Name,
|
||||||
nameof(NotifyLocationChanged));
|
nameof(NotifyLocationChanged));
|
||||||
|
|
||||||
_logger.LogDebug($"{nameof(RemoteUriHelper)} initialized.");
|
_logger.LogDebug($"{nameof(RemoteUriHelper)} initialized.");
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +67,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
/// For framework use only.
|
/// For framework use only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JSInvokable(nameof(NotifyLocationChanged))]
|
[JSInvokable(nameof(NotifyLocationChanged))]
|
||||||
public static void NotifyLocationChanged(string uriAbsolute)
|
public static void NotifyLocationChanged(string uriAbsolute, bool isInterceptedLink)
|
||||||
{
|
{
|
||||||
var circuit = CircuitHost.Current;
|
var circuit = CircuitHost.Current;
|
||||||
if (circuit == null)
|
if (circuit == null)
|
||||||
|
|
@ -73,19 +75,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
var message = $"{nameof(NotifyLocationChanged)} called without a circuit.";
|
var message = $"{nameof(NotifyLocationChanged)} called without a circuit.";
|
||||||
throw new InvalidOperationException(message);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var uriHelper = (RemoteUriHelper)circuit.Services.GetRequiredService<IUriHelper>();
|
var uriHelper = (RemoteUriHelper)circuit.Services.GetRequiredService<IUriHelper>();
|
||||||
|
|
||||||
uriHelper.SetAbsoluteUri(uriAbsolute);
|
uriHelper.SetAbsoluteUri(uriAbsolute);
|
||||||
|
|
||||||
uriHelper._logger.LogDebug($"Location changed to '{uriAbsolute}'.");
|
uriHelper._logger.LogDebug($"Location changed to '{uriAbsolute}'.");
|
||||||
uriHelper.TriggerOnLocationChanged();
|
uriHelper.TriggerOnLocationChanged(isInterceptedLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
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)
|
if (_jsRuntime == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.AspNetCore.Components.Server;
|
using Microsoft.AspNetCore.Components.Server;
|
||||||
using Microsoft.AspNetCore.Components.Server.BlazorPack;
|
using Microsoft.AspNetCore.Components.Server.BlazorPack;
|
||||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||||
|
|
@ -66,6 +67,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
// These intentionally replace the non-interactive versions included in MVC.
|
// These intentionally replace the non-interactive versions included in MVC.
|
||||||
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
services.AddScoped<IUriHelper, RemoteUriHelper>();
|
||||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||||
|
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||||
services.AddScoped<AuthenticationStateProvider, FixedAuthenticationStateProvider>();
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
|
|
@ -206,6 +204,5 @@ window.Blazor._internal.forceCloseConnection();");
|
||||||
Browser.True(() => Browser.Manage().Logs.GetLog(LogType.Browser)
|
Browser.True(() => Browser.Manage().Logs.GetLog(LogType.Browser)
|
||||||
.Any(l => l.Level == LogLevel.Info && l.Message.Contains("Connection disconnected.")));
|
.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");
|
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]
|
[Fact]
|
||||||
public void CanNavigateProgrammatically()
|
public void CanNavigateProgrammatically()
|
||||||
{
|
{
|
||||||
|
|
@ -339,12 +349,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
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 pathBaseWithoutHash = ServerPathBase.Split('#')[0];
|
||||||
var jsExecutor = (IJavaScriptExecutor)Browser;
|
var jsExecutor = (IJavaScriptExecutor)Browser;
|
||||||
var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}");
|
var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}");
|
||||||
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
|
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
|
||||||
|
|
||||||
|
return absoluteUri.AbsoluteUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertHighlightedLinks(params string[] linkTexts)
|
private void AssertHighlightedLinks(params string[] linkTexts)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
||||||
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
||||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</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.HtmlBlockChildContent">ChildContent HTML Block</option>
|
||||||
<option value="BasicTestApp.HtmlMixedChildContent">ChildContent Mixed Block</option>
|
<option value="BasicTestApp.HtmlMixedChildContent">ChildContent Mixed Block</option>
|
||||||
<option value="BasicTestApp.HtmlEncodedChildContent">ChildContent HTML Encoded 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.InteropOnInitializationComponent">Interop on initialization</option>
|
||||||
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
||||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||||
|
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@if (SelectedComponentType != null)
|
@if (SelectedComponentType != null)
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<div id="test-info">Oops, that component wasn't found!</div>
|
|
||||||
|
|
@ -27,3 +27,6 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/" target="_blank">Target (_blank)</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
|
@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;
|
return element.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uriHelperNavigate() {
|
||||||
|
Blazor.navigateTo('/subdir/some-path');
|
||||||
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
// Load either blazor.webassembly.js or blazor.server.js depending
|
// 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
|
// 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
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Error
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@page
|
@page
|
||||||
@using ComponentsApp.App
|
@using ComponentsApp.App
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
@ -12,8 +12,7 @@
|
||||||
<link href="css/site.css" rel="stylesheet" />
|
<link href="css/site.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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 src="_framework/blazor.server.js" autostart="false"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Components.Server;
|
using Microsoft.AspNetCore.Components.Server;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
|
@ -207,6 +208,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddScoped<StaticComponentRenderer>();
|
services.TryAddScoped<StaticComponentRenderer>();
|
||||||
services.TryAddScoped<IUriHelper, HttpUriHelper>();
|
services.TryAddScoped<IUriHelper, HttpUriHelper>();
|
||||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||||
|
services.TryAddScoped<IComponentContext, UnsupportedComponentContext>();
|
||||||
|
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
||||||
|
|
||||||
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
|
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
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");
|
var response = await client.GetAsync("http://localhost/components");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("\n<p>Hello world!</p>", "Greetings", content);
|
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");
|
var response = await client.GetAsync("http://localhost/components");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("\n<p>Hello world!</p>", "Greetings", content);
|
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");
|
var response = await client.GetAsync("http://localhost/components/routable");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("\nRouter component\n<p>Routed successfully</p>", "Routing", content);
|
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");
|
var response = await client.GetAsync("http://localhost/components/routable");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("\nRouter component\n<p>Routed successfully</p>", "Routing", content);
|
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");
|
var response = await client.GetAsync("http://localhost/components/false");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("<p>Hello world!</p>", "Greetings", content, unwrap: true);
|
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");
|
var response = await client.GetAsync("http://localhost/components/routable/false");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent("Router component\n<p>Routed successfully</p>", "Routing", content, unwrap: true);
|
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");
|
var response = await client.GetAsync("http://localhost/components");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
AssertComponent(expectedHtml, "FetchData", content);
|
AssertComponent(expectedHtml, "FetchData", content);
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<p>Route not found</p>
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
Router component
|
Router component
|
||||||
<Router
|
<Router AppAssembly="System.Reflection.Assembly.GetAssembly(typeof(RouterContainer))">
|
||||||
AppAssembly="System.Reflection.Assembly.GetAssembly(typeof(RouterContainer))"
|
<NotFoundContent>
|
||||||
FallbackComponent="typeof(Fallback)">
|
<p>Route not found</p>
|
||||||
|
</NotFoundContent>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue