diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts index d12aec6169..71bfade3c9 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Platform/Mono/MonoPlatform.ts @@ -34,7 +34,7 @@ export const monoPlatform: Platform = { const typeHandle = find_class(assemblyHandle, namespace, className); if (!typeHandle) { - throw new Error(`Could not find type "${className}'" in namespace "${namespace}" in assembly "${assemblyName}"`); + throw new Error(`Could not find type "${className}" in namespace "${namespace}" in assembly "${assemblyName}"`); } const methodHandle = find_method(typeHandle, methodName, -1); diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Routing/UriHelper.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Routing/UriHelper.ts index a502075dbb..53442dae62 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Routing/UriHelper.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Routing/UriHelper.ts @@ -1,7 +1,7 @@ import { registerFunction } from '../Interop/RegisteredFunction'; import { platform } from '../Environment'; import { MethodHandle } from '../Platform/Platform'; -const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Routing.UriHelper'; +const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Services.BrowserUriHelper'; let notifyLocationChangedMethod: MethodHandle; let hasRegisteredEventListeners = false; @@ -37,8 +37,8 @@ function handleInternalNavigation() { if (!notifyLocationChangedMethod) { notifyLocationChangedMethod = platform.findMethod( 'Microsoft.AspNetCore.Blazor.Browser', - 'Microsoft.AspNetCore.Blazor.Browser.Routing', - 'UriHelper', + 'Microsoft.AspNetCore.Blazor.Browser.Services', + 'BrowserUriHelper', 'NotifyLocationChanged' ); } diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs index 842ab4e4a3..e7cb7b8329 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs @@ -7,6 +7,7 @@ using System.Reflection; using Microsoft.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.Layouts; using Microsoft.AspNetCore.Blazor.RenderTree; +using Microsoft.AspNetCore.Blazor.Services; namespace Microsoft.AspNetCore.Blazor.Browser.Routing { @@ -22,6 +23,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing string _baseUriPrefix; string _locationAbsolute; + [Inject] private IUriHelper UriHelper { get; set; } + /// /// Gets or sets the assembly that should be searched, along with its referenced /// assemblies, for components matching the URI. diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/NavLink.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/NavLink.cs index d7c4c7a5c2..9c478bfa46 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/NavLink.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/NavLink.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.RenderTree; +using Microsoft.AspNetCore.Blazor.Services; using System; using System.Collections.Generic; using System.Linq; @@ -32,6 +33,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing private string _hrefAbsolute; private IReadOnlyDictionary _allAttributes; + [Inject] private IUriHelper UriHelper { get; set; } + public void Init(RenderHandle renderHandle) { _renderHandle = renderHandle; diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs similarity index 69% rename from src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs rename to src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs index 2ac97ad121..799c6c8065 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs @@ -2,28 +2,31 @@ // 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.Services; using System; -namespace Microsoft.AspNetCore.Blazor.Browser.Routing +namespace Microsoft.AspNetCore.Blazor.Browser.Services { - // TODO: Make this not static, and wrap it in an interface that can be injected through DI. - /// - /// Helpers for working with URIs and navigation state. + /// Default browser implementation of . /// - public static class UriHelper + public class BrowserUriHelper : IUriHelper { - static readonly string _functionPrefix = typeof(UriHelper).FullName; + // Since there's only one browser (and hence only one navigation state), the internal state + // is all static. In typical usage the DI system will register BrowserUriHelper as a singleton + // so it makes no difference, but if you manually instantiate more than one BrowserUriHelper + // that's fine too - they will just share their internal state. + // This class will never be used during server-side prerendering, so we don't have thread- + // safety concerns due to the static state. + static readonly string _functionPrefix = typeof(BrowserUriHelper).FullName; + static bool _hasEnabledNavigationInterception; static string _currentAbsoluteUri; + static EventHandler _onLocationChanged; static string _baseUriString; static Uri _baseUri; - static EventHandler _onLocationChanged; - static bool _hasEnabledNavigationInterception; - /// - /// An event that fires when the navigation location has changed. - /// - public static event EventHandler OnLocationChanged + /// + public event EventHandler OnLocationChanged { add { @@ -38,63 +41,34 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing } } - /// - /// Gets the URI prefix that can be prepended before URI paths to produce an absolute URI. - /// Typically this corresponds to the 'href' attribute on the document's <base> element. - /// - /// The URI prefix. - public static string GetBaseUriPrefix() + /// + public string GetBaseUriPrefix() { EnsureBaseUriPopulated(); return _baseUriString; } - private static void EnsureBaseUriPopulated() - { - // The is fixed for the lifetime of the page, so just cache it - if (_baseUriString == null) - { - var baseUri = RegisteredFunction.InvokeUnmarshalled( - $"{_functionPrefix}.getBaseURI"); - _baseUriString = ToBaseUriPrefix(baseUri); - _baseUri = new Uri(_baseUriString); - } - } - - /// - /// Gets the browser's current absolute URI. - /// - /// The browser's current absolute URI. - public static string GetAbsoluteUri() + /// + public string GetAbsoluteUri() { if (_currentAbsoluteUri == null) { _currentAbsoluteUri = RegisteredFunction.InvokeUnmarshalled( - $"{_functionPrefix}.getLocationHref"); + $"{_functionPrefix}.getLocationHref"); } return _currentAbsoluteUri; } - /// - /// Converts a relative URI into an absolute one. - /// - /// The relative URI. - /// The absolute URI. - public static Uri ToAbsoluteUri(string relativeUri) + /// + public Uri ToAbsoluteUri(string relativeUri) { EnsureBaseUriPopulated(); return new Uri(_baseUri, relativeUri); } - /// - /// Given a base URI prefix (e.g., one previously returned by ), - /// converts an absolute URI into one relative to the base URI prefix. - /// - /// The base URI prefix (e.g., previously returned by ). - /// An absolute URI that is within the space of the base URI prefix. - /// A relative URI path. - public static string ToBaseRelativePath(string baseUriPrefix, string absoluteUri) + /// + public string ToBaseRelativePath(string baseUriPrefix, string absoluteUri) { if (absoluteUri.Equals(baseUriPrefix, StringComparison.Ordinal)) { @@ -117,6 +91,18 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing throw new ArgumentException($"The URI '{absoluteUri}' is not contained by the base URI '{baseUriPrefix}'."); } + private static void EnsureBaseUriPopulated() + { + // The is fixed for the lifetime of the page, so just cache it + if (_baseUriString == null) + { + var baseUri = RegisteredFunction.InvokeUnmarshalled( + $"{_functionPrefix}.getBaseURI"); + _baseUriString = ToBaseUriPrefix(baseUri); + _baseUri = new Uri(_baseUriString); + } + } + private static void NotifyLocationChanged(string newAbsoluteUri) { _currentAbsoluteUri = newAbsoluteUri; diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Services/DefaultBrowserServiceProvider.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Services/DefaultBrowserServiceProvider.cs index 0bfa8d0a9d..1dd43e67de 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Services/DefaultBrowserServiceProvider.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Services/DefaultBrowserServiceProvider.cs @@ -1,6 +1,7 @@ // 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.Blazor.Services; using Microsoft.Extensions.DependencyInjection; using System; @@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services private void AddDefaultServices(ServiceCollection serviceCollection) { - // TODO: Add default services for HttpClient, IUrlHelper, etc. + serviceCollection.AddSingleton(new BrowserUriHelper()); } } } diff --git a/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs b/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs new file mode 100644 index 0000000000..12e8fd0dad --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs @@ -0,0 +1,48 @@ +// 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.Blazor.Services +{ + /// + /// Helpers for working with URIs and navigation state. + /// + public interface IUriHelper + { + /// + /// Gets the current absolute URI. + /// + /// The browser's current absolute URI. + string GetAbsoluteUri(); + + /// + /// An event that fires when the navigation location has changed. + /// + event EventHandler OnLocationChanged; + + /// + /// Converts a relative URI into an absolute one (by resolving it + /// relative to the current absolute URI). + /// + /// The relative URI. + /// The absolute URI. + Uri ToAbsoluteUri(string href); + + /// + /// Gets the URI prefix that can be prepended before URI paths to produce an absolute URI. + /// Typically this corresponds to the 'href' attribute on the document's <base> element. + /// + /// The URI prefix. + string GetBaseUriPrefix(); + + /// + /// Given a base URI prefix (e.g., one previously returned by ), + /// converts an absolute URI into one relative to the base URI prefix. + /// + /// The base URI prefix (e.g., previously returned by ). + /// An absolute URI that is within the space of the base URI prefix. + /// A relative URI path. + string ToBaseRelativePath(string baseUriPrefix, string locationAbsolute); + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs b/test/Microsoft.AspNetCore.Blazor.Browser.Test/BrowserUriHelperTest.cs similarity index 81% rename from test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs rename to test/Microsoft.AspNetCore.Blazor.Browser.Test/BrowserUriHelperTest.cs index 12261e1154..5c9177d541 100644 --- a/test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Browser.Test/BrowserUriHelperTest.cs @@ -1,14 +1,16 @@ // 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.Blazor.Browser.Routing; +using Microsoft.AspNetCore.Blazor.Browser.Services; using System; using Xunit; namespace Microsoft.AspNetCore.Blazor.Browser.Test { - public class UriHelperTest + public class BrowserUriHelperTest { + private BrowserUriHelper _browserUriHelper = new BrowserUriHelper(); + [Theory] [InlineData("scheme://host/", "scheme://host")] [InlineData("scheme://host:123/", "scheme://host:123")] @@ -17,7 +19,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test [InlineData("scheme://host/path/page?query=string&another=here", "scheme://host/path")] public void ComputesCorrectBaseUriPrefix(string baseUri, string expectedResult) { - var actualResult = UriHelper.ToBaseUriPrefix(baseUri); + var actualResult = BrowserUriHelper.ToBaseUriPrefix(baseUri); Assert.Equal(expectedResult, actualResult); } @@ -29,7 +31,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test [InlineData("scheme://host/path", "scheme://host/path", "/")] public void ComputesCorrectValidBaseRelativePaths(string baseUriPrefix, string absoluteUri, string expectedResult) { - var actualResult = UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); + var actualResult = _browserUriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); Assert.Equal(expectedResult, actualResult); } @@ -40,7 +42,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Test { var ex = Assert.Throws(() => { - UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); + _browserUriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); }); Assert.Equal(