From bee1a55cff493cd0ee65a69e11c7aabb85131bd9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 13 Mar 2017 15:28:04 -0700 Subject: [PATCH] Use RazorPagesOptions.RootDirectory when looking for page hierarchies. Fixes #5915 --- .../Internal/PageActionInvokerProvider.cs | 8 ++- .../RazorPagesTest.cs | 30 ++++++++ .../RazorPagesWithBasePathTest.cs | 29 ++++++++ .../Internal/PageActionInvokerProviderTest.cs | 69 +++++++++++++++++-- .../TestOptionsManager.cs | 14 ++-- .../Pages/WithPageImport/Index.cshtml | 2 + .../Pages/WithPageImport/_PageImports.cshtml | 1 + .../Pages/WithPageStart/Index.cshtml | 2 + .../Pages/WithPageStart/_PageStart.cshtml | 1 + .../Services/CustomService.cs | 10 +++ 10 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 test/WebSites/RazorPagesWebSite/Pages/WithPageImport/Index.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/WithPageImport/_PageImports.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/WithPageStart/Index.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/WithPageStart/_PageStart.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Services/CustomService.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs index f13d7edc44..db34a9e27e 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs @@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private readonly IModelMetadataProvider _modelMetadataProvider; private readonly ITempDataDictionaryFactory _tempDataFactory; private readonly HtmlHelperOptions _htmlHelperOptions; + private readonly RazorPagesOptions _razorPagesOptions; private readonly IPageHandlerMethodSelector _selector; private readonly TempDataPropertyProvider _propertyProvider; private readonly RazorProject _razorProject; @@ -53,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal ITempDataDictionaryFactory tempDataFactory, IOptions mvcOptions, IOptions htmlHelperOptions, + IOptions razorPagesOptions, IPageHandlerMethodSelector selector, TempDataPropertyProvider propertyProvider, RazorProject razorProject, @@ -69,6 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal _modelMetadataProvider = modelMetadataProvider; _tempDataFactory = tempDataFactory; _htmlHelperOptions = htmlHelperOptions.Value; + _razorPagesOptions = razorPagesOptions.Value; _selector = selector; _propertyProvider = propertyProvider; _razorProject = razorProject; @@ -201,7 +204,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal internal List> GetPageStartFactories(CompiledPageActionDescriptor descriptor) { var pageStartFactories = new List>(); - var pageStartItems = _razorProject.FindHierarchicalItems(descriptor.ViewEnginePath, PageStartFileName); + var pageStartItems = _razorProject.FindHierarchicalItems( + _razorPagesOptions.RootDirectory, + descriptor.RelativePath, + PageStartFileName); foreach (var item in pageStartItems) { if (item.Exists) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 6e1ff83365..cef92cd86c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.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 System; using System.Linq; using System.Net; using System.Net.Http; @@ -248,6 +249,35 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("/Login?ReturnUrl=%2FHelloWorldWithAuth", response.Headers.Location.PathAndQuery); } + + [Fact] + public async Task PageStart_IsDiscoveredWhenRootDirectoryIsNotSpecified() + { + // Test for https://github.com/aspnet/Mvc/issues/5915 + //Arrange + var expected = $"Hello from _PageStart{Environment.NewLine}Hello from /Pages/WithPageStart/Index.cshtml!"; + + // Act + var response = await Client.GetStringAsync("/Pages/WithPageStart"); + + // Assert + Assert.Equal(expected, response.Trim()); + } + + [Fact] + public async Task PageImport_IsDiscoveredWhenRootDirectoryIsNotSpecified() + { + // Test for https://github.com/aspnet/Mvc/issues/5915 + //Arrange + var expected = "Hello from CustomService!"; + + // Act + var response = await Client.GetStringAsync("/Pages/WithPageImport"); + + // Assert + Assert.Equal(expected, response.Trim()); + } + private static string GetCookie(HttpResponseMessage response) { var setCookie = response.Headers.GetValues("Set-Cookie").ToArray(); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 13294a214c..a5aa8c0d1e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.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 System; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -113,5 +114,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuthFolder", response.Headers.Location.PathAndQuery); } + + [Fact] + public async Task PageStart_IsDiscoveredWhenRootDirectoryIsSpecified() + { + // Test for https://github.com/aspnet/Mvc/issues/5915 + //Arrange + var expected = $"Hello from _PageStart{Environment.NewLine}Hello from /Pages/WithPageStart/Index.cshtml!"; + + // Act + var response = await Client.GetStringAsync("/WithPageStart"); + + // Assert + Assert.Equal(expected, response.Trim()); + } + + [Fact] + public async Task PageImport_IsDiscoveredWhenRootDirectoryIsSpecified() + { + // Test for https://github.com/aspnet/Mvc/issues/5915 + //Arrange + var expected = "Hello from CustomService!"; + + // Act + var response = await Client.GetStringAsync("/WithPageImport"); + + // Assert + Assert.Equal(expected, response.Trim()); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index b4b4a82b4c..1b5a6b186e 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -12,12 +12,10 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Moq; @@ -574,7 +572,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Arrange var descriptor = new PageActionDescriptor() { - RelativePath = "Path1", + RelativePath = "/Views/Deeper/Index.cshtml", FilterDescriptors = new FilterDescriptor[0], ViewEnginePath = "/Views/Deeper/Index.cshtml" }; @@ -623,13 +621,72 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal mock.Verify(); } + [Theory] + [InlineData("/Pages/Level1/")] + [InlineData("/Pages/Level1")] + public void GetPageFactories_DoesNotFindPageStartsOutsideBaseDirectory(string rootDirectory) + { + // Arrange + var descriptor = new PageActionDescriptor() + { + RelativePath = "/Pages/Level1/Level2/Index.cshtml", + FilterDescriptors = new FilterDescriptor[0], + ViewEnginePath = "/Pages/Level1/Level2/Index.cshtml" + }; + var compiledPageDescriptor = new CompiledPageActionDescriptor(descriptor) + { + PageTypeInfo = typeof(object).GetTypeInfo(), + }; + var loader = new Mock(); + loader.Setup(l => l.Load(It.IsAny())) + .Returns(compiledPageDescriptor); + var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1); + var actionDescriptorProvider = new Mock(); + actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection); + + var fileProvider = new TestFileProvider(); + fileProvider.AddFile("/_PageStart.cshtml", "page content"); + fileProvider.AddFile("/Pages/_PageStart.cshtml", "page content"); + fileProvider.AddFile("/Pages/Level1/_PageStart.cshtml", "page content"); + fileProvider.AddFile("/Pages/Level1/Level2/_PageStart.cshtml", "page content"); + fileProvider.AddFile("/Pages/Level1/Level3/_PageStart.cshtml", "page content"); + + var razorProject = new TestRazorProject(fileProvider); + + var mock = new Mock(MockBehavior.Strict); + mock.Setup(p => p.CreateFactory("/Pages/Level1/Level2/_PageStart.cshtml")) + .Returns(new RazorPageFactoryResult(() => null, new List())) + .Verifiable(); + mock.Setup(p => p.CreateFactory("/Pages/Level1/_PageStart.cshtml")) + .Returns(new RazorPageFactoryResult(() => null, new List())) + .Verifiable(); + var razorPageFactoryProvider = mock.Object; + var options = new RazorPagesOptions + { + RootDirectory = rootDirectory, + }; + + var invokerProvider = CreateInvokerProvider( + loader.Object, + actionDescriptorProvider.Object, + razorPageFactoryProvider: razorPageFactoryProvider, + razorProject: razorProject, + razorPagesOptions: options); + + // Act + var factories = invokerProvider.GetPageStartFactories(compiledPageDescriptor); + + // Assert + mock.Verify(); + } + [Fact] public void GetPageStartFactories_NoFactoriesForMissingFiles() { // Arrange var descriptor = new PageActionDescriptor() { - RelativePath = "Path1", + RelativePath = "/Views/Deeper/Index.cshtml", FilterDescriptors = new FilterDescriptor[0], ViewEnginePath = "/Views/Deeper/Index.cshtml" }; @@ -692,7 +749,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal IPageFactoryProvider pageProvider = null, IPageModelFactoryProvider modelProvider = null, IRazorPageFactoryProvider razorPageFactoryProvider = null, - RazorProject razorProject = null) + RazorProject razorProject = null, + RazorPagesOptions razorPagesOptions = null) { var tempDataFactory = new Mock(); tempDataFactory.Setup(t => t.GetTempData(It.IsAny())) @@ -714,6 +772,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal tempDataFactory.Object, new TestOptionsManager(), new TestOptionsManager(), + new TestOptionsManager(razorPagesOptions ?? new RazorPagesOptions()), Mock.Of(), new TempDataPropertyProvider(), razorProject, diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestOptionsManager.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestOptionsManager.cs index 688f216b62..212d184812 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestOptionsManager.cs +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestOptionsManager.cs @@ -1,17 +1,23 @@ // 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.Linq; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc { - public class TestOptionsManager : OptionsManager - where T : class, new() + public class TestOptionsManager : IOptions + where TOptions : class, new() { public TestOptionsManager() - : base(Enumerable.Empty>()) + : this(new TOptions()) { } + + public TestOptionsManager(TOptions value) + { + Value = value; + } + + public TOptions Value { get; } } } diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/Index.cshtml b/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/Index.cshtml new file mode 100644 index 0000000000..d5e4338713 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/Index.cshtml @@ -0,0 +1,2 @@ +@page +Hello from @CustomService.Value! \ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/_PageImports.cshtml b/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/_PageImports.cshtml new file mode 100644 index 0000000000..2331a35a67 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/WithPageImport/_PageImports.cshtml @@ -0,0 +1 @@ +@using CustomNamespace \ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/Index.cshtml b/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/Index.cshtml new file mode 100644 index 0000000000..5f8139efb5 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/Index.cshtml @@ -0,0 +1,2 @@ +@page +Hello from @Path! \ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/_PageStart.cshtml b/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/_PageStart.cshtml new file mode 100644 index 0000000000..65a09e5bb1 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/WithPageStart/_PageStart.cshtml @@ -0,0 +1 @@ +Hello from _PageStart diff --git a/test/WebSites/RazorPagesWebSite/Services/CustomService.cs b/test/WebSites/RazorPagesWebSite/Services/CustomService.cs new file mode 100644 index 0000000000..23dbf1c0ba --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Services/CustomService.cs @@ -0,0 +1,10 @@ +// 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. + +namespace CustomNamespace +{ + public static class CustomService + { + public static string Value => nameof(CustomService); + } +}