diff --git a/src/Components/Server/src/Circuits/CircuitPrerenderer.cs b/src/Components/Server/src/Circuits/CircuitPrerenderer.cs index bd1ea15ef7..ecec783a9a 100644 --- a/src/Components/Server/src/Circuits/CircuitPrerenderer.cs +++ b/src/Components/Server/src/Circuits/CircuitPrerenderer.cs @@ -65,7 +65,16 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private string GetFullBaseUri(HttpRequest request) { - return UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase); + var result = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase); + + // PathBase may be "/" or "/some/thing", but to be a well-formed base URI + // it has to end with a trailing slash + if (!result.EndsWith('/')) + { + result += '/'; + } + + return result; } } } diff --git a/src/Components/Server/test/Circuits/CircuitPrerendererTest.cs b/src/Components/Server/test/Circuits/CircuitPrerendererTest.cs new file mode 100644 index 0000000000..cf6c0c3929 --- /dev/null +++ b/src/Components/Server/test/Circuits/CircuitPrerendererTest.cs @@ -0,0 +1,132 @@ +// 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.Server.Circuits; +using Microsoft.AspNetCore.Components.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Server.Tests.Circuits +{ + public class CircuitPrerendererTest + { + // Because CircuitPrerenderer is a point of integration with HttpContext, + // it's not a good candidate for unit testing. The majority of prerendering + // unit tests should be elsewhere in HtmlRendererTests inside the + // Microsoft.AspNetCore.Components.Tests projects. + // + // The only unit tests added here should specifically be about how we're + // interacting with the HttpContext for configuring the prerenderer. + + [Fact] + public async Task ExtractsUriFromHttpContext_EmptyPathBase() + { + // Arrange + var circuitFactory = new TestCircuitFactory(); + var circuitPrerenderer = new CircuitPrerenderer(circuitFactory); + var httpContext = new Mock(); + var httpRequest = new Mock().SetupAllProperties(); + httpContext.Setup(h => h.Request).Returns(httpRequest.Object); + httpRequest.Object.Scheme = "https"; + httpRequest.Object.Host = new HostString("example.com", 1234); + httpRequest.Object.Path = "/some/path"; + + var prerenderingContext = new ComponentPrerenderingContext + { + ComponentType = typeof(UriDisplayComponent), + Parameters = ParameterCollection.Empty, + Context = httpContext.Object + }; + + // Act + var result = await circuitPrerenderer.PrerenderComponentAsync(prerenderingContext); + + // Assert + Assert.Equal(new[] + { + "The current URI is ", + "https://example.com:1234/some/path", + " within base URI ", + "https://example.com:1234/" + }, result); + } + + [Fact] + public async Task ExtractsUriFromHttpContext_NonemptyPathBase() + { + // Arrange + var circuitFactory = new TestCircuitFactory(); + var circuitPrerenderer = new CircuitPrerenderer(circuitFactory); + var httpContext = new Mock(); + var httpRequest = new Mock().SetupAllProperties(); + httpContext.Setup(h => h.Request).Returns(httpRequest.Object); + httpRequest.Object.Scheme = "https"; + httpRequest.Object.Host = new HostString("example.com", 1234); + httpRequest.Object.PathBase = "/my/dir"; + httpRequest.Object.Path = "/some/path"; + + var prerenderingContext = new ComponentPrerenderingContext + { + ComponentType = typeof(UriDisplayComponent), + Parameters = ParameterCollection.Empty, + Context = httpContext.Object + }; + + // Act + var result = await circuitPrerenderer.PrerenderComponentAsync(prerenderingContext); + + // Assert + Assert.Equal(new[] + { + "The current URI is ", + "https://example.com:1234/my/dir/some/path", + " within base URI ", + "https://example.com:1234/my/dir/" + }, result); + } + + class TestCircuitFactory : CircuitFactory + { + public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute) + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(_ => + { + var uriHelper = new RemoteUriHelper(); + uriHelper.Initialize(uriAbsolute, baseUriAbsolute); + return uriHelper; + }); + var serviceScope = serviceCollection.BuildServiceProvider().CreateScope(); + return TestCircuitHost.Create(serviceScope); + } + } + + class UriDisplayComponent : IComponent + { + private RenderHandle _renderHandle; + + [Inject] IUriHelper UriHelper { get; set; } + + public void Configure(RenderHandle renderHandle) + { + _renderHandle = renderHandle; + } + + public Task SetParametersAsync(ParameterCollection parameters) + { + _renderHandle.Render(builder => + { + builder.AddContent(0, "The current URI is "); + builder.AddContent(1, UriHelper.GetAbsoluteUri()); + builder.AddContent(2, " within base URI "); + builder.AddContent(3, UriHelper.GetBaseUri()); + }); + + return Task.CompletedTask; + } + } + } +} diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index e2246b30e7..c07fb92932 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits if (remoteRenderer == null) { remoteRenderer = new RemoteRenderer( - Mock.Of(), + serviceScope.ServiceProvider ?? Mock.Of(), new RendererRegistry(), jsRuntime, clientProxy,