diff --git a/Blazor.sln b/Blazor.sln index 01b0045712..7ac8f8f225 100644 --- a/Blazor.sln +++ b/Blazor.sln @@ -66,6 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngleSharpBuilder", "src\an EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Razor.Extensions", "src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj", "{D652A019-B765-4922-B7B8-3AB1C58338D7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Blazor.Browser.Test", "test\Microsoft.AspNetCore.Blazor.Browser.Test\Microsoft.AspNetCore.Blazor.Browser.Test.csproj", "{EC2A38BF-6E77-4A8E-A731-15929544F29C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -160,6 +162,10 @@ Global {D652A019-B765-4922-B7B8-3AB1C58338D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {D652A019-B765-4922-B7B8-3AB1C58338D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {D652A019-B765-4922-B7B8-3AB1C58338D7}.Release|Any CPU.Build.0 = Release|Any CPU + {EC2A38BF-6E77-4A8E-A731-15929544F29C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC2A38BF-6E77-4A8E-A731-15929544F29C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC2A38BF-6E77-4A8E-A731-15929544F29C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC2A38BF-6E77-4A8E-A731-15929544F29C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -192,6 +198,7 @@ Global {4E3BD19A-6159-4548-A88F-700443E6D934} = {B867E038-B3CE-43E3-9292-61568C46CDEB} {36706AC2-C851-4038-B161-9C1E44B668C8} = {4E3BD19A-6159-4548-A88F-700443E6D934} {D652A019-B765-4922-B7B8-3AB1C58338D7} = {B867E038-B3CE-43E3-9292-61568C46CDEB} + {EC2A38BF-6E77-4A8E-A731-15929544F29C} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3} diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fe974f5074 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Browser.Test")] diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs index 647c29eab0..87fa2ba250 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/BrowserRouter.cs @@ -10,18 +10,35 @@ using Microsoft.AspNetCore.Blazor.RenderTree; namespace Microsoft.AspNetCore.Blazor.Browser.Routing { + /// + /// A component that displays whichever other component corresponds to the + /// browser's changing navigation state. + /// public class BrowserRouter : IComponent, IDisposable { RenderHandle _renderHandle; string _baseUriPrefix; string _locationAbsolute; + /// + /// Gets or sets the assembly that should be searched, along with its referenced + /// assemblies, for components matching the URI. + /// public Assembly AppAssembly { get; set; } + /// + /// Gets or sets the namespace prefix that should be prepended when searching + /// for matching components. + /// public string PagesNamespace { get; set; } + /// + /// Gets or sets the component name that will be used if the URI ends with + /// a slash. + /// public string DefaultComponentName { get; set; } = "Index"; + /// public void Init(RenderHandle renderHandle) { _renderHandle = renderHandle; @@ -32,12 +49,14 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing _locationAbsolute = UriHelper.GetAbsoluteUri(); } + /// public void SetParameters(ParameterCollection parameters) { parameters.AssignToProperties(this); Refresh(); } + /// public void Dispose() { UriHelper.OnLocationChanged -= OnLocationChanged; diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs index 1ab69d4f50..da02e965eb 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Routing/UriHelper.cs @@ -6,29 +6,59 @@ using System; namespace Microsoft.AspNetCore.Blazor.Browser.Routing { + // TODO: Make this not static, and wrap it in an interface that can be injected through DI. + // We can make EnableNavigationInteception private, and call it automatically when the any + // concrete instance is instantiated. + + /// + /// Helpers for working with URIs and navigation state. + /// public static class UriHelper { static readonly string _functionPrefix = typeof(UriHelper).FullName; + /// + /// An event that fires when the navigation location has changed. + /// public static event EventHandler OnLocationChanged; + /// + /// Prevents default navigation on all links whose href is inside the base URI space, + /// causing clicks on those links to trigger instead. + /// public static void EnableNavigationInteception() => RegisteredFunction.InvokeUnmarshalled( $"{_functionPrefix}.enableNavigationInteception"); + /// + /// 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() { var baseUri = RegisteredFunction.InvokeUnmarshalled( $"{_functionPrefix}.getBaseURI"); - return ToBaseURIPrefix(baseUri); + return ToBaseUriPrefix(baseUri); } + /// + /// Gets the browser's current absolute URI. + /// + /// The browser's current absolute URI. public static string GetAbsoluteUri() { return RegisteredFunction.InvokeUnmarshalled( $"{_functionPrefix}.getLocationHref"); } + /// + /// 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) { // The absolute URI must be of the form "{baseUriPrefix}/something", @@ -49,13 +79,14 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing => OnLocationChanged?.Invoke(null, newAbsoluteUri); /// - /// Given the href value from the document's element, returns the URI - /// prefix that can be prepended to URI paths to produce an absolute URI. + /// Given the document's document.baseURI value, returns the URI prefix + /// that can be prepended to URI paths to produce an absolute URI. /// This is computed by removing the final slash and any following characters. + /// Internal for tests. /// - /// The href value from a document's element. + /// The page's document.baseURI value. /// The URI prefix - private static string ToBaseURIPrefix(string baseUri) + internal static string ToBaseUriPrefix(string baseUri) { if (baseUri != null) { diff --git a/src/Microsoft.AspNetCore.Blazor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Blazor/Properties/AssemblyInfo.cs index fab4af4814..dd7d9672c0 100644 --- a/src/Microsoft.AspNetCore.Blazor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Blazor/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Test")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Browser.Test")] diff --git a/test/Microsoft.AspNetCore.Blazor.Browser.Test/Microsoft.AspNetCore.Blazor.Browser.Test.csproj b/test/Microsoft.AspNetCore.Blazor.Browser.Test/Microsoft.AspNetCore.Blazor.Browser.Test.csproj new file mode 100644 index 0000000000..edcacc0c6e --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Browser.Test/Microsoft.AspNetCore.Blazor.Browser.Test.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + + false + + + + + + + + + + + + + + + + + diff --git a/test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs b/test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs new file mode 100644 index 0000000000..24fe9f2419 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Browser.Test/UriHelperTest.cs @@ -0,0 +1,51 @@ +// 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 System; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Browser.Test +{ + public class UriHelperTest + { + [Theory] + [InlineData("scheme://host/", "scheme://host")] + [InlineData("scheme://host:123/", "scheme://host:123")] + [InlineData("scheme://host/path", "scheme://host")] + [InlineData("scheme://host/path/", "scheme://host/path")] + [InlineData("scheme://host/path/page?query=string&another=here", "scheme://host/path")] + public void ComputesCorrectBaseUriPrefix(string baseUri, string expectedResult) + { + var actualResult = UriHelper.ToBaseUriPrefix(baseUri); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData("scheme://host", "scheme://host/", "/")] + [InlineData("scheme://host", "scheme://host/path", "/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) + { + var actualResult = UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData("scheme://host", "otherscheme://host/")] // Mismatched prefix is error + [InlineData("scheme://host", "scheme://otherhost/")] // Mismatched prefix is error + [InlineData("scheme://host/path", "scheme://host/path")] // URI isn't within base URI space + public void ThrowsForInvalidBaseRelativePaths(string baseUriPrefix, string absoluteUri) + { + var ex = Assert.Throws(() => + { + UriHelper.ToBaseRelativePath(baseUriPrefix, absoluteUri); + }); + + Assert.Equal( + $"The URI '{absoluteUri}' is not contained by the base URI '{baseUriPrefix}'.", + ex.Message); + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.Test/Microsoft.AspNetCore.Blazor.Test.csproj b/test/Microsoft.AspNetCore.Blazor.Test/Microsoft.AspNetCore.Blazor.Test.csproj index 64a966b165..406cb29513 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/Microsoft.AspNetCore.Blazor.Test.csproj +++ b/test/Microsoft.AspNetCore.Blazor.Test/Microsoft.AspNetCore.Blazor.Test.csproj @@ -17,7 +17,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Blazor.Test/Helpers/AutoRenderComponent.cs b/test/shared/AutoRenderComponent.cs similarity index 100% rename from test/Microsoft.AspNetCore.Blazor.Test/Helpers/AutoRenderComponent.cs rename to test/shared/AutoRenderComponent.cs diff --git a/test/Microsoft.AspNetCore.Blazor.Test/Helpers/CapturedBatch.cs b/test/shared/CapturedBatch.cs similarity index 96% rename from test/Microsoft.AspNetCore.Blazor.Test/Helpers/CapturedBatch.cs rename to test/shared/CapturedBatch.cs index 95356ed51b..c11601daa5 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/Helpers/CapturedBatch.cs +++ b/test/shared/CapturedBatch.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Blazor.RenderTree; -namespace Microsoft.AspNetCore.Blazor.Test +namespace Microsoft.AspNetCore.Blazor.Test.Helpers { public class CapturedBatch { diff --git a/test/Microsoft.AspNetCore.Blazor.Test/Helpers/IComponentExtensions.cs b/test/shared/IComponentExtensions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Blazor.Test/Helpers/IComponentExtensions.cs rename to test/shared/IComponentExtensions.cs diff --git a/test/Microsoft.AspNetCore.Blazor.Test/Helpers/TestRenderer.cs b/test/shared/TestRenderer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Blazor.Test/Helpers/TestRenderer.cs rename to test/shared/TestRenderer.cs