* Handle links to empty-string href, resolved against base href Needed to change the URLs used in E2E tests to be able to cover this (i.e., removed the /RouterTest prefixes so the default relative URL became an empty string) * Change links in StandaloneApp sample to be relative * Standardize on base URIs having trailing slash everywhere Hence also change terminology from "base URI prefix" to simply "base URI" * Handle link highlighting when visiting base-href-but-without-trailing-slash * Removing leading slashes from base-relative URLs in templates
This commit is contained in:
parent
7f3ba97fb7
commit
353da42cce
|
|
@ -34,10 +34,10 @@ else
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
<a href="/fetchdata/@StartDate.AddDays(-5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-left">
|
<a href="fetchdata/@StartDate.AddDays(-5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-left">
|
||||||
◀ Previous
|
◀ Previous
|
||||||
</a>
|
</a>
|
||||||
<a href="/fetchdata/@StartDate.AddDays(5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-right">
|
<a href="fetchdata/@StartDate.AddDays(5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-right">
|
||||||
Next ▶
|
Next ▶
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -57,7 +57,7 @@ else
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
forecasts = await Http.GetJsonAsync<WeatherForecast[]>(
|
forecasts = await Http.GetJsonAsync<WeatherForecast[]>(
|
||||||
$"/sample-data/weather.json?date={StartDate.ToString("yyyy-MM-dd")}");
|
$"sample-data/weather.json?date={StartDate.ToString("yyyy-MM-dd")}");
|
||||||
|
|
||||||
// Because StandaloneApp doesn't really have a server endpoint to get dynamic data from,
|
// Because StandaloneApp doesn't really have a server endpoint to get dynamic data from,
|
||||||
// fake the DateFormatted values here. This would not apply in a real app.
|
// fake the DateFormatted values here. This would not apply in a real app.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="top-row pl-4 navbar navbar-dark">
|
<div class="top-row pl-4 navbar navbar-dark">
|
||||||
<a class="navbar-brand" href="/">Blazor app</a>
|
<a class="navbar-brand" href="">Blazor app</a>
|
||||||
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -8,17 +8,17 @@
|
||||||
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/" Match=NavLinkMatch.All>
|
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/counter">
|
<NavLink class="nav-link" href="counter">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/fetchdata">
|
<NavLink class="nav-link" href="fetchdata">
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { registerFunction } from '../Interop/RegisteredFunction';
|
import { registerFunction } from '../Interop/RegisteredFunction';
|
||||||
import { platform } from '../Environment';
|
import { platform } from '../Environment';
|
||||||
import { MethodHandle, System_String } from '../Platform/Platform';
|
import { MethodHandle, System_String } from '../Platform/Platform';
|
||||||
const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Services.BrowserUriHelper';
|
const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Services.BrowserUriHelper';
|
||||||
|
|
@ -22,9 +22,10 @@ registerFunction(`${registeredFunctionPrefix}.enableNavigationInterception`, ()
|
||||||
const anchorTarget = findClosestAncestor(event.target as Element | null, 'A');
|
const anchorTarget = findClosestAncestor(event.target as Element | null, 'A');
|
||||||
if (anchorTarget) {
|
if (anchorTarget) {
|
||||||
const href = anchorTarget.getAttribute('href');
|
const href = anchorTarget.getAttribute('href');
|
||||||
if (isWithinBaseUriSpace(toAbsoluteUri(href))) {
|
const absoluteHref = toAbsoluteUri(href);
|
||||||
|
if (isWithinBaseUriSpace(absoluteHref)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
performInternalNavigation(href);
|
performInternalNavigation(absoluteHref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -37,15 +38,16 @@ registerFunction(`${registeredFunctionPrefix}.navigateTo`, (uriDotNetString: Sys
|
||||||
});
|
});
|
||||||
|
|
||||||
export function navigateTo(uri: string) {
|
export function navigateTo(uri: string) {
|
||||||
if (isWithinBaseUriSpace(toAbsoluteUri(uri))) {
|
const absoluteUri = toAbsoluteUri(uri);
|
||||||
performInternalNavigation(uri);
|
if (isWithinBaseUriSpace(absoluteUri)) {
|
||||||
|
performInternalNavigation(absoluteUri);
|
||||||
} else {
|
} else {
|
||||||
location.href = uri;
|
location.href = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function performInternalNavigation(href: string) {
|
function performInternalNavigation(absoluteInternalHref: string) {
|
||||||
history.pushState(null, /* ignored title */ '', href);
|
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
|
||||||
handleInternalNavigation();
|
handleInternalNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,10 +82,10 @@ function findClosestAncestor(element: Element | null, tagName: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWithinBaseUriSpace(href: string) {
|
function isWithinBaseUriSpace(href: string) {
|
||||||
const baseUriPrefixWithTrailingSlash = toBaseUriPrefixWithTrailingSlash(document.baseURI!); // TODO: Might baseURI really be null?
|
const baseUriWithTrailingSlash = toBaseUriWithTrailingSlash(document.baseURI!); // TODO: Might baseURI really be null?
|
||||||
return href.startsWith(baseUriPrefixWithTrailingSlash);
|
return href.startsWith(baseUriWithTrailingSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toBaseUriPrefixWithTrailingSlash(baseUri: string) {
|
function toBaseUriWithTrailingSlash(baseUri: string) {
|
||||||
return baseUri.substr(0, baseUri.lastIndexOf('/') + 1);
|
return baseUri.substr(0, baseUri.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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.Blazor.Browser.Http;
|
using Microsoft.AspNetCore.Blazor.Browser.Http;
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
serviceCollection.AddSingleton<IUriHelper>(uriHelper);
|
serviceCollection.AddSingleton<IUriHelper>(uriHelper);
|
||||||
serviceCollection.AddSingleton(new HttpClient(new BrowserHttpMessageHandler())
|
serviceCollection.AddSingleton(new HttpClient(new BrowserHttpMessageHandler())
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri(uriHelper.GetBaseUriPrefix())
|
BaseAddress = new Uri(uriHelper.GetBaseUri())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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.Blazor.Browser.Interop;
|
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||||
|
|
@ -22,8 +22,11 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
static bool _hasEnabledNavigationInterception;
|
static bool _hasEnabledNavigationInterception;
|
||||||
static string _cachedAbsoluteUri;
|
static string _cachedAbsoluteUri;
|
||||||
static EventHandler<string> _onLocationChanged;
|
static EventHandler<string> _onLocationChanged;
|
||||||
static string _baseUriStringNoTrailingSlash; // No trailing slash so we can just prepend it to suffixes
|
|
||||||
static Uri _baseUriWithTrailingSlash; // With trailing slash so it can be used in new Uri(base, relative)
|
// These two are always kept in sync. We store both representations to
|
||||||
|
// avoid having to convert between them on demand.
|
||||||
|
static Uri _baseUriWithTrailingSlash;
|
||||||
|
static string _baseUriStringWithTrailingSlash;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<string> OnLocationChanged
|
public event EventHandler<string> OnLocationChanged
|
||||||
|
|
@ -44,10 +47,10 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetBaseUriPrefix()
|
public string GetBaseUri()
|
||||||
{
|
{
|
||||||
EnsureBaseUriPopulated();
|
EnsureBaseUriPopulated();
|
||||||
return _baseUriStringNoTrailingSlash;
|
return _baseUriStringWithTrailingSlash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -81,27 +84,25 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string ToBaseRelativePath(string baseUriPrefix, string absoluteUri)
|
public string ToBaseRelativePath(string baseUri, string absoluteUri)
|
||||||
{
|
{
|
||||||
if (absoluteUri.Equals(baseUriPrefix, StringComparison.Ordinal))
|
if (absoluteUri.StartsWith(baseUri, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Special case: if you're exactly at the base URI, treat it as if you
|
// The absolute URI must be of the form "{baseUri}something" (where
|
||||||
// were at "{baseUriPrefix}/" (i.e., with a following slash). It's a bit
|
// baseUri ends with a slash), and from that we return "something"
|
||||||
// ambiguous because we don't know whether the server would return the
|
return absoluteUri.Substring(baseUri.Length);
|
||||||
// same page whether or not the slash is present, but ASP.NET Core at
|
} else if ($"{absoluteUri}/".Equals(baseUri, StringComparison.Ordinal))
|
||||||
// least does by default when using PathBase.
|
|
||||||
return "/";
|
|
||||||
}
|
|
||||||
else if (absoluteUri.StartsWith(baseUriPrefix, StringComparison.Ordinal)
|
|
||||||
&& absoluteUri.Length > baseUriPrefix.Length
|
|
||||||
&& absoluteUri[baseUriPrefix.Length] == '/')
|
|
||||||
{
|
{
|
||||||
// The absolute URI must be of the form "{baseUriPrefix}/something",
|
// Special case: for the base URI "/something/", if you're at
|
||||||
// and from that we return "/something"
|
// "/something" then treat it as if you were at "/something/" (i.e.,
|
||||||
return absoluteUri.Substring(baseUriPrefix.Length);
|
// with the trailing slash). It's a bit ambiguous because we don't know
|
||||||
|
// whether the server would return the same page whether or not the
|
||||||
|
// slash is present, but ASP.NET Core at least does by default when
|
||||||
|
// using PathBase.
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException($"The URI '{absoluteUri}' is not contained by the base URI '{baseUriPrefix}'.");
|
throw new ArgumentException($"The URI '{absoluteUri}' is not contained by the base URI '{baseUri}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -118,12 +119,12 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
private static void EnsureBaseUriPopulated()
|
private static void EnsureBaseUriPopulated()
|
||||||
{
|
{
|
||||||
// The <base href> is fixed for the lifetime of the page, so just cache it
|
// The <base href> is fixed for the lifetime of the page, so just cache it
|
||||||
if (_baseUriStringNoTrailingSlash == null)
|
if (_baseUriStringWithTrailingSlash == null)
|
||||||
{
|
{
|
||||||
var baseUri = RegisteredFunction.InvokeUnmarshalled<string>(
|
var baseUriAbsolute = RegisteredFunction.InvokeUnmarshalled<string>(
|
||||||
$"{_functionPrefix}.getBaseURI");
|
$"{_functionPrefix}.getBaseURI");
|
||||||
_baseUriStringNoTrailingSlash = ToBaseUriPrefix(baseUri);
|
_baseUriStringWithTrailingSlash = ToBaseUri(baseUriAbsolute);
|
||||||
_baseUriWithTrailingSlash = new Uri(_baseUriStringNoTrailingSlash + "/");
|
_baseUriWithTrailingSlash = new Uri(_baseUriStringWithTrailingSlash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,25 +148,25 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given the document's document.baseURI value, returns the URI prefix
|
/// Given the document's document.baseURI value, returns the URI
|
||||||
/// that can be prepended to URI paths to produce an absolute URI.
|
/// that can be prepended to relative URI paths to produce an absolute URI.
|
||||||
/// This is computed by removing the final slash and any following characters.
|
/// This is computed by removing anything after the final slash.
|
||||||
/// Internal for tests.
|
/// Internal for tests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseUri">The page's document.baseURI value.</param>
|
/// <param name="absoluteBaseUri">The page's document.baseURI value.</param>
|
||||||
/// <returns>The URI prefix</returns>
|
/// <returns>The URI prefix</returns>
|
||||||
internal static string ToBaseUriPrefix(string baseUri)
|
internal static string ToBaseUri(string absoluteBaseUri)
|
||||||
{
|
{
|
||||||
if (baseUri != null)
|
if (absoluteBaseUri != null)
|
||||||
{
|
{
|
||||||
var lastSlashIndex = baseUri.LastIndexOf('/');
|
var lastSlashIndex = absoluteBaseUri.LastIndexOf('/');
|
||||||
if (lastSlashIndex >= 0)
|
if (lastSlashIndex >= 0)
|
||||||
{
|
{
|
||||||
return baseUri.Substring(0, lastSlashIndex);
|
return absoluteBaseUri.Substring(0, lastSlashIndex + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return "/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,6 @@ else
|
||||||
|
|
||||||
protected override async Task OnInitAsync()
|
protected override async Task OnInitAsync()
|
||||||
{
|
{
|
||||||
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("/api/SampleData/WeatherForecasts");
|
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="top-row pl-4 navbar navbar-dark">
|
<div class="top-row pl-4 navbar navbar-dark">
|
||||||
<a class="navbar-brand" href="/">BlazorHosted-CSharp</a>
|
<a class="navbar-brand" href="">BlazorHosted-CSharp</a>
|
||||||
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -8,17 +8,17 @@
|
||||||
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/" Match=NavLinkMatch.All>
|
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/counter">
|
<NavLink class="nav-link" href="counter">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/fetchdata">
|
<NavLink class="nav-link" href="fetchdata">
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ else
|
||||||
|
|
||||||
protected override async Task OnInitAsync()
|
protected override async Task OnInitAsync()
|
||||||
{
|
{
|
||||||
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("/sample-data/weather.json");
|
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
class WeatherForecast
|
class WeatherForecast
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="top-row pl-4 navbar navbar-dark">
|
<div class="top-row pl-4 navbar navbar-dark">
|
||||||
<a class="navbar-brand" href="/">BlazorStandalone-CSharp</a>
|
<a class="navbar-brand" href="">BlazorStandalone-CSharp</a>
|
||||||
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
<button class="navbar-toggler" onclick=@ToggleNavMenu>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -8,17 +8,17 @@
|
||||||
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/" Match=NavLinkMatch.All>
|
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/counter">
|
<NavLink class="nav-link" href="counter">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/fetchdata">
|
<NavLink class="nav-link" href="fetchdata">
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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.Blazor.Components;
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
@ -96,18 +96,45 @@ namespace Microsoft.AspNetCore.Blazor.Routing
|
||||||
|
|
||||||
private bool ShouldMatch(string currentUriAbsolute)
|
private bool ShouldMatch(string currentUriAbsolute)
|
||||||
{
|
{
|
||||||
if (Match == NavLinkMatch.Prefix)
|
if (EqualsHrefExactlyOrIfTrailingSlashAdded(currentUriAbsolute))
|
||||||
{
|
{
|
||||||
return StartsWithAndHasSeparator(currentUriAbsolute, _hrefAbsolute);
|
return true;
|
||||||
}
|
}
|
||||||
else if (Match == NavLinkMatch.All)
|
|
||||||
|
if (Match == NavLinkMatch.Prefix
|
||||||
|
&& IsStrictlyPrefixWithSeparator(currentUriAbsolute, _hrefAbsolute))
|
||||||
{
|
{
|
||||||
return string.Equals(currentUriAbsolute, _hrefAbsolute, StringComparison.Ordinal);
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EqualsHrefExactlyOrIfTrailingSlashAdded(string currentUriAbsolute)
|
||||||
|
{
|
||||||
|
if (string.Equals(currentUriAbsolute, _hrefAbsolute, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Unsupported {nameof(NavLinkMatch)} value: {Match}");
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentUriAbsolute.Length == _hrefAbsolute.Length - 1)
|
||||||
|
{
|
||||||
|
// Special case: highlight links to http://host/path/ even if you're
|
||||||
|
// at http://host/path (with no trailing slash)
|
||||||
|
//
|
||||||
|
// This is because the router accepts an absolute URI value of "same
|
||||||
|
// as base URI but without trailing slash" as equivalent to "base URI",
|
||||||
|
// which in turn is because it's common for servers to return the same page
|
||||||
|
// for http://host/vdir as they do for host://host/vdir/ as it's no
|
||||||
|
// good to display a blank page in that case.
|
||||||
|
if (_hrefAbsolute[_hrefAbsolute.Length - 1] == '/'
|
||||||
|
&& _hrefAbsolute.StartsWith(currentUriAbsolute, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render(RenderTreeBuilder builder)
|
private void Render(RenderTreeBuilder builder)
|
||||||
|
|
@ -134,15 +161,10 @@ namespace Microsoft.AspNetCore.Blazor.Routing
|
||||||
=> str1 == null ? str2
|
=> str1 == null ? str2
|
||||||
: (str2 == null ? str1 : $"{str1} {str2}");
|
: (str2 == null ? str1 : $"{str1} {str2}");
|
||||||
|
|
||||||
private static bool StartsWithAndHasSeparator(string value, string prefix)
|
private static bool IsStrictlyPrefixWithSeparator(string value, string prefix)
|
||||||
{
|
{
|
||||||
var valueLength = value.Length;
|
|
||||||
var prefixLength = prefix.Length;
|
var prefixLength = prefix.Length;
|
||||||
if (prefixLength == valueLength)
|
if (value.Length > prefixLength)
|
||||||
{
|
|
||||||
return string.Equals(value, prefix, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
else if (valueLength > prefixLength)
|
|
||||||
{
|
{
|
||||||
return value.StartsWith(prefix, StringComparison.Ordinal)
|
return value.StartsWith(prefix, StringComparison.Ordinal)
|
||||||
&& (
|
&& (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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 System;
|
using System;
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Blazor.Routing
|
||||||
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
static readonly char[] _queryOrHashStartChar = new[] { '?', '#' };
|
||||||
|
|
||||||
RenderHandle _renderHandle;
|
RenderHandle _renderHandle;
|
||||||
string _baseUriPrefix;
|
string _baseUri;
|
||||||
string _locationAbsolute;
|
string _locationAbsolute;
|
||||||
|
|
||||||
[Inject] private IUriHelper UriHelper { get; set; }
|
[Inject] private IUriHelper UriHelper { get; set; }
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Blazor.Routing
|
||||||
public void Init(RenderHandle renderHandle)
|
public void Init(RenderHandle renderHandle)
|
||||||
{
|
{
|
||||||
_renderHandle = renderHandle;
|
_renderHandle = renderHandle;
|
||||||
_baseUriPrefix = UriHelper.GetBaseUriPrefix();
|
_baseUri = UriHelper.GetBaseUri();
|
||||||
_locationAbsolute = UriHelper.GetAbsoluteUri();
|
_locationAbsolute = UriHelper.GetAbsoluteUri();
|
||||||
UriHelper.OnLocationChanged += OnLocationChanged;
|
UriHelper.OnLocationChanged += OnLocationChanged;
|
||||||
}
|
}
|
||||||
|
|
@ -76,13 +76,13 @@ namespace Microsoft.AspNetCore.Blazor.Routing
|
||||||
|
|
||||||
private void Refresh()
|
private void Refresh()
|
||||||
{
|
{
|
||||||
var locationPath = UriHelper.ToBaseRelativePath(_baseUriPrefix, _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)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with a route for '{locationPath}'.");
|
throw new InvalidOperationException($"'{nameof(Router)}' cannot find any component with a route for '/{locationPath}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!typeof(IComponent).IsAssignableFrom(context.Handler))
|
if (!typeof(IComponent).IsAssignableFrom(context.Handler))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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 System;
|
using System;
|
||||||
|
|
@ -30,26 +30,26 @@ namespace Microsoft.AspNetCore.Blazor.Services
|
||||||
Uri ToAbsoluteUri(string href);
|
Uri ToAbsoluteUri(string href);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the URI prefix that can be prepended before URI paths to produce an absolute URI.
|
/// Gets the base URI (with trailing slash) that can be prepended before relative URI paths to produce an absolute URI.
|
||||||
/// Typically this corresponds to the 'href' attribute on the document's <base> element.
|
/// Typically this corresponds to the 'href' attribute on the document's <base> element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The URI prefix.</returns>
|
/// <returns>The URI prefix, which has a trailing slash.</returns>
|
||||||
string GetBaseUriPrefix();
|
string GetBaseUri();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a base URI prefix (e.g., one previously returned by <see cref="GetBaseUriPrefix"/>),
|
/// Given a base URI (e.g., one previously returned by <see cref="GetBaseUri"/>),
|
||||||
/// converts an absolute URI into one relative to the base URI prefix.
|
/// converts an absolute URI into one relative to the base URI prefix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseUriPrefix">The base URI prefix (e.g., previously returned by <see cref="GetBaseUriPrefix"/>).</param>
|
/// <param name="baseUri">The base URI prefix (e.g., previously returned by <see cref="GetBaseUri"/>).</param>
|
||||||
/// <param name="locationAbsolute">An absolute URI that is within the space of the base URI prefix.</param>
|
/// <param name="locationAbsolute">An absolute URI that is within the space of the base URI.</param>
|
||||||
/// <returns>A relative URI path.</returns>
|
/// <returns>A relative URI path.</returns>
|
||||||
string ToBaseRelativePath(string baseUriPrefix, string locationAbsolute);
|
string ToBaseRelativePath(string baseUri, string locationAbsolute);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Navigates to the specified URI.
|
/// Navigates to the specified URI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
/// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
|
||||||
/// prefix (as returned by <see cref="GetBaseUriPrefix"/>).</param>
|
/// (as returned by <see cref="GetBaseUri"/>).</param>
|
||||||
void NavigateTo(string uri);
|
void NavigateTo(string uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,41 +12,43 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test
|
||||||
private BrowserUriHelper _browserUriHelper = new BrowserUriHelper();
|
private BrowserUriHelper _browserUriHelper = new BrowserUriHelper();
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("scheme://host/", "scheme://host")]
|
[InlineData("scheme://host/", "scheme://host/")]
|
||||||
[InlineData("scheme://host:123/", "scheme://host:123")]
|
[InlineData("scheme://host:123/", "scheme://host:123/")]
|
||||||
[InlineData("scheme://host/path", "scheme://host")]
|
[InlineData("scheme://host/path", "scheme://host/")]
|
||||||
[InlineData("scheme://host/path/", "scheme://host/path")]
|
[InlineData("scheme://host/path/", "scheme://host/path/")]
|
||||||
[InlineData("scheme://host/path/page?query=string&another=here", "scheme://host/path")]
|
[InlineData("scheme://host/path/page?query=string&another=here", "scheme://host/path/")]
|
||||||
public void ComputesCorrectBaseUriPrefix(string baseUri, string expectedResult)
|
public void ComputesCorrectBaseUri(string baseUri, string expectedResult)
|
||||||
{
|
{
|
||||||
var actualResult = BrowserUriHelper.ToBaseUriPrefix(baseUri);
|
var actualResult = BrowserUriHelper.ToBaseUri(baseUri);
|
||||||
Assert.Equal(expectedResult, actualResult);
|
Assert.Equal(expectedResult, actualResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("scheme://host", "scheme://host/", "/")]
|
[InlineData("scheme://host/", "scheme://host", "")]
|
||||||
[InlineData("scheme://host", "scheme://host/path", "/path")]
|
[InlineData("scheme://host/", "scheme://host/", "")]
|
||||||
[InlineData("scheme://host/path", "scheme://host/path/", "/")]
|
[InlineData("scheme://host/", "scheme://host/path", "path")]
|
||||||
[InlineData("scheme://host/path", "scheme://host/path/more", "/more")]
|
[InlineData("scheme://host/path/", "scheme://host/path/", "")]
|
||||||
[InlineData("scheme://host/path", "scheme://host/path", "/")]
|
[InlineData("scheme://host/path/", "scheme://host/path/more", "more")]
|
||||||
public void ComputesCorrectValidBaseRelativePaths(string baseUriPrefix, string absoluteUri, string expectedResult)
|
[InlineData("scheme://host/path/", "scheme://host/path", "")]
|
||||||
|
public void ComputesCorrectValidBaseRelativePaths(string baseUri, string absoluteUri, string expectedResult)
|
||||||
{
|
{
|
||||||
var actualResult = _browserUriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri);
|
var actualResult = _browserUriHelper.ToBaseRelativePath(baseUri, absoluteUri);
|
||||||
Assert.Equal(expectedResult, actualResult);
|
Assert.Equal(expectedResult, actualResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("scheme://host", "otherscheme://host/")] // Mismatched prefix is error
|
[InlineData("scheme://host/", "otherscheme://host/")]
|
||||||
[InlineData("scheme://host", "scheme://otherhost/")] // Mismatched prefix is error
|
[InlineData("scheme://host/", "scheme://otherhost/")]
|
||||||
public void ThrowsForInvalidBaseRelativePaths(string baseUriPrefix, string absoluteUri)
|
[InlineData("scheme://host/path/", "scheme://host/")]
|
||||||
|
public void ThrowsForInvalidBaseRelativePaths(string baseUri, string absoluteUri)
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
_browserUriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri);
|
_browserUriHelper.ToBaseRelativePath(baseUri, absoluteUri);
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
$"The URI '{absoluteUri}' is not contained by the base URI '{baseUriPrefix}'.",
|
$"The URI '{absoluteUri}' is not contained by the base URI '{baseUri}'.",
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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 System;
|
using System;
|
||||||
|
|
@ -31,27 +31,41 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanArriveAtDefaultPage()
|
public void CanArriveAtDefaultPage()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||||
AssertHighlightedLinks("Default (matches all)");
|
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanArriveAtDefaultPageWithoutTrailingSlash()
|
||||||
|
{
|
||||||
|
// This is a bit of a degenerate case because ideally devs would configure their
|
||||||
|
// servers to enforce a canonical URL (with trailing slash) for the homepage.
|
||||||
|
// But in case they don't want to, we need to handle it the same as if the URL does
|
||||||
|
// have a trailing slash.
|
||||||
|
SetUrlViaPushState($"{ServerPathBase}");
|
||||||
|
|
||||||
|
var app = MountTestComponent<TestRouter>();
|
||||||
|
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||||
|
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanArriveAtPageWithParameters()
|
public void CanArriveAtPageWithParameters()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/WithParameters/Name/Dan/LastName/Roth");
|
SetUrlViaPushState($"{ServerPathBase}/WithParameters/Name/Ghi/LastName/O'Jkl");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
Assert.Equal("Your full name is Dan Roth.", app.FindElement(By.Id("test-info")).Text);
|
Assert.Equal("Your full name is Ghi O'Jkl.", app.FindElement(By.Id("test-info")).Text);
|
||||||
AssertHighlightedLinks();
|
AssertHighlightedLinks();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanArriveAtNonDefaultPage()
|
public void CanArriveAtNonDefaultPage()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
|
||||||
|
|
@ -61,7 +75,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToOtherPage()
|
public void CanFollowLinkToOtherPage()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other")).Click();
|
app.FindElement(By.LinkText("Other")).Click();
|
||||||
|
|
@ -72,7 +86,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
|
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
|
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
|
||||||
|
|
@ -80,32 +94,43 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl()
|
||||||
|
{
|
||||||
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
|
var app = MountTestComponent<TestRouter>();
|
||||||
|
app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click();
|
||||||
|
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||||
|
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToPageWithParameters()
|
public void CanFollowLinkToPageWithParameters()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("With parameters")).Click();
|
app.FindElement(By.LinkText("With parameters")).Click();
|
||||||
Assert.Equal("Your full name is Steve Sanderson.", app.FindElement(By.Id("test-info")).Text);
|
Assert.Equal("Your full name is Abc McDef.", app.FindElement(By.Id("test-info")).Text);
|
||||||
AssertHighlightedLinks("With parameters");
|
AssertHighlightedLinks("With parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToDefaultPage()
|
public void CanFollowLinkToDefaultPage()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default (matches all)")).Click();
|
app.FindElement(By.LinkText("Default (matches all)")).Click();
|
||||||
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
|
||||||
AssertHighlightedLinks("Default (matches all)");
|
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToOtherPageWithQueryString()
|
public void CanFollowLinkToOtherPageWithQueryString()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with query")).Click();
|
app.FindElement(By.LinkText("Other with query")).Click();
|
||||||
|
|
@ -116,7 +141,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToDefaultPageWithQueryString()
|
public void CanFollowLinkToDefaultPageWithQueryString()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default with query")).Click();
|
app.FindElement(By.LinkText("Default with query")).Click();
|
||||||
|
|
@ -127,7 +152,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToOtherPageWithHash()
|
public void CanFollowLinkToOtherPageWithHash()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with hash")).Click();
|
app.FindElement(By.LinkText("Other with hash")).Click();
|
||||||
|
|
@ -138,7 +163,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToDefaultPageWithHash()
|
public void CanFollowLinkToDefaultPageWithHash()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/Other");
|
SetUrlViaPushState($"{ServerPathBase}/Other");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default with hash")).Click();
|
app.FindElement(By.LinkText("Default with hash")).Click();
|
||||||
|
|
@ -149,7 +174,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanNavigateProgrammatically()
|
public void CanNavigateProgrammatically()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
|
SetUrlViaPushState($"{ServerPathBase}/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.TagName("button")).Click();
|
app.FindElement(By.TagName("button")).Click();
|
||||||
|
|
@ -168,7 +193,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
{
|
{
|
||||||
var jsExecutor = (IJavaScriptExecutor)Browser;
|
var jsExecutor = (IJavaScriptExecutor)Browser;
|
||||||
var absoluteUri = new Uri(_server.RootUri, relativeUri);
|
var absoluteUri = new Uri(_server.RootUri, relativeUri);
|
||||||
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString()}')");
|
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertHighlightedLinks(params string[] linkTexts)
|
private void AssertHighlightedLinks(params string[] linkTexts)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
@page "/RouterTest"
|
@page "/"
|
||||||
@using BasicTestApp.RouterTest
|
|
||||||
<div id="test-info">This is the default page.</div>
|
<div id="test-info">This is the default page.</div>
|
||||||
<Links />
|
<Links />
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
@page "/Links"
|
|
||||||
@using Microsoft.AspNetCore.Blazor.Routing
|
@using Microsoft.AspNetCore.Blazor.Routing
|
||||||
@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper
|
@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper
|
||||||
<style type="text/css">a.active { background-color: yellow; font-weight: bold; }</style>
|
<style type="text/css">a.active { background-color: yellow; font-weight: bold; }</style>
|
||||||
<ul>
|
<ul>
|
||||||
<li><NavLink href="/subdir/RouterTest/" Match=NavLinkMatch.All>Default (matches all)</NavLink></li>
|
<li><NavLink href="/subdir/" Match=NavLinkMatch.All>Default (matches all)</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/?abc=123">Default with query</NavLink></li>
|
<li><NavLink href="" Match=NavLinkMatch.All>Default with base-relative URL (matches all)</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/#blah">Default with hash</NavLink></li>
|
<li><NavLink href="/subdir/?abc=123">Default with query</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/Other">Other</NavLink></li>
|
<li><NavLink href="/subdir/#blah">Default with hash</NavLink></li>
|
||||||
<li><NavLink href="RouterTest/Other" Match=NavLinkMatch.All>Other with base-relative URL (matches all)</NavLink></li>
|
<li><NavLink href="/subdir/Other">Other</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/Other?abc=123">Other with query</NavLink></li>
|
<li><NavLink href="Other" Match=NavLinkMatch.All>Other with base-relative URL (matches all)</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/Other#blah">Other with hash</NavLink></li>
|
<li><NavLink href="/subdir/Other?abc=123">Other with query</NavLink></li>
|
||||||
<li><NavLink href="/subdir/RouterTest/WithParameters/Name/Steve/LastName/Sanderson">With parameters</NavLink></li>
|
<li><NavLink href="/subdir/Other#blah">Other with hash</NavLink></li>
|
||||||
|
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With parameters</NavLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button onclick=@(x => uriHelper.NavigateTo("RouterTest/Other"))>
|
<button onclick=@(x => uriHelper.NavigateTo("Other"))>
|
||||||
Programmatic navigation
|
Programmatic navigation
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
@page "/RouterTest/Other"
|
@page "/Other"
|
||||||
@using BasicTestApp.RouterTest
|
|
||||||
<div id="test-info">This is another page.</div>
|
<div id="test-info">This is another page.</div>
|
||||||
<Links />
|
<Links />
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
@using Microsoft.AspNetCore.Blazor.Routing
|
|
||||||
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly />
|
<Router AppAssembly=typeof(BasicTestApp.Program).Assembly />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
@page "/RouterTest/WithParameters/Name/{firstName}/LastName/{lastName}"
|
@page "/WithParameters/Name/{firstName}/LastName/{lastName}"
|
||||||
@using BasicTestApp.RouterTest
|
|
||||||
@using Microsoft.AspNetCore.Blazor.Components
|
|
||||||
<div id="test-info">Your full name is @FirstName @LastName.</div>
|
<div id="test-info">Your full name is @FirstName @LastName.</div>
|
||||||
<Links />
|
<Links />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue