From f8e315d03da5a46badaf53a0628c9e9def289fc1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 26 Mar 2018 17:03:50 -0700 Subject: [PATCH] CompiledPageRouteModelProvider should de-dup descriptors Fixes #7543 --- .../CompiledPageRouteModelProvider.cs | 49 ++++++++++++--- .../RazorPagesWithBasePathTest.cs | 20 +++++++ .../CompiledPageRouteModelProviderTest.cs | 60 ++++++++++++++++++- .../Pages/ClassLibraryPages/Overriden.cshtml | 2 + .../Pages/ClassLibraryPages/Served.cshtml | 2 + .../RazorPagesClassLibrary.csproj | 11 ++++ .../Pages/ClassLibraryPages/Overriden.cshtml | 2 + .../RazorPagesWebSite.csproj | 1 + 8 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml create mode 100644 test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml create mode 100644 test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj create mode 100644 test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs index 7f87b10f41..06e899ddf8 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs @@ -58,22 +58,55 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } } - /// - /// Gets the sequence of from . - /// - /// The s - /// The sequence of . - protected virtual IEnumerable GetViewDescriptors(ApplicationPartManager applicationManager) + private IEnumerable GetViewDescriptors(ApplicationPartManager applicationManager) { if (applicationManager == null) { throw new ArgumentNullException(nameof(applicationManager)); } + var viewsFeature = GetViewFeature(applicationManager); + + var visited = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var viewDescriptor in viewsFeature.ViewDescriptors) + { + if (!visited.Add(viewDescriptor.RelativePath)) + { + // Already seen an descriptor with a higher "order" + continue; + } + + if (!viewDescriptor.IsPrecompiled) + { + continue; + } + + if (IsRazorPage(viewDescriptor)) + { + yield return viewDescriptor; + } + } + + bool IsRazorPage(CompiledViewDescriptor viewDescriptor) + { + if (viewDescriptor.Item != null) + { + return viewDescriptor.Item.Kind == RazorPageDocumentClassifierPass.RazorPageDocumentKind; + } + else if (viewDescriptor.ViewAttribute != null) + { + return viewDescriptor.ViewAttribute is RazorPageAttribute; + } + + return false; + } + } + + protected virtual ViewsFeature GetViewFeature(ApplicationPartManager applicationManager) + { var viewsFeature = new ViewsFeature(); applicationManager.PopulateFeature(viewsFeature); - - return viewsFeature.ViewDescriptors.Where(d => d.IsPrecompiled && d.ViewAttribute is RazorPageAttribute); + return viewsFeature; } private void CreateModels(PageRouteModelProviderContext context) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 3fdebf7eb0..d465d58b02 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -484,5 +484,25 @@ Hello from /Pages/Shared/"; // Assert Assert.Contains("Name is required", response); } + + [Fact] + public async Task PagesFromClassLibraries_CanBeServed() + { + // Act + var response = await Client.GetStringAsync("/ClassLibraryPages/Served"); + + // Assert + Assert.Contains("This page is served from RazorPagesClassLibrary", response); + } + + [Fact] + public async Task PagesFromClassLibraries_CanBeOverriden() + { + // Act + var response = await Client.GetStringAsync("/ClassLibraryPages/Overriden"); + + // Assert + Assert.Contains("This page is overriden by RazorPagesWebSite", response); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs index 8bf1090f43..2a66a9e08c 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs @@ -518,6 +518,53 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }); } + [Fact] + public void OnProvidersExecuting_UsesTheFirstDescriptorForEachPath() + { + // ViewsFeature may contain duplicate entries for the same Page - for instance when an app overloads a library's views. + // It picks the first entry for each path. In the ordinary case, this should ensure that the app's Razor Pages are prefered + // to a Razor Page added by a library. + + // Arrange + var descriptors = new[] + { + // Page coming from the app + CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[] + { + new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"), + }), + CreateVersion_2_1_Descriptor("/Pages/Home.cshtml", metadata: new object[] + { + new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/Index.cshtml"), + }), + // Page coming from the app + CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[] + { + new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"), + }), + }; + + var provider = CreateProvider(descriptors: descriptors); + var context = new PageRouteModelProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection( + context.RouteModels, + result => + { + Assert.Equal("/Pages/About.cshtml", result.RelativePath); + Assert.Equal("/About", result.ViewEnginePath); + }, + result => + { + Assert.Equal("/Pages/Home.cshtml", result.RelativePath); + Assert.Equal("/Home", result.ViewEnginePath); + }); + } + [Fact] public void GetRouteTemplate_ReturnsPathFromRazorPageAttribute() { @@ -602,6 +649,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { return new CompiledViewDescriptor { + IsPrecompiled = true, RelativePath = path, ViewAttribute = new RazorPageAttribute(path, typeof(object), routeTemplate), }; @@ -613,6 +661,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { return new CompiledViewDescriptor { + IsPrecompiled = true, RelativePath = path, Item = new TestRazorCompiledItem(typeof(object), "mvc.1.0.razor-page", path, metadata ?? Array.Empty()), }; @@ -631,7 +680,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public List Descriptors { get; } = new List(); - protected override IEnumerable GetViewDescriptors(ApplicationPartManager applicationManager) => Descriptors; + protected override ViewsFeature GetViewFeature(ApplicationPartManager applicationManager) + { + var feature = new ViewsFeature(); + foreach (var descriptor in Descriptors) + { + feature.ViewDescriptors.Add(descriptor); + } + + return feature; + } } } } \ No newline at end of file diff --git a/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml b/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml new file mode 100644 index 0000000000..297cb03980 --- /dev/null +++ b/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml @@ -0,0 +1,2 @@ +@page +@{ throw new Exception("This page should be overriden by the application."); } diff --git a/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml b/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml new file mode 100644 index 0000000000..b58fb3c243 --- /dev/null +++ b/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml @@ -0,0 +1,2 @@ +@page +This page is served from RazorPagesClassLibrary diff --git a/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj b/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj new file mode 100644 index 0000000000..99900dc55d --- /dev/null +++ b/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml b/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml new file mode 100644 index 0000000000..18267bd84c --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml @@ -0,0 +1,2 @@ +@page +This page is overriden by RazorPagesWebSite diff --git a/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj b/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj index c20274818a..3d83f2342f 100644 --- a/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj +++ b/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj @@ -6,6 +6,7 @@ +