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