Refactor PageRouteModel generation
This commit is contained in:
parent
66c13ae5e5
commit
094b61dfc6
|
|
@ -1,2 +1,2 @@
|
|||
version:2.1.0-preview1-15661
|
||||
commithash:c9349d4c8a495d3085d9b879214d80f2f45e2193
|
||||
version:2.1.0-preview1-15670
|
||||
commithash:49176144e03c3015d83b21e3f1d0ce093c05ecc3
|
||||
|
|
|
|||
|
|
@ -22,37 +22,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private readonly RazorTemplateEngine _templateEngine;
|
||||
private readonly ILogger<CompiledPageRouteModelProvider> _logger;
|
||||
private readonly PageRouteModelFactory _routeModelFactory;
|
||||
|
||||
public CompiledPageRouteModelProvider(
|
||||
ApplicationPartManager applicationManager,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor,
|
||||
RazorTemplateEngine templateEngine,
|
||||
ILoggerFactory loggerFactory)
|
||||
ILogger<CompiledPageRouteModelProvider> logger)
|
||||
{
|
||||
if (applicationManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationManager));
|
||||
}
|
||||
|
||||
if (pagesOptionsAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pagesOptionsAccessor));
|
||||
}
|
||||
|
||||
if (templateEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(templateEngine));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_applicationManager = applicationManager;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
_templateEngine = templateEngine;
|
||||
_logger = loggerFactory.CreateLogger<CompiledPageRouteModelProvider>();
|
||||
_applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
||||
_pagesOptions = pagesOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(pagesOptionsAccessor));
|
||||
_templateEngine = templateEngine ?? throw new ArgumentNullException(nameof(templateEngine));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(templateEngine));
|
||||
_routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger);
|
||||
}
|
||||
|
||||
public int Order => -1000;
|
||||
|
|
@ -64,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
CreateModels(context.RouteModels);
|
||||
CreateModels(context);
|
||||
}
|
||||
|
||||
public void OnProvidersExecuted(PageRouteModelProviderContext context)
|
||||
|
|
@ -75,7 +57,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private void CreateModels(IList<PageRouteModel> results)
|
||||
/// <summary>
|
||||
/// Gets the sequence of <see cref="CompiledViewDescriptor"/> from <paramref name="applicationManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="applicationManager">The <see cref="ApplicationPartManager"/>s</param>
|
||||
/// <returns>The sequence of <see cref="CompiledViewDescriptor"/>.</returns>
|
||||
protected virtual IEnumerable<CompiledViewDescriptor> GetViewDescriptors(ApplicationPartManager applicationManager)
|
||||
{
|
||||
if (applicationManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationManager));
|
||||
}
|
||||
|
||||
var viewsFeature = new ViewsFeature();
|
||||
applicationManager.PopulateFeature(viewsFeature);
|
||||
|
||||
return viewsFeature.ViewDescriptors.Where(d => d.IsPrecompiled && d.ViewAttribute is RazorPageAttribute);
|
||||
}
|
||||
|
||||
private void CreateModels(PageRouteModelProviderContext context)
|
||||
{
|
||||
var rootDirectory = _pagesOptions.RootDirectory;
|
||||
if (!rootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
|
|
@ -97,84 +97,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
continue;
|
||||
}
|
||||
|
||||
PageRouteModel model = null;
|
||||
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
|
||||
PageRouteModel routeModel = null;
|
||||
|
||||
// When RootDirectory and AreaRootDirectory overlap (e.g. RootDirectory = '/', AreaRootDirectory = '/Areas'), we
|
||||
// only want to allow a page to be associated with the area route.
|
||||
if (_pagesOptions.AllowAreas && viewDescriptor.RelativePath.StartsWith(areaRootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
model = GetAreaPageRouteModel(areaRootDirectory, viewDescriptor);
|
||||
routeModel = _routeModelFactory.CreateAreaRouteModel(viewDescriptor.RelativePath, pageAttribute.RouteTemplate);
|
||||
}
|
||||
else if (viewDescriptor.RelativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
model = GetPageRouteModel(rootDirectory, viewDescriptor);
|
||||
routeModel = _routeModelFactory.CreateRouteModel(pageAttribute.Path, pageAttribute.RouteTemplate);
|
||||
}
|
||||
|
||||
if (model != null)
|
||||
if (routeModel != null)
|
||||
{
|
||||
results.Add(model);
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PageRouteModel GetPageRouteModel(string rootDirectory, CompiledViewDescriptor viewDescriptor)
|
||||
{
|
||||
var viewEnginePath = GetRootTrimmedPath(rootDirectory, viewDescriptor.RelativePath);
|
||||
if (viewEnginePath.EndsWith(RazorViewEngine.ViewExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
viewEnginePath = viewEnginePath.Substring(0, viewEnginePath.Length - RazorViewEngine.ViewExtension.Length);
|
||||
}
|
||||
|
||||
var model = new PageRouteModel(viewDescriptor.RelativePath, viewEnginePath);
|
||||
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
|
||||
PageSelectorModel.PopulateDefaults(model, viewEnginePath, pageAttribute.RouteTemplate);
|
||||
return model;
|
||||
}
|
||||
|
||||
private PageRouteModel GetAreaPageRouteModel(string areaRootDirectory, CompiledViewDescriptor viewDescriptor)
|
||||
{
|
||||
var rootTrimmedPath = GetRootTrimmedPath(areaRootDirectory, viewDescriptor.RelativePath);
|
||||
|
||||
if (PageSelectorModel.TryParseAreaPath(_pagesOptions, rootTrimmedPath, _logger, out var result))
|
||||
{
|
||||
var model = new PageRouteModel(viewDescriptor.RelativePath, result.viewEnginePath)
|
||||
{
|
||||
RouteValues = { ["area"] = result.areaName },
|
||||
};
|
||||
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
|
||||
PageSelectorModel.PopulateDefaults(model, result.pageRoute, pageAttribute.RouteTemplate);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
// We were unable to parse the path to match the format we expect /Areas/AreaName/Pages/PagePath.cshtml
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sequence of <see cref="CompiledViewDescriptor"/> from <paramref name="applicationManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="applicationManager">The <see cref="ApplicationPartManager"/>s</param>
|
||||
/// <returns>The sequence of <see cref="CompiledViewDescriptor"/>.</returns>
|
||||
protected virtual IEnumerable<CompiledViewDescriptor> GetViewDescriptors(ApplicationPartManager applicationManager)
|
||||
{
|
||||
if (applicationManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationManager));
|
||||
}
|
||||
|
||||
var viewsFeature = new ViewsFeature();
|
||||
applicationManager.PopulateFeature(viewsFeature);
|
||||
|
||||
return viewsFeature.ViewDescriptors.Where(d => d.IsPrecompiled && d.ViewAttribute is RazorPageAttribute);
|
||||
}
|
||||
|
||||
private string GetRootTrimmedPath(string rootDirectory, string path)
|
||||
{
|
||||
// rootDirectory = "/Pages/AllMyPages/"
|
||||
// path = "/Pages/AllMyPages/Home.cshtml"
|
||||
// Result = "/Home.cshtml"
|
||||
var startIndex = rootDirectory.Length - 1;
|
||||
return path.Substring(startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,196 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
internal class PageRouteModelFactory
|
||||
{
|
||||
private static readonly string IndexFileName = "Index" + RazorViewEngine.ViewExtension;
|
||||
private readonly RazorPagesOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _normalizedRootDirectory;
|
||||
private readonly string _normalizedAreaRootDirectory;
|
||||
|
||||
public PageRouteModelFactory(
|
||||
RazorPagesOptions options,
|
||||
ILogger logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
_normalizedRootDirectory = NormalizeDirectory(options.RootDirectory);
|
||||
_normalizedAreaRootDirectory = NormalizeDirectory(options.AreaRootDirectory);
|
||||
}
|
||||
|
||||
public PageRouteModel CreateRouteModel(string relativePath, string routeTemplate)
|
||||
{
|
||||
var viewEnginePath = GetViewEnginePath(_normalizedRootDirectory, relativePath);
|
||||
var routeModel = new PageRouteModel(relativePath, viewEnginePath);
|
||||
|
||||
PopulateRouteModel(routeModel, viewEnginePath, routeTemplate);
|
||||
|
||||
return routeModel;
|
||||
}
|
||||
|
||||
public PageRouteModel CreateAreaRouteModel(string relativePath, string routeTemplate)
|
||||
{
|
||||
if (!TryParseAreaPath(relativePath, out var areaResult))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var routeModel = new PageRouteModel(relativePath, areaResult.viewEnginePath);
|
||||
|
||||
var routePrefix = CreateAreaRoute(areaResult.areaName, areaResult.viewEnginePath);
|
||||
PopulateRouteModel(routeModel, routePrefix, routeTemplate);
|
||||
routeModel.RouteValues["area"] = areaResult.areaName;
|
||||
|
||||
return routeModel;
|
||||
}
|
||||
|
||||
private static void PopulateRouteModel(PageRouteModel model, string pageRoute, string routeTemplate)
|
||||
{
|
||||
model.RouteValues.Add("page", model.ViewEnginePath);
|
||||
|
||||
if (AttributeRouteModel.IsOverridePattern(routeTemplate))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(
|
||||
Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
|
||||
model.RelativePath));
|
||||
}
|
||||
|
||||
var selectorModel = CreateSelectorModel(pageRoute, routeTemplate);
|
||||
model.Selectors.Add(selectorModel);
|
||||
|
||||
var fileName = Path.GetFileName(model.RelativePath);
|
||||
if (string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// For pages ending in /Index.cshtml, we want to allow incoming routing, but
|
||||
// force outgoing routes to match to the path sans /Index.
|
||||
selectorModel.AttributeRouteModel.SuppressLinkGeneration = true;
|
||||
|
||||
var index = pageRoute.LastIndexOf('/');
|
||||
var parentDirectoryPath = index == -1 ?
|
||||
string.Empty :
|
||||
pageRoute.Substring(0, index);
|
||||
model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, routeTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal bool TryParseAreaPath(
|
||||
string relativePath,
|
||||
out (string areaName, string viewEnginePath) result)
|
||||
{
|
||||
// path = "/Areas/Products/Pages/Manage/Home.cshtml"
|
||||
// Result ("Products", "/Manage/Home")
|
||||
|
||||
result = default;
|
||||
Debug.Assert(relativePath.StartsWith("/", StringComparison.Ordinal));
|
||||
// Parse the area root directory.
|
||||
var areaRootEndIndex = relativePath.IndexOf('/', startIndex: 1);
|
||||
if (areaRootEndIndex == -1 ||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The first directory that follows the area root is the area name.
|
||||
var areaEndIndex = relativePath.IndexOf('/', startIndex: areaRootEndIndex + 1);
|
||||
if (areaEndIndex == -1 || areaEndIndex == relativePath.Length)
|
||||
{
|
||||
_logger.UnsupportedAreaPath(_options, relativePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
var areaName = relativePath.Substring(areaRootEndIndex + 1, areaEndIndex - areaRootEndIndex - 1);
|
||||
|
||||
string viewEnginePath;
|
||||
if (_options.RootDirectory == "/")
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
result = (areaName, viewEnginePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetViewEnginePath(string rootDirectory, string path)
|
||||
{
|
||||
// rootDirectory = "/Pages/AllMyPages/"
|
||||
// path = "/Pages/AllMyPages/Home.cshtml"
|
||||
// Result = "/Home"
|
||||
Debug.Assert(path.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase));
|
||||
Debug.Assert(path.EndsWith(RazorViewEngine.ViewExtension, StringComparison.OrdinalIgnoreCase));
|
||||
var startIndex = rootDirectory.Length - 1;
|
||||
var endIndex = path.Length - RazorViewEngine.ViewExtension.Length;
|
||||
return path.Substring(startIndex, endIndex - startIndex);
|
||||
}
|
||||
|
||||
private static string CreateAreaRoute(string areaName, string viewEnginePath)
|
||||
{
|
||||
// AreaName = Products, ViewEnginePath = /List/Categories
|
||||
// Result = /Products/List/Categories
|
||||
Debug.Assert(!string.IsNullOrEmpty(areaName));
|
||||
Debug.Assert(!string.IsNullOrEmpty(viewEnginePath));
|
||||
Debug.Assert(viewEnginePath.StartsWith("/", StringComparison.Ordinal));
|
||||
|
||||
var builder = new InplaceStringBuilder(1 + areaName.Length + viewEnginePath.Length);
|
||||
builder.Append('/');
|
||||
builder.Append(areaName);
|
||||
builder.Append(viewEnginePath);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static SelectorModel CreateSelectorModel(string prefix, string routeTemplate)
|
||||
{
|
||||
return new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeDirectory(string directory)
|
||||
{
|
||||
Debug.Assert(directory.StartsWith("/", StringComparison.Ordinal));
|
||||
if (directory.Length > 1 && !directory.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
return directory + "/";
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public static class PageSelectorModel
|
||||
{
|
||||
private static readonly string IndexFileName = "Index" + RazorViewEngine.ViewExtension;
|
||||
|
||||
public static void PopulateDefaults(PageRouteModel model, string pageRoute, string routeTemplate)
|
||||
{
|
||||
model.RouteValues.Add("page", model.ViewEnginePath);
|
||||
|
||||
if (AttributeRouteModel.IsOverridePattern(routeTemplate))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(
|
||||
Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
|
||||
model.RelativePath));
|
||||
}
|
||||
|
||||
var selectorModel = CreateSelectorModel(pageRoute, routeTemplate);
|
||||
model.Selectors.Add(selectorModel);
|
||||
|
||||
var fileName = Path.GetFileName(model.RelativePath);
|
||||
if (string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// For pages ending in /Index.cshtml, we want to allow incoming routing, but
|
||||
// force outgoing routes to match to the path sans /Index.
|
||||
selectorModel.AttributeRouteModel.SuppressLinkGeneration = true;
|
||||
|
||||
var index = pageRoute.LastIndexOf('/');
|
||||
var parentDirectoryPath = index == -1 ?
|
||||
string.Empty :
|
||||
pageRoute.Substring(0, index);
|
||||
model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, routeTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParseAreaPath(
|
||||
RazorPagesOptions razorPagesOptions,
|
||||
string path,
|
||||
ILogger logger,
|
||||
out (string areaName, string viewEnginePath, string pageRoute) result)
|
||||
{
|
||||
// path = "/Products/Pages/Manage/Home.cshtml"
|
||||
// Result = ("Products", "/Manage/Home", "/Products/Manage/Home")
|
||||
|
||||
result = default;
|
||||
Debug.Assert(path.StartsWith("/", StringComparison.Ordinal));
|
||||
|
||||
// 1. Parse the area name. This will be the first token we encounter.
|
||||
var areaEndIndex = path.IndexOf('/', startIndex: 1);
|
||||
if (areaEndIndex == -1 || areaEndIndex == path.Length)
|
||||
{
|
||||
logger.UnsupportedAreaPath(razorPagesOptions, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var areaName = path.Substring(1, areaEndIndex - 1);
|
||||
|
||||
string pageName;
|
||||
if (razorPagesOptions.RootDirectory == "/")
|
||||
{
|
||||
// When RootDirectory is "/", every thing past the area name is the page path.
|
||||
Debug.Assert(path.EndsWith(RazorViewEngine.ViewExtension), $"{path} does not end in extension '{RazorViewEngine.ViewExtension}'.");
|
||||
pageName = path.Substring(areaEndIndex, path.Length - areaEndIndex - RazorViewEngine.ViewExtension.Length);
|
||||
}
|
||||
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(razorPagesOptions.RootDirectory.StartsWith("/", StringComparison.Ordinal));
|
||||
var normalizedPagesRootDirectory = razorPagesOptions.RootDirectory.Substring(1);
|
||||
if (!normalizedPagesRootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
normalizedPagesRootDirectory += "/";
|
||||
}
|
||||
|
||||
Debug.Assert(normalizedPagesRootDirectory.Length > 0);
|
||||
// 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(path, areaEndIndex + 1, normalizedPagesRootDirectory, 0, normalizedPagesRootDirectory.Length, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
logger.UnsupportedAreaPath(razorPagesOptions, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var pageNameIndex = areaEndIndex + normalizedPagesRootDirectory.Length;
|
||||
pageName = path.Substring(pageNameIndex, path.Length - pageNameIndex - RazorViewEngine.ViewExtension.Length);
|
||||
}
|
||||
|
||||
var builder = new InplaceStringBuilder(areaEndIndex + pageName.Length);
|
||||
builder.Append(path, 0, areaEndIndex);
|
||||
builder.Append(pageName);
|
||||
var pageRoute = builder.ToString();
|
||||
|
||||
result = (areaName, pageName, pageRoute);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SelectorModel CreateSelectorModel(string prefix, string template)
|
||||
{
|
||||
return new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = AttributeRouteModel.CombineTemplates(prefix, template),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
|
@ -14,7 +15,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
private readonly RazorProject _project;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private readonly ILogger _logger;
|
||||
private readonly PageRouteModelFactory _routeModelFactory;
|
||||
private readonly ILogger<RazorProjectPageRouteModelProvider> _logger;
|
||||
|
||||
public RazorProjectPageRouteModelProvider(
|
||||
RazorProject razorProject,
|
||||
|
|
@ -24,12 +26,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
_project = razorProject;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
_logger = loggerFactory.CreateLogger<RazorProjectPageRouteModelProvider>();
|
||||
_routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger);
|
||||
}
|
||||
|
||||
|
||||
/// <remarks>
|
||||
/// Ordered to execute after <see cref="CompiledPageRouteModelProvider"/>.
|
||||
/// </remarks>
|
||||
public int Order => -1000 + 10;
|
||||
public int Order => -1000 + 10;
|
||||
|
||||
public void OnProvidersExecuted(PageRouteModelProviderContext context)
|
||||
{
|
||||
|
|
@ -40,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
// When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectoryy = /Areas;
|
||||
// we need to ensure that the page is only route-able via the area route. By adding area routes first,
|
||||
// we'll ensure non area routes get skipped when it encounters an IsAlreadyRegistered check.
|
||||
|
||||
if (_pagesOptions.AllowAreas)
|
||||
{
|
||||
AddAreaPageModels(context);
|
||||
|
|
@ -64,10 +68,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
}
|
||||
|
||||
var relativePath = item.CombinedPath;
|
||||
if (IsAlreadyRegistered(context, relativePath))
|
||||
if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// A route for this file was already registered either by the CompiledPageRouteModel or as an area route.
|
||||
// by this provider. Skip registering an additional entry.
|
||||
|
||||
// Note: We're comparing duplicates based on root-relative paths. This eliminates a page from being discovered
|
||||
// by overlapping area and non-area routes where ViewEnginePath would be different.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -85,9 +92,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
continue;
|
||||
}
|
||||
|
||||
var routeModel = new PageRouteModel(relativePath, viewEnginePath: item.FilePathWithoutExtension);
|
||||
PageSelectorModel.PopulateDefaults(routeModel, routeModel.ViewEnginePath, routeTemplate);
|
||||
context.RouteModels.Add(routeModel);
|
||||
var routeModel = _routeModelFactory.CreateRouteModel(relativePath, routeTemplate);
|
||||
if (routeModel != null)
|
||||
{
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
}
|
||||
|
||||
var relativePath = item.CombinedPath;
|
||||
if (IsAlreadyRegistered(context, relativePath))
|
||||
if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// A route for this file was already registered either by the CompiledPageRouteModel.
|
||||
// Skip registering an additional entry.
|
||||
|
|
@ -114,36 +123,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!PageSelectorModel.TryParseAreaPath(_pagesOptions, item.FilePath, _logger, out var areaResult))
|
||||
var routeModel = _routeModelFactory.CreateAreaRouteModel(relativePath, routeTemplate);
|
||||
if (routeModel != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var routeModel = new PageRouteModel(relativePath, viewEnginePath: areaResult.viewEnginePath)
|
||||
{
|
||||
RouteValues =
|
||||
{
|
||||
["area"] = areaResult.areaName,
|
||||
},
|
||||
};
|
||||
|
||||
PageSelectorModel.PopulateDefaults(routeModel, areaResult.pageRoute, routeTemplate);
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAlreadyRegistered(PageRouteModelProviderContext context, string relativePath)
|
||||
{
|
||||
for (var i = 0; i < context.RouteModels.Count; i++)
|
||||
{
|
||||
var existingRouteModel = context.RouteModels[i];
|
||||
if (string.Equals(relativePath, existingRouteModel.RelativePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsRouteable(RazorProjectItem item)
|
||||
|
|
|
|||
|
|
@ -24,42 +24,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
public class CompiledPageRouteModelProviderTest
|
||||
{
|
||||
public CompiledPageRouteModelProviderTest()
|
||||
{
|
||||
FileProvider = new TestFileProvider();
|
||||
Project = new FileProviderRazorProject(
|
||||
Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == FileProvider),
|
||||
Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
TemplateEngine = new RazorTemplateEngine(RazorEngine.Create(), Project);
|
||||
|
||||
PagesOptions = new RazorPagesOptions();
|
||||
Provider = new TestCompiledPageRouteModelProvider(new ApplicationPartManager(), Options.Create(PagesOptions), TemplateEngine, NullLoggerFactory.Instance);
|
||||
}
|
||||
|
||||
public TestFileProvider FileProvider { get; }
|
||||
|
||||
public RazorProject Project { get; }
|
||||
|
||||
public RazorTemplateEngine TemplateEngine { get; }
|
||||
|
||||
public RazorPagesOptions PagesOptions { get; }
|
||||
|
||||
public TestCompiledPageRouteModelProvider Provider { get; }
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsModelsForCompiledViews()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Home.cshtml", "some-prefix"),
|
||||
});
|
||||
};
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -100,16 +79,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsModelsForCompiledViews_Version_2_1()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_1_Descriptor("/Pages/Home.cshtml", "some-prefix"),
|
||||
});
|
||||
};
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -150,20 +130,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_ValidatesChecksum_RejectsPageWhenContentDoesntMatch()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
FileProvider.AddFile("/Pages/About.cshtml", "some other content");
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/Pages/About.cshtml", "some other content");
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(context.RouteModels);
|
||||
|
|
@ -173,22 +155,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_ValidatesChecksum_AcceptsPageWhenContentMatches()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Pages/_ViewImports.cshtml"),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
FileProvider.AddFile("/Pages/About.cshtml", "some content");
|
||||
FileProvider.AddFile("/Pages/_ViewImports.cshtml", "some import");
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/Pages/About.cshtml", "some content");
|
||||
fileProvider.AddFile("/Pages/_ViewImports.cshtml", "some import");
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -200,21 +184,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_ValidatesChecksum_SkipsValidationWhenMainSourceMissing()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Pages/_ViewImports.cshtml"),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
FileProvider.AddFile("/Pages/_ViewImports.cshtml", "some other import");
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/Pages/_ViewImports.cshtml", "some other import");
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -226,21 +212,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsModelsForCompiledAreaPages()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
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}"),
|
||||
});
|
||||
};
|
||||
|
||||
PagesOptions.AllowAreas = true;
|
||||
PagesOptions.AreaRootDirectory = "/Features";
|
||||
PagesOptions.RootDirectory = "/Files";
|
||||
var options = new RazorPagesOptions
|
||||
{
|
||||
AllowAreas = true,
|
||||
AreaRootDirectory = "/Features",
|
||||
RootDirectory = "/Files",
|
||||
};
|
||||
|
||||
var provider = CreateProvider(options: options, descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -311,18 +301,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_DoesNotAddsModelsForAreaPages_IfFeatureIsDisabled()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Areas/Accounts/Pages/Home.cshtml"),
|
||||
});
|
||||
};
|
||||
|
||||
PagesOptions.AllowAreas = false;
|
||||
var options = new RazorPagesOptions { AllowAreas = false };
|
||||
|
||||
var provider = CreateProvider(options: options, descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -348,21 +339,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_DoesNotAddAreaAndNonAreaRoutesForAPage()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Areas/Accounts/Manage/Home.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Areas/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Contact.cshtml"),
|
||||
});
|
||||
};
|
||||
|
||||
PagesOptions.AllowAreas = true;
|
||||
PagesOptions.AreaRootDirectory = "/Areas";
|
||||
PagesOptions.RootDirectory = "/";
|
||||
var options = new RazorPagesOptions
|
||||
{
|
||||
AllowAreas = true,
|
||||
AreaRootDirectory = "/Areas",
|
||||
RootDirectory = "/",
|
||||
};
|
||||
|
||||
var provider = CreateProvider(options: options, descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -408,18 +403,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage_WithIndexAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
});
|
||||
|
||||
PagesOptions.RootDirectory = "/";
|
||||
};
|
||||
var options = new RazorPagesOptions { RootDirectory = "/" };
|
||||
|
||||
var provider = CreateProvider(options: options, descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -448,16 +443,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
});
|
||||
};
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -486,21 +482,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Home.cshtml", "/some-prefix"),
|
||||
});
|
||||
};
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => Provider.OnProvidersExecuting(context));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
|
||||
Assert.Equal(
|
||||
"The route for the page at '/Pages/Home.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
private TestCompiledPageRouteModelProvider CreateProvider(
|
||||
RazorPagesOptions options = null,
|
||||
IList<CompiledViewDescriptor> descriptors = null,
|
||||
TestFileProvider fileProvider = null)
|
||||
{
|
||||
options = options ?? new RazorPagesOptions();
|
||||
fileProvider = fileProvider ?? new TestFileProvider();
|
||||
var project = new FileProviderRazorProject(
|
||||
Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider),
|
||||
Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
var templateEngine = new RazorTemplateEngine(RazorEngine.Create(), project);
|
||||
|
||||
var provider = new TestCompiledPageRouteModelProvider(
|
||||
new ApplicationPartManager(),
|
||||
Options.Create(options),
|
||||
templateEngine,
|
||||
NullLogger<CompiledPageRouteModelProvider>.Instance);
|
||||
|
||||
provider.Descriptors.AddRange(descriptors ?? Array.Empty<CompiledViewDescriptor>());
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static CompiledViewDescriptor CreateVersion_2_0_Descriptor(string path, string routeTemplate = "")
|
||||
{
|
||||
return new CompiledViewDescriptor
|
||||
|
|
@ -529,8 +549,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ApplicationPartManager partManager,
|
||||
IOptions<RazorPagesOptions> options,
|
||||
RazorTemplateEngine templateEngine,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(partManager, options, templateEngine, loggerFactory)
|
||||
ILogger<CompiledPageRouteModelProvider> logger)
|
||||
: base(partManager, options, templateEngine, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -539,4 +559,4 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
protected override IEnumerable<CompiledViewDescriptor> GetViewDescriptors(ApplicationPartManager applicationManager) => Descriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
// 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.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class PageRouteModelFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void CreateRouteModel_AddsSelector()
|
||||
{
|
||||
// Arrange
|
||||
var relativePath = "/Pages/Users/Profile.cshtml";
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var routeModel = routeModelFactory.CreateRouteModel(relativePath, "{id?}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(relativePath, routeModel.RelativePath);
|
||||
Assert.Equal("/Users/Profile", routeModel.ViewEnginePath);
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.Selectors,
|
||||
selector => Assert.Equal("Users/Profile/{id?}", selector.AttributeRouteModel.Template));
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.RouteValues,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Users/Profile", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRouteModel_AddsMultipleSelectorsForIndexPage()
|
||||
{
|
||||
// Arrange
|
||||
var relativePath = "/Pages/Users/Profile/Index.cshtml";
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var routeModel = routeModelFactory.CreateRouteModel(relativePath, "{id?}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(relativePath, routeModel.RelativePath);
|
||||
Assert.Equal("/Users/Profile/Index", routeModel.ViewEnginePath);
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.Selectors,
|
||||
selector => Assert.Equal("Users/Profile/Index/{id?}", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Users/Profile/{id?}", selector.AttributeRouteModel.Template));
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.RouteValues,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Users/Profile/Index", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateAreaRouteModel_AddsSelector()
|
||||
{
|
||||
// Arrange
|
||||
var relativePath = "/Areas/TestArea/Pages/Users/Profile.cshtml";
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var routeModel = routeModelFactory.CreateAreaRouteModel(relativePath, "{id?}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(relativePath, routeModel.RelativePath);
|
||||
Assert.Equal("/Users/Profile", routeModel.ViewEnginePath);
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.Selectors,
|
||||
selector => Assert.Equal("TestArea/Users/Profile/{id?}", selector.AttributeRouteModel.Template));
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.RouteValues.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("TestArea", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Users/Profile", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateAreaRouteModel_AddsMultipleSelectorsForIndexPage()
|
||||
{
|
||||
// Arrange
|
||||
var relativePath = "/Areas/TestArea/Pages/Users/Profile/Index.cshtml";
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var routeModel = routeModelFactory.CreateAreaRouteModel(relativePath, "{id?}");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(relativePath, routeModel.RelativePath);
|
||||
Assert.Equal("/Users/Profile/Index", routeModel.ViewEnginePath);
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.Selectors,
|
||||
selector => Assert.Equal("TestArea/Users/Profile/Index/{id?}", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("TestArea/Users/Profile/{id?}", selector.AttributeRouteModel.Template));
|
||||
|
||||
Assert.Collection(
|
||||
routeModel.RouteValues.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("TestArea", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Users/Profile/Index", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/Areas/About.cshtml")]
|
||||
[InlineData("/Areas/MyArea/Index.cshtml")]
|
||||
public void TryParseAreaPath_ReturnsFalse_IfPathDoesNotConform(string path)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var success = routeModelFactory.TryParseAreaPath(path, out _);
|
||||
|
||||
// Assert
|
||||
Assert.False(success);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/Areas/MyArea/Views/About.cshtml")]
|
||||
[InlineData("/Areas/MyArea/SubDir/Pages/Index.cshtml")]
|
||||
[InlineData("/Areas/MyArea/NotPages/SubDir/About.cshtml")]
|
||||
public void TryParseAreaPath_ReturnsFalse_IfPathDoesNotBelongToRootDirectory(string path)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
|
||||
|
||||
// Act
|
||||
var success = routeModelFactory.TryParseAreaPath(path, out _);
|
||||
|
||||
// Assert
|
||||
Assert.False(success);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/Areas/MyArea/Pages/Index.cshtml", "MyArea", "/Index")]
|
||||
[InlineData("/Areas/Accounts/Pages/Manage/Edit.cshtml", "Accounts", "/Manage/Edit")]
|
||||
public void TryParseAreaPath_ParsesAreaPath(
|
||||
string path,
|
||||
string expectedArea,
|
||||
string expectedViewEnginePath)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
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);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class PageSelectorModelTest
|
||||
{
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/Areas/About.cshtml")]
|
||||
[InlineData("/Areas/MyArea/Index.cshtml")]
|
||||
public void TryParseAreaPath_ReturnsFalse_IfPathDoesNotConform(string path)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
|
||||
// Act
|
||||
var success = PageSelectorModel.TryParseAreaPath(options, path, NullLogger.Instance, out _);
|
||||
|
||||
// Assert
|
||||
Assert.False(success);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/MyArea/Views/About.cshtml")]
|
||||
[InlineData("/MyArea/SubDir/Pages/Index.cshtml")]
|
||||
[InlineData("/MyArea/NotPages/SubDir/About.cshtml")]
|
||||
public void TryParseAreaPath_ReturnsFalse_IfPathDoesNotBelongToRootDirectory(string path)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
|
||||
// Act
|
||||
var success = PageSelectorModel.TryParseAreaPath(options, path, NullLogger.Instance, out _);
|
||||
|
||||
// Assert
|
||||
Assert.False(success);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/MyArea/Pages/Index.cshtml", "MyArea", "/Index", "/MyArea/Index")]
|
||||
[InlineData("/Accounts/Pages/Manage/Edit.cshtml", "Accounts", "/Manage/Edit", "/Accounts/Manage/Edit")]
|
||||
public void TryParseAreaPath_ParsesAreaPath(
|
||||
string path,
|
||||
string expectedArea,
|
||||
string expectedViewEnginePath,
|
||||
string expectedRoute)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions();
|
||||
|
||||
// Act
|
||||
var success = PageSelectorModel.TryParseAreaPath(options, path, NullLogger.Instance, out var result);
|
||||
|
||||
// Assert
|
||||
Assert.True(success);
|
||||
Assert.Equal(expectedArea, result.areaName);
|
||||
Assert.Equal(expectedViewEnginePath, result.viewEnginePath);
|
||||
Assert.Equal(expectedRoute, result.pageRoute);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")]
|
||||
[InlineData("/MyArea/Dir1/Dir2/Index.cshtml", "MyArea", "/Index", "/MyArea/Index")]
|
||||
[InlineData("/Accounts/Dir1/Dir2/Manage/Edit.cshtml", "Accounts", "/Manage/Edit", "/Accounts/Manage/Edit")]
|
||||
public void TryParseAreaPath_ParsesAreaPath_WithMultiLevelRootDirectory(
|
||||
string path,
|
||||
string expectedArea,
|
||||
string expectedViewEnginePath,
|
||||
string expectedRoute)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorPagesOptions
|
||||
{
|
||||
RootDirectory = "/Dir1/Dir2"
|
||||
};
|
||||
|
||||
// Act
|
||||
var success = PageSelectorModel.TryParseAreaPath(options, path, NullLogger.Instance, out var result);
|
||||
|
||||
// Assert
|
||||
Assert.True(success);
|
||||
Assert.Equal(expectedArea, result.areaName);
|
||||
Assert.Equal(expectedViewEnginePath, result.viewEnginePath);
|
||||
Assert.Equal(expectedRoute, result.pageRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue