Remove root directory customization options for Razor Pages areas

Fixes #7608
This commit is contained in:
Pranav K 2018-04-09 13:22:00 -07:00
parent 62272ad56a
commit 9cea47fa7d
12 changed files with 47 additions and 158 deletions

View File

@ -117,12 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
rootDirectory = rootDirectory + "/";
}
var areaRootDirectory = _pagesOptions.AreaRootDirectory;
if (!areaRootDirectory.EndsWith("/", StringComparison.Ordinal))
{
areaRootDirectory = areaRootDirectory + "/";
}
var areaRootDirectory = "/Areas/";
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
{
if (viewDescriptor.Item != null && !ChecksumValidator.IsItemValid(_razorProjectEngine.FileSystem, viewDescriptor.Item))

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@ -58,14 +57,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (razorPagesOptions.Value.AllowAreas)
{
var areaRootDirectory = razorPagesOptions.Value.AreaRootDirectory;
Debug.Assert(!string.IsNullOrEmpty(areaRootDirectory));
areaRootDirectory = areaRootDirectory.TrimEnd('/');
// Search pattern that matches all cshtml files under the Pages AreaRootDirectory
var areaRootSearchPattern = areaRootDirectory + "/**/*.cshtml";
var areaRootSearchPattern = "/Areas/**/*.cshtml";
var importFileAtAreaPagesRoot = areaRootDirectory + "/" + templateEngine.Options.ImportsFileName;
var importFileAtAreaPagesRoot = $"/Areas/{templateEngine.Options.ImportsFileName}";
var importPathsOutsideAreaPagesRoot = templateEngine.GetImportItems(importFileAtAreaPagesRoot)
.Select(item => item.FilePath);

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private static readonly Action<ILogger, string, string, Exception> _handlerMethodExecuted;
private static readonly Action<ILogger, object, Exception> _pageFilterShortCircuit;
private static readonly Action<ILogger, string, string[], Exception> _malformedPageDirective;
private static readonly Action<ILogger, string, string, string, string, Exception> _unsupportedAreaPath;
private static readonly Action<ILogger, string, Exception> _unsupportedAreaPath;
private static readonly Action<ILogger, Type, Exception> _notMostEffectiveFilter;
private static readonly Action<ILogger, string, string, string, Exception> _beforeExecutingMethodOnFilter;
private static readonly Action<ILogger, string, string, string, Exception> _afterExecutingMethodOnFilter;
@ -63,10 +63,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
2,
"{FilterType}: After executing {Method} on filter {Filter}.");
_unsupportedAreaPath = LoggerMessage.Define<string, string, string, string>(
_unsupportedAreaPath = LoggerMessage.Define<string>(
LogLevel.Warning,
1,
"The page at '{FilePath}' is located under the area root directory '{AreaRootDirectory}' but does not follow the path format '{AreaRootDirectory}{RootDirectory}/Directory/FileName.cshtml");
"The page at '{FilePath}' is located under the area root directory '/Areas/' but does not follow the path format '/Areas/AreaName/Pages/Directory/FileName.cshtml");
}
public static void ExecutingHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, object[] arguments)
@ -140,11 +140,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_notMostEffectiveFilter(logger, policyType, null);
}
public static void UnsupportedAreaPath(this ILogger logger, RazorPagesOptions options, string filePath)
public static void UnsupportedAreaPath(this ILogger logger, string filePath)
{
if (logger.IsEnabled(LogLevel.Warning))
{
_unsupportedAreaPath(logger, filePath, options.AreaRootDirectory, options.AreaRootDirectory, options.RootDirectory, null);
_unsupportedAreaPath(logger, filePath, null);
}
}
}

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_normalizedRootDirectory = NormalizeDirectory(options.RootDirectory);
_normalizedAreaRootDirectory = NormalizeDirectory(options.AreaRootDirectory);
_normalizedAreaRootDirectory = "/Areas/";
}
public PageRouteModel CreateRouteModel(string relativePath, string routeTemplate)
@ -86,6 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
// path = "/Areas/Products/Pages/Manage/Home.cshtml"
// Result ("Products", "/Manage/Home")
const string AreaPagesRoot = "/Pages/";
result = default;
Debug.Assert(relativePath.StartsWith("/", StringComparison.Ordinal));
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
areaRootEndIndex >= relativePath.Length - 1 || // There's at least one token after the area root.
!relativePath.StartsWith(_normalizedAreaRootDirectory, StringComparison.OrdinalIgnoreCase)) // The path must start with area root.
{
_logger.UnsupportedAreaPath(_options, relativePath);
_logger.UnsupportedAreaPath(relativePath);
return false;
}
@ -103,35 +104,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var areaEndIndex = relativePath.IndexOf('/', startIndex: areaRootEndIndex + 1);
if (areaEndIndex == -1 || areaEndIndex == relativePath.Length)
{
_logger.UnsupportedAreaPath(_options, relativePath);
_logger.UnsupportedAreaPath(relativePath);
return false;
}
var areaName = relativePath.Substring(areaRootEndIndex + 1, areaEndIndex - areaRootEndIndex - 1);
string viewEnginePath;
if (_options.RootDirectory == "/")
// Ensure the next token is the "Pages" directory
if (string.Compare(relativePath, areaEndIndex, AreaPagesRoot, 0, AreaPagesRoot.Length, StringComparison.OrdinalIgnoreCase) != 0)
{
// When RootDirectory is "/", every thing past the area name is the page path.
Debug.Assert(relativePath.EndsWith(RazorViewEngine.ViewExtension), $"{relativePath} does not end in extension '{RazorViewEngine.ViewExtension}'.");
viewEnginePath = relativePath.Substring(areaEndIndex, relativePath.Length - areaEndIndex - RazorViewEngine.ViewExtension.Length);
_logger.UnsupportedAreaPath(relativePath);
return false;
}
else
{
// Normalize the pages root directory so that it has a trailing slash. This ensures we're looking at a directory delimiter
// and not just the area name occuring as part of a segment.
Debug.Assert(_options.RootDirectory.StartsWith("/", StringComparison.Ordinal));
// If the pages root has a value i.e. it's not the app root "/", ensure that the area path contains this value.
if (string.Compare(relativePath, areaEndIndex, _normalizedRootDirectory, 0, _normalizedRootDirectory.Length, StringComparison.OrdinalIgnoreCase) != 0)
{
_logger.UnsupportedAreaPath(_options, relativePath);
return false;
}
// Include the trailing slash of the root directory at the start of the viewEnginePath
var pageNameIndex = areaEndIndex + _normalizedRootDirectory.Length - 1;
viewEnginePath = relativePath.Substring(pageNameIndex, relativePath.Length - pageNameIndex - RazorViewEngine.ViewExtension.Length);
}
// Include the trailing slash of the root directory at the start of the viewEnginePath
var pageNameIndex = areaEndIndex + AreaPagesRoot.Length - 1;
var viewEnginePath = relativePath.Substring(pageNameIndex, relativePath.Length - pageNameIndex - RazorViewEngine.ViewExtension.Length);
result = (areaName, viewEnginePath);
return true;

View File

@ -36,10 +36,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
options.PageViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
Debug.Assert(!string.IsNullOrEmpty(_pagesOptions.AreaRootDirectory));
var areaDirectory = CombinePath(_pagesOptions.AreaRootDirectory, "{2}");
var areaDirectory = CombinePath("/Areas/", "{2}");
// Areas/{2}/Pages/
var areaPagesDirectory = CombinePath(areaDirectory, rootDirectory);
var areaPagesDirectory = CombinePath(areaDirectory, "/Pages/");
// Areas/{2}/Pages/{1}/{0}.cshtml
// Areas/{2}/Pages/Shared/{0}.cshtml

View File

@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RazorProjectPageRouteModelProvider : IPageRouteModelProvider
{
private const string AreaRootDirectory = "/Areas/";
private readonly RazorProjectFileSystem _razorFileSystem;
private readonly RazorPagesOptions _pagesOptions;
private readonly PageRouteModelFactory _routeModelFactory;
@ -54,12 +55,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private void AddPageModels(PageRouteModelProviderContext context)
{
var normalizedAreaRootDirectory = _pagesOptions.AreaRootDirectory;
if (!normalizedAreaRootDirectory.EndsWith("/", StringComparison.Ordinal))
{
normalizedAreaRootDirectory += "/";
}
foreach (var item in _razorFileSystem.EnumerateItems(_pagesOptions.RootDirectory))
{
if (!IsRouteable(item))
@ -84,11 +79,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
continue;
}
if (_pagesOptions.AllowAreas && relativePath.StartsWith(normalizedAreaRootDirectory, StringComparison.OrdinalIgnoreCase))
if (_pagesOptions.AllowAreas && relativePath.StartsWith(AreaRootDirectory, StringComparison.OrdinalIgnoreCase))
{
// Ignore Razor pages that are under the area root directory when AllowAreas is enabled.
// Conforming page paths will be added by AddAreaPageModels.
_logger.UnsupportedAreaPath(_pagesOptions, relativePath);
_logger.UnsupportedAreaPath(relativePath);
continue;
}
@ -102,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private void AddAreaPageModels(PageRouteModelProviderContext context)
{
foreach (var item in _razorFileSystem.EnumerateItems(_pagesOptions.AreaRootDirectory))
foreach (var item in _razorFileSystem.EnumerateItems(AreaRootDirectory))
{
if (!IsRouteable(item))
{

View File

@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
private readonly ICompatibilitySwitch[] _switches;
private string _root = "/Pages";
private string _areasRoot = "/Areas";
public RazorPagesOptions()
{
@ -68,10 +67,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
/// <remarks>
/// <para>
/// When enabled, any Razor Page under the directory structure <c>/{AreaRootDirectory}/AreaName/{RootDirectory}/</c>
/// When enabled, any Razor Page under the directory structure <c>/Area/AreaName/Pages/</c>
/// will be associated with an area with the name <c>AreaName</c>.
/// <seealso cref="AreaRootDirectory"/>
/// <seealso cref="RootDirectory"/>
/// </para>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
@ -137,30 +134,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
set => _allowMappingHeadRequestsToGetHandler.Value = value;
}
/// <summary>
/// Application relative path used as the root of discovery for Razor Page files associated with areas.
/// Defaults to the <c>/Areas</c> directory under application root.
/// <seealso cref="AllowAreas" />
/// </summary>
public string AreaRootDirectory
{
get => _areasRoot;
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value));
}
if (value[0] != '/')
{
throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(value));
}
_areasRoot = value;
}
}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -214,15 +214,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange
var descriptors = new[]
{
CreateVersion_2_0_Descriptor("/Features/Products/Files/About.cshtml"),
CreateVersion_2_0_Descriptor("/Features/Products/Files/Manage/Index.cshtml"),
CreateVersion_2_0_Descriptor("/Features/Products/Files/Manage/Edit.cshtml", "{id}"),
CreateVersion_2_0_Descriptor("/Areas/Products/Files/About.cshtml"),
CreateVersion_2_0_Descriptor("/Areas/Products/Pages/About.cshtml"),
CreateVersion_2_0_Descriptor("/Areas/Products/Pages/Manage/Index.cshtml"),
CreateVersion_2_0_Descriptor("/Areas/Products/Pages/Manage/Edit.cshtml", "{id}"),
};
var options = new RazorPagesOptions
{
AllowAreas = true,
AreaRootDirectory = "/Features",
// Setting this value should not affect area page lookup.
RootDirectory = "/Files",
};
@ -237,7 +238,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
context.RouteModels,
result =>
{
Assert.Equal("/Features/Products/Files/About.cshtml", result.RelativePath);
Assert.Equal("/Areas/Products/Pages/About.cshtml", result.RelativePath);
Assert.Equal("/About", result.ViewEnginePath);
Assert.Collection(
result.Selectors,
@ -257,7 +258,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
},
result =>
{
Assert.Equal("/Features/Products/Files/Manage/Index.cshtml", result.RelativePath);
Assert.Equal("/Areas/Products/Pages/Manage/Index.cshtml", result.RelativePath);
Assert.Equal("/Manage/Index", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Products/Manage/Index", selector.AttributeRouteModel.Template),
@ -277,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
},
result =>
{
Assert.Equal("/Features/Products/Files/Manage/Edit.cshtml", result.RelativePath);
Assert.Equal("/Areas/Products/Pages/Manage/Edit.cshtml", result.RelativePath);
Assert.Equal("/Manage/Edit", result.ViewEnginePath);
Assert.Collection(
result.Selectors,
@ -341,6 +342,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange
var descriptors = new[]
{
CreateVersion_2_0_Descriptor("/Areas/Accounts/Pages/Manage/Home.cshtml"),
CreateVersion_2_0_Descriptor("/Areas/Accounts/Manage/Home.cshtml"),
CreateVersion_2_0_Descriptor("/Areas/About.cshtml"),
CreateVersion_2_0_Descriptor("/Contact.cshtml"),
@ -349,7 +351,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var options = new RazorPagesOptions
{
AllowAreas = true,
AreaRootDirectory = "/Areas",
RootDirectory = "/",
};
@ -364,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
context.RouteModels,
result =>
{
Assert.Equal("/Areas/Accounts/Manage/Home.cshtml", result.RelativePath);
Assert.Equal("/Areas/Accounts/Pages/Manage/Home.cshtml", result.RelativePath);
Assert.Equal("/Manage/Home", result.ViewEnginePath);
Assert.Collection(
result.Selectors,

View File

@ -87,34 +87,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
fileProvider.Verify(f => f.Watch("/Areas/**/*.cshtml"));
}
[Theory]
[InlineData("/areas-base-dir")]
[InlineData("/areas-base-dir/")]
public void GetChangeToken_WatchesFilesUnderCustomAreaRoot(string rootDirectory)
{
// Arrange
var fileProvider = new Mock<IFileProvider>();
fileProvider.Setup(f => f.Watch(It.IsAny<string>()))
.Returns(Mock.Of<IChangeToken>());
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
var templateEngine = new RazorTemplateEngine(
RazorEngine.Create(),
new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment));
var options = Options.Create(new RazorPagesOptions
{
AllowAreas = true,
AreaRootDirectory = rootDirectory,
});
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
// Act
var changeToken = changeProvider.GetChangeToken();
// Assert
fileProvider.Verify(f => f.Watch("/areas-base-dir/**/*.cshtml"));
}
[Fact]
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenPagesRootIsNested()
{
@ -140,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
[Fact]
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenAreaPagesRootIsNested()
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenAllowAreasIsSpecified()
{
// Arrange
var fileProvider = new TestFileProvider();
@ -152,7 +124,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
var options = Options.Create(new RazorPagesOptions());
options.Value.RootDirectory = "/dir1/dir2";
options.Value.AreaRootDirectory = "/dir3/dir4";
options.Value.AllowAreas = true;
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
@ -162,9 +133,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(compositeChangeToken.ChangeTokens,
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/_ViewImports.cshtml"), changeToken),
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir3/_ViewImports.cshtml"), changeToken),
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/dir2/**/*.cshtml"), changeToken),
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir3/dir4/**/*.cshtml"), changeToken));
changeToken => Assert.Same(fileProvider.GetChangeToken("/Areas/**/*.cshtml"), changeToken));
}
[Fact]

View File

@ -221,30 +221,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(expectedArea, result.areaName);
Assert.Equal(expectedViewEnginePath, result.viewEnginePath);
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
[InlineData("/Areas/MyArea/Dir1/Dir2/Index.cshtml", "MyArea", "/Index")]
[InlineData("/Areas/Accounts/Dir1/Dir2/Manage/Edit.cshtml", "Accounts", "/Manage/Edit")]
public void TryParseAreaPath_ParsesAreaPath_WithMultiLevelRootDirectory(
string path,
string expectedArea,
string expectedViewEnginePath)
{
// Arrange
var options = new RazorPagesOptions
{
RootDirectory = "/Dir1/Dir2"
};
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
// Act
var success = routeModelFactory.TryParseAreaPath(path, out var result);
// Assert
Assert.True(success);
Assert.Equal(expectedArea, result.areaName);
Assert.Equal(expectedViewEnginePath, result.viewEnginePath);
}
}
}

View File

@ -88,21 +88,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
[Fact]
public void Configure_WithCustomRoots_AddsAreaPageViewLocationFormats()
public void Configure_WithCustomRoot_AddsAreaPageViewLocationFormats()
{
// Arrange
var expected = new[]
{
"/Features/{2}/RazorFiles/{1}/{0}.cshtml",
"/Features/{2}/RazorFiles/Shared/{0}.cshtml",
"/Features/{2}/Views/Shared/{0}.cshtml",
"/Areas/{2}/Pages/{1}/{0}.cshtml",
"/Areas/{2}/Pages/Shared/{0}.cshtml",
"/Areas/{2}/Views/Shared/{0}.cshtml",
"/RazorFiles/Shared/{0}.cshtml",
"/Views/Shared/{0}.cshtml",
};
var razorPagesOptions = new RazorPagesOptions
{
AreaRootDirectory = "/Features",
RootDirectory = "/RazorFiles/",
};
var viewEngineOptions = GetViewEngineOptions();

View File

@ -155,15 +155,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
// Arrange
var fileSystem = new VirtualRazorProjectFileSystem();
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Categories.cshtml", "@page"));
// We shouldn't add a route for this.
fileSystem.Add(new TestRazorProjectItem("/Areas/Home.cshtml", "@page"));
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Categories.cshtml", "@page"));
fileSystem.Add(new TestRazorProjectItem("/About.cshtml", "@page"));
// We shouldn't add a route for the following paths.
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Categories.cshtml", "@page"));
fileSystem.Add(new TestRazorProjectItem("/Areas/Home.cshtml", "@page"));
var optionsManager = Options.Create(new RazorPagesOptions
{
RootDirectory = "/",
AreaRootDirectory = "/Areas",
AllowAreas = true,
});
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
@ -176,7 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Areas/Products/Categories.cshtml", model.RelativePath);
Assert.Equal("/Areas/Products/Pages/Categories.cshtml", model.RelativePath);
Assert.Equal("/Categories", model.ViewEnginePath);
Assert.Collection(model.Selectors,
selector => Assert.Equal("Products/Categories", selector.AttributeRouteModel.Template));