Add support for page precompilation
This commit is contained in:
parent
e766a259ed
commit
8ed55107e7
|
|
@ -0,0 +1,60 @@
|
|||
// 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.IO;
|
||||
using System.Reflection;
|
||||
#if NETSTANDARD1_6
|
||||
using System.Runtime.Loader;
|
||||
#endif
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public static class CompiledViewManfiest
|
||||
{
|
||||
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
||||
|
||||
public static Type GetManifestType(AssemblyPart assemblyPart, string typeName)
|
||||
{
|
||||
EnsureFeatureAssembly(assemblyPart);
|
||||
|
||||
var precompiledAssemblyName = new AssemblyName(assemblyPart.Assembly.FullName);
|
||||
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;
|
||||
|
||||
return Type.GetType($"{typeName},{precompiledAssemblyName}");
|
||||
}
|
||||
|
||||
private static void EnsureFeatureAssembly(AssemblyPart assemblyPart)
|
||||
{
|
||||
#if NETSTANDARD1_6
|
||||
if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty(assemblyPart.Assembly.Location))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name
|
||||
+ PrecompiledViewsAssemblySuffix
|
||||
+ ".dll";
|
||||
var precompiledAssemblyFilePath = Path.Combine(
|
||||
Path.GetDirectoryName(assemblyPart.Assembly.Location),
|
||||
precompiledAssemblyFileName);
|
||||
|
||||
if (File.Exists(precompiledAssemblyFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyLoadContext.Default.LoadFromAssemblyPath(precompiledAssemblyFilePath);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
||||
}
|
||||
}
|
||||
#elif NET46
|
||||
#else
|
||||
#error target frameworks needs to be updated.
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
|
|
@ -15,9 +13,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// </summary>
|
||||
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the suffix for the view assembly.
|
||||
/// </summary>
|
||||
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,19 +25,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// </summary>
|
||||
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";
|
||||
|
||||
private static readonly string FullyQualifiedManifestTypeName = ViewInfoContainerNamespace + "." + ViewInfoContainerTypeName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||
{
|
||||
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
||||
{
|
||||
var viewInfoContainerTypeName = GetViewInfoContainerType(assemblyPart);
|
||||
if (viewInfoContainerTypeName == null)
|
||||
var viewContainer = GetManifest(assemblyPart);
|
||||
if (viewContainer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var viewContainer = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);
|
||||
|
||||
foreach (var item in viewContainer.ViewInfos)
|
||||
{
|
||||
feature.Views[item.Path] = item.Type;
|
||||
|
|
@ -55,40 +50,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// </summary>
|
||||
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
|
||||
/// <returns>The <see cref="ViewInfoContainer"/> <see cref="Type"/>.</returns>
|
||||
protected virtual Type GetViewInfoContainerType(AssemblyPart assemblyPart)
|
||||
protected virtual ViewInfoContainer GetManifest(AssemblyPart assemblyPart)
|
||||
{
|
||||
#if NETSTANDARD1_6
|
||||
if (!assemblyPart.Assembly.IsDynamic && !string.IsNullOrEmpty(assemblyPart.Assembly.Location))
|
||||
var type = CompiledViewManfiest.GetManifestType(assemblyPart, FullyQualifiedManifestTypeName);
|
||||
if (type != null)
|
||||
{
|
||||
var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name
|
||||
+ PrecompiledViewsAssemblySuffix
|
||||
+ ".dll";
|
||||
var precompiledAssemblyFilePath = Path.Combine(
|
||||
Path.GetDirectoryName(assemblyPart.Assembly.Location),
|
||||
precompiledAssemblyFileName);
|
||||
|
||||
if (File.Exists(precompiledAssemblyFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(precompiledAssemblyFilePath);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
||||
}
|
||||
}
|
||||
return (ViewInfoContainer)Activator.CreateInstance(type);
|
||||
}
|
||||
#elif NET46
|
||||
#else
|
||||
#error target frameworks needs to be updated.
|
||||
#endif
|
||||
|
||||
var precompiledAssemblyName = new AssemblyName(assemblyPart.Assembly.FullName);
|
||||
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;
|
||||
|
||||
var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
|
||||
return Type.GetType(typeName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,10 +135,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
// DependencyContextRazorViewEngineOptionsSetup needs to run after RazorViewEngineOptionsSetup.
|
||||
// The ordering of the following two lines is important to ensure this behavior.
|
||||
#pragma warning disable 0618
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||
#pragma warning restore 0618
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<
|
||||
IConfigureOptions<RazorViewEngineOptions>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds or modifies an <see cref="PageApplicationModelProviderContext"/> for Razor Page discovery.
|
||||
/// </summary>
|
||||
public interface IPageApplicationModelProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the order value for determining the order of execution of providers. Providers execute in
|
||||
/// ascending numeric value of the <see cref="Order"/> property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Providers are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
|
||||
/// A provider with a lower numeric value of <see cref="Order"/> will have its
|
||||
/// <see cref="OnProvidersExecuting"/> called before that of a provider with a higher numeric value of
|
||||
/// <see cref="Order"/>. The <see cref="OnProvidersExecuted"/> method is called in the reverse ordering after
|
||||
/// all calls to <see cref="OnProvidersExecuting"/>. A provider with a lower numeric value of
|
||||
/// <see cref="Order"/> will have its <see cref="OnProvidersExecuted"/> method called after that of a provider
|
||||
/// with a higher numeric value of <see cref="Order"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If two providers have the same numeric value of <see cref="Order"/>, then their relative execution order
|
||||
/// is undefined.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executed for the first pass of building <see cref="PageApplicationModel"/> instances. See <see cref="Order"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="PageApplicationModelProviderContext"/>.</param>
|
||||
void OnProvidersExecuting(PageApplicationModelProviderContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Executed for the second pass of building <see cref="PageApplicationModel"/> instances. See <see cref="Order"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="PageApplicationModelProviderContext"/>.</param>
|
||||
void OnProvidersExecuted(PageApplicationModelProviderContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -59,17 +59,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application root relative path for the page.
|
||||
/// Gets the application root relative path for the page.
|
||||
/// </summary>
|
||||
public string RelativePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path relative to the base path for page discovery.
|
||||
/// Gets the path relative to the base path for page discovery.
|
||||
/// </summary>
|
||||
public string ViewEnginePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the applicable <see cref="IFilterMetadata"/> instances.
|
||||
/// Gets the applicable <see cref="IFilterMetadata"/> instances.
|
||||
/// </summary>
|
||||
public IList<IFilterMetadata> Filters { get; }
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public IDictionary<object, object> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SelectorModel"/> instances.
|
||||
/// Gets the <see cref="SelectorModel"/> instances.
|
||||
/// </summary>
|
||||
public IList<SelectorModel> Selectors { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// A context object for <see cref="IPageApplicationModelProvider"/>.
|
||||
/// </summary>
|
||||
public class PageApplicationModelProviderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="PageApplicationModel"/> instances.
|
||||
/// </summary>
|
||||
public IList<PageApplicationModel> Results { get; } = new List<PageApplicationModel>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||
{
|
||||
public class CompiledPageInfoFeature
|
||||
{
|
||||
public IList<CompiledPageInfo> CompiledPages { get; } = new List<CompiledPageInfo>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
|
||||
/// </summary>
|
||||
public class CompiledPageFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
|
||||
/// </summary>
|
||||
public static readonly string CompiledPageManifestNamespace = "AspNetCore";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name for the view collection type in the view assembly.
|
||||
/// </summary>
|
||||
public static readonly string CompiledPageManifestTypeName = "__CompiledRazorPagesManifest";
|
||||
|
||||
private static readonly string FullyQualifiedManifestTypeName =
|
||||
CompiledPageManifestNamespace + "." + CompiledPageManifestTypeName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||
{
|
||||
foreach (var item in GetCompiledPageInfo(parts))
|
||||
{
|
||||
feature.Views.Add(item.Path, item.CompiledType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sequence of <see cref="CompiledPageInfo"/> from <paramref name="parts"/>.
|
||||
/// </summary>
|
||||
/// <param name="parts">The <see cref="ApplicationPart"/>s</param>
|
||||
/// <returns>The sequence of <see cref="CompiledPageInfo"/>.</returns>
|
||||
public static IEnumerable<CompiledPageInfo> GetCompiledPageInfo(IEnumerable<ApplicationPart> parts)
|
||||
{
|
||||
return parts.OfType<AssemblyPart>()
|
||||
.Select(part => CompiledViewManfiest.GetManifestType(part, FullyQualifiedManifestTypeName))
|
||||
.Where(type => type != null)
|
||||
.Select(type => (CompiledPageManifest)Activator.CreateInstance(type))
|
||||
.SelectMany(manifest => manifest.CompiledPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
||||
{
|
||||
public class CompiledPageInfo
|
||||
{
|
||||
public CompiledPageInfo(string path, Type compiledType, string routePrefix)
|
||||
{
|
||||
Path = path;
|
||||
CompiledType = compiledType;
|
||||
RoutePrefix = routePrefix;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public string RoutePrefix { get; }
|
||||
|
||||
public Type CompiledType { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,11 @@
|
|||
// 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.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||
|
|
@ -24,13 +26,16 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
AddFeatureProviders(builder);
|
||||
AddServices(builder.Services);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IMvcCoreBuilder AddRazorPages(
|
||||
this IMvcCoreBuilder builder,
|
||||
Action<RazorViewEngineOptions> setupAction)
|
||||
Action<RazorPagesOptions> setupAction)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
|
|
@ -43,6 +48,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
AddFeatureProviders(builder);
|
||||
AddServices(builder.Services);
|
||||
|
||||
builder.Services.Configure(setupAction);
|
||||
|
|
@ -50,6 +57,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return builder;
|
||||
}
|
||||
|
||||
private static void AddFeatureProviders(IMvcCoreBuilder builder)
|
||||
{
|
||||
if (!builder.PartManager.FeatureProviders.OfType<CompiledPageFeatureProvider>().Any())
|
||||
{
|
||||
builder.PartManager.FeatureProviders.Add(new CompiledPageFeatureProvider());
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal static void AddServices(IServiceCollection services)
|
||||
{
|
||||
|
|
@ -57,10 +72,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
|
||||
|
||||
// Action Invoker
|
||||
// Action description and invocation
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageApplicationModelProvider, RazorProjectPageApplicationModelProvider>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageApplicationModelProvider, CompiledPageApplicationModelProvider>());
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public abstract class CompiledPageManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompiledPageManifest"/>.
|
||||
/// </summary>
|
||||
/// <param name="pages">The sequence of <see cref="CompiledPageInfo"/>.</param>
|
||||
public CompiledPageManifest(IReadOnlyList<CompiledPageInfo> pages)
|
||||
{
|
||||
CompiledPages = pages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IReadOnlyList{T}"/> of <see cref="CompiledPageInfo"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<CompiledPageInfo> CompiledPages { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,29 +3,27 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public class PageActionDescriptorProvider : IActionDescriptorProvider
|
||||
{
|
||||
private static readonly string IndexFileName = "Index.cshtml";
|
||||
private readonly RazorProject _project;
|
||||
private readonly List<IPageApplicationModelProvider> _applicationModelProviders;
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
|
||||
public PageActionDescriptorProvider(
|
||||
RazorProject project,
|
||||
IEnumerable<IPageApplicationModelProvider> pageMetadataProviders,
|
||||
IOptions<MvcOptions> mvcOptionsAccessor,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor)
|
||||
{
|
||||
_project = project;
|
||||
_applicationModelProviders = pageMetadataProviders.OrderBy(p => p.Order).ToList();
|
||||
_mvcOptions = mvcOptionsAccessor.Value;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
}
|
||||
|
|
@ -34,57 +32,37 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
|
||||
{
|
||||
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
|
||||
var pageApplicationModels = BuildModel();
|
||||
|
||||
for (var i = 0; i < pageApplicationModels.Count; i++)
|
||||
{
|
||||
if (item.FileName.StartsWith("_"))
|
||||
{
|
||||
// Files like _ViewImports.cshtml should not be routable.
|
||||
continue;
|
||||
}
|
||||
|
||||
string template;
|
||||
if (!PageDirectiveFeature.TryGetPageDirective(item, out template))
|
||||
{
|
||||
// .cshtml pages without @page are not RazorPages.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AttributeRouteModel.IsOverridePattern(template))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(
|
||||
Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
|
||||
item.Path));
|
||||
}
|
||||
|
||||
AddActionDescriptors(context.Results, item, template);
|
||||
AddActionDescriptors(context.Results, pageApplicationModels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected IList<PageApplicationModel> BuildModel()
|
||||
{
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
for (var i = 0; i < _applicationModelProviders.Count; i++)
|
||||
{
|
||||
_applicationModelProviders[i].OnProvidersExecuting(context);
|
||||
}
|
||||
|
||||
for (var i = _applicationModelProviders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_applicationModelProviders[i].OnProvidersExecuted(context);
|
||||
}
|
||||
|
||||
return context.Results;
|
||||
}
|
||||
|
||||
public void OnProvidersExecuted(ActionDescriptorProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
private void AddActionDescriptors(IList<ActionDescriptor> actions, RazorProjectItem item, string template)
|
||||
private void AddActionDescriptors(IList<ActionDescriptor> actions, PageApplicationModel model)
|
||||
{
|
||||
var model = new PageApplicationModel(item.CombinedPath, item.PathWithoutExtension);
|
||||
var routePrefix = item.PathWithoutExtension;
|
||||
model.Selectors.Add(CreateSelectorModel(routePrefix, template));
|
||||
|
||||
if (string.Equals(IndexFileName, item.FileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parentDirectoryPath = item.Path;
|
||||
var index = parentDirectoryPath.LastIndexOf('/');
|
||||
if (index == -1)
|
||||
{
|
||||
parentDirectoryPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentDirectoryPath = parentDirectoryPath.Substring(0, index);
|
||||
}
|
||||
model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, template));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
|
||||
{
|
||||
_pagesOptions.Conventions[i].Apply(model);
|
||||
|
|
@ -111,28 +89,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Order = selector.AttributeRouteModel.Order ?? 0,
|
||||
Template = selector.AttributeRouteModel.Template,
|
||||
},
|
||||
DisplayName = $"Page: {item.Path}",
|
||||
DisplayName = $"Page: {model.ViewEnginePath}",
|
||||
FilterDescriptors = filters,
|
||||
Properties = new Dictionary<object, object>(model.Properties),
|
||||
RelativePath = item.CombinedPath,
|
||||
RelativePath = model.RelativePath,
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "page", item.PathWithoutExtension },
|
||||
{ "page", model.ViewEnginePath},
|
||||
},
|
||||
ViewEnginePath = item.Path,
|
||||
ViewEnginePath = model.ViewEnginePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static SelectorModel CreateSelectorModel(string prefix, string template)
|
||||
{
|
||||
return new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = AttributeRouteModel.CombineTemplates(prefix, template),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,16 +16,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
return TryGetPageDirective(projectItem.Read, out template);
|
||||
}
|
||||
|
||||
public static bool TryGetPageDirective(Func<Stream> streamFactory, out string template)
|
||||
{
|
||||
if (streamFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(streamFactory));
|
||||
}
|
||||
|
||||
const string PageDirective = "@page";
|
||||
|
||||
var stream = projectItem.Read();
|
||||
|
||||
string content = null;
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
string content;
|
||||
using (var streamReader = new StreamReader(streamFactory()))
|
||||
{
|
||||
do
|
||||
{
|
||||
content = streamReader.ReadLine();
|
||||
|
||||
} while (content != null && string.IsNullOrWhiteSpace(content));
|
||||
content = content?.Trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class CompiledPageApplicationModelProvider : IPageApplicationModelProvider
|
||||
{
|
||||
private readonly object _cacheLock = new object();
|
||||
private readonly ApplicationPartManager _applicationManager;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private List<PageApplicationModel> _cachedApplicationModels;
|
||||
|
||||
public CompiledPageApplicationModelProvider(
|
||||
ApplicationPartManager applicationManager,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
}
|
||||
|
||||
public int Order => -1000;
|
||||
|
||||
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
|
||||
{
|
||||
EnsureCache();
|
||||
for (var i = 0; i < _cachedApplicationModels.Count; i++)
|
||||
{
|
||||
var pageModel = _cachedApplicationModels[i];
|
||||
context.Results.Add(new PageApplicationModel(pageModel));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_cachedApplicationModels != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rootDirectory = _pagesOptions.RootDirectory;
|
||||
if (!rootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
rootDirectory = rootDirectory + "/";
|
||||
}
|
||||
|
||||
var cachedApplicationModels = new List<PageApplicationModel>();
|
||||
var pages = GetCompiledPages();
|
||||
foreach (var page in pages)
|
||||
{
|
||||
if (!page.Path.StartsWith(rootDirectory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var viewEnginePath = GetViewEnginePath(rootDirectory, page.Path);
|
||||
var model = new PageApplicationModel(page.Path, viewEnginePath);
|
||||
PageSelectorModel.PopulateDefaults(model, page.RoutePrefix);
|
||||
|
||||
cachedApplicationModels.Add(model);
|
||||
}
|
||||
|
||||
_cachedApplicationModels = cachedApplicationModels;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<CompiledPageInfo> GetCompiledPages()
|
||||
=> CompiledPageFeatureProvider.GetCompiledPageInfo(_applicationManager.ApplicationParts);
|
||||
|
||||
private string GetViewEnginePath(string rootDirectory, string path)
|
||||
{
|
||||
var endIndex = path.LastIndexOf('.');
|
||||
if (endIndex == -1)
|
||||
{
|
||||
endIndex = path.Length;
|
||||
}
|
||||
|
||||
// rootDirectory = "/Pages/AllMyPages/"
|
||||
// path = "/Pages/AllMyPages/Home.cshtml"
|
||||
// Result = "/Home"
|
||||
var startIndex = rootDirectory.Length - 1;
|
||||
|
||||
return path.Substring(startIndex, endIndex - startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -216,13 +216,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
ViewStartFileName);
|
||||
foreach (var item in viewStartItems)
|
||||
{
|
||||
if (item.Exists)
|
||||
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
|
||||
if (factoryResult.Success)
|
||||
{
|
||||
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
|
||||
if (factoryResult.Success)
|
||||
{
|
||||
viewStartFactories.Insert(0, factoryResult.RazorPageFactory);
|
||||
}
|
||||
viewStartFactories.Insert(0, factoryResult.RazorPageFactory);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public static class PageSelectorModel
|
||||
{
|
||||
private const string IndexFileName = "Index.cshtml";
|
||||
|
||||
public static void PopulateDefaults(PageApplicationModel model, string routeTemplate)
|
||||
{
|
||||
if (AttributeRouteModel.IsOverridePattern(routeTemplate))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(
|
||||
Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
|
||||
model.RelativePath));
|
||||
}
|
||||
|
||||
model.Selectors.Add(CreateSelectorModel(model.ViewEnginePath, routeTemplate));
|
||||
|
||||
var fileName = Path.GetFileName(model.RelativePath);
|
||||
if (string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parentDirectoryPath = model.ViewEnginePath;
|
||||
var index = parentDirectoryPath.LastIndexOf('/');
|
||||
if (index == -1)
|
||||
{
|
||||
parentDirectoryPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentDirectoryPath = parentDirectoryPath.Substring(0, index);
|
||||
}
|
||||
model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, routeTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
private static SelectorModel CreateSelectorModel(string prefix, string template)
|
||||
{
|
||||
return new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = AttributeRouteModel.CombineTemplates(prefix, template),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class RazorProjectPageApplicationModelProvider : IPageApplicationModelProvider
|
||||
{
|
||||
private readonly RazorProject _project;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
|
||||
public RazorProjectPageApplicationModelProvider(
|
||||
RazorProject razorProject,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor)
|
||||
{
|
||||
_project = razorProject;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
}
|
||||
|
||||
public int Order => -1000;
|
||||
|
||||
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
|
||||
{
|
||||
if (item.FileName.StartsWith("_"))
|
||||
{
|
||||
// Pages like _ViewImports should not be routable.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PageDirectiveFeature.TryGetPageDirective(item, out var routeTemplate))
|
||||
{
|
||||
// .cshtml pages without @page are not RazorPages.
|
||||
continue;
|
||||
}
|
||||
|
||||
var pageApplicationModel = new PageApplicationModel(
|
||||
relativePath: item.CombinedPath,
|
||||
viewEnginePath: item.PathWithoutExtension);
|
||||
PageSelectorModel.PopulateDefaults(pageApplicationModel, routeTemplate);
|
||||
|
||||
context.Results.Add(pageApplicationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,8 +121,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
_containerLookup = containerLookup;
|
||||
}
|
||||
|
||||
protected override Type GetViewInfoContainerType(AssemblyPart assemblyPart) =>
|
||||
_containerLookup[assemblyPart];
|
||||
protected override ViewInfoContainer GetManifest(AssemblyPart assemblyPart)
|
||||
{
|
||||
var type = _containerLookup[assemblyPart];
|
||||
return (ViewInfoContainer)Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewInfoContainer1 : ViewInfoContainer
|
||||
|
|
|
|||
|
|
@ -19,17 +19,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
public class PageActionDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetDescriptors_DoesNotAddDescriptorsForFilesWithoutDirectives()
|
||||
public void GetDescriptors_DoesNotAddDescriptorsIfNoApplicationModelsAreDiscovered()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("/", "/Index.cshtml", "<h1>Hello world</h1>"),
|
||||
});
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider();
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -42,17 +37,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirective()
|
||||
public void GetDescriptors_AddsDescriptorsForModelWithSelector()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
var model = new PageApplicationModel("/Test.cshtml", "/Test")
|
||||
{
|
||||
Selectors =
|
||||
{
|
||||
GetProjectItem("/", "/Test.cshtml", $"@page{Environment.NewLine}<h1>Hello world</h1>"),
|
||||
});
|
||||
new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = "/Test/{id:int?}",
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(model);
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -65,53 +68,49 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/Test.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/Test", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("Test", descriptor.AttributeRouteInfo.Template);
|
||||
Assert.Equal("/Test/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirectiveAndRouteTemplate()
|
||||
public void GetDescriptors_AddsActionDescriptorForEachSelector()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(
|
||||
new PageApplicationModel("/base-path/Test.cshtml", "/base-path/Test")
|
||||
{
|
||||
GetProjectItem("/", "/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||
});
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var result = Assert.Single(context.Results);
|
||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/Test.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/Test", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("Test/Home", descriptor.AttributeRouteInfo.Template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_GeneratesRouteTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>(MockBehavior.Strict);
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
Selectors =
|
||||
{
|
||||
CreateSelectorModel("base-path/Test/Home")
|
||||
}
|
||||
},
|
||||
new PageApplicationModel("/base-path/Index.cshtml", "/base-path/Index")
|
||||
{
|
||||
GetProjectItem("/", "/base-path/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||
GetProjectItem("/", "/base-path/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||
GetProjectItem("/", "/base-path/Admin/Index.cshtml", $"@page{Environment.NewLine}"),
|
||||
GetProjectItem("/", "/base-path/Admin/User.cshtml", $"@page{Environment.NewLine}"),
|
||||
Selectors =
|
||||
{
|
||||
CreateSelectorModel("base-path/Index"),
|
||||
CreateSelectorModel("base-path/"),
|
||||
}
|
||||
},
|
||||
new PageApplicationModel("/base-path/Admin/Index.cshtml", "/base-path/Admin/Index")
|
||||
{
|
||||
Selectors =
|
||||
{
|
||||
CreateSelectorModel("base-path/Admin/Index"),
|
||||
CreateSelectorModel("base-path/Admin"),
|
||||
}
|
||||
},
|
||||
new PageApplicationModel("/base-path/Admin/User.cshtml", "/base-path/Admin/User")
|
||||
{
|
||||
Selectors =
|
||||
{
|
||||
CreateSelectorModel("base-path/Admin/User"),
|
||||
},
|
||||
});
|
||||
|
||||
var options = GetRazorPagesOptions();
|
||||
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor<MvcOptions>(),
|
||||
options);
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -123,122 +122,39 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Collection(context.Results,
|
||||
result => Assert.Equal("base-path/Test/Home", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path/Index", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path/", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path/Admin/Index", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path/Admin", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("base-path/Admin/User", result.AttributeRouteInfo.Template));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_UsesBasePathOption_WhenGeneratingRouteTemplate()
|
||||
private static SelectorModel CreateSelectorModel(string template)
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>(MockBehavior.Strict);
|
||||
razorProject.Setup(p => p.EnumerateItems("/base-path"))
|
||||
.Returns(new[]
|
||||
return new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
GetProjectItem("/base-path", "/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||
GetProjectItem("/base-path", "/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||
GetProjectItem("/base-path", "/Admin/Index.cshtml", $"@page{Environment.NewLine}"),
|
||||
GetProjectItem("/base-path", "/Admin/User.cshtml", $"@page{Environment.NewLine}"),
|
||||
});
|
||||
var options = GetRazorPagesOptions();
|
||||
options.Value.RootDirectory = "/base-path";
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
GetAccessor<MvcOptions>(),
|
||||
options);
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
result => Assert.Equal("Test/Home", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("Index", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("Admin/Index", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("Admin", result.AttributeRouteInfo.Template),
|
||||
result => Assert.Equal("Admin/User", result.AttributeRouteInfo.Template));
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Path1")]
|
||||
[InlineData("~/Path1")]
|
||||
public void GetDescriptors_ThrowsIfRouteTemplatesAreOverriden(string template)
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("/", "/Test.cshtml", $"@page \"{template}\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||
});
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
||||
// Act and Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
|
||||
Assert.Equal(
|
||||
"The route for the page at '/Test.cshtml' cannot start with / or ~/. " +
|
||||
"Pages do not support overriding the file path of the page.",
|
||||
ex.Message);
|
||||
Template = template,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_WithEmptyPageDirective_MapsIndexToEmptySegment()
|
||||
public void GetDescriptors_AddsMultipleDescriptorsForPageWithMultipleSelectors()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(
|
||||
new PageApplicationModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
|
||||
{
|
||||
GetProjectItem("", "/About/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||
Selectors =
|
||||
{
|
||||
CreateSelectorModel("/Catalog/Details/Index/{id:int?}"),
|
||||
CreateSelectorModel("/Catalog/Details/{id:int?}"),
|
||||
},
|
||||
});
|
||||
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
result =>
|
||||
{
|
||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/About/Index", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("About/Index", descriptor.AttributeRouteInfo.Template);
|
||||
},
|
||||
result =>
|
||||
{
|
||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/About/Index", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("About", descriptor.AttributeRouteInfo.Template);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_WithNonEmptyPageDirective_MapsIndexToEmptySegment()
|
||||
{
|
||||
// Arrange
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("", "/Catalog/Details/Index.cshtml", $"@page \"{{id:int?}}\" {Environment.NewLine}"),
|
||||
});
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor<MvcOptions>(),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -253,14 +169,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("Catalog/Details/Index/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||
Assert.Equal("/Catalog/Details/Index/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||
},
|
||||
result =>
|
||||
{
|
||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
||||
Assert.Equal("Catalog/Details/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||
Assert.Equal("/Catalog/Details/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -269,14 +185,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
// Arrange
|
||||
var options = new MvcOptions();
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
||||
});
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor(options),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -310,14 +221,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var options = new MvcOptions();
|
||||
options.Filters.Add(filter1);
|
||||
options.Filters.Add(filter2);
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
||||
});
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor(options),
|
||||
GetRazorPagesOptions());
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -368,15 +274,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
});
|
||||
var razorOptions = GetRazorPagesOptions();
|
||||
razorOptions.Value.Conventions.Add(convention.Object);
|
||||
|
||||
var razorProject = new Mock<RazorProject>();
|
||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||
.Returns(new[]
|
||||
{
|
||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
||||
});
|
||||
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||
var provider = new PageActionDescriptorProvider(
|
||||
razorProject.Object,
|
||||
new[] { applicationModelProvider },
|
||||
GetAccessor(options),
|
||||
razorOptions);
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
|
|
@ -410,6 +310,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
});
|
||||
}
|
||||
|
||||
private static PageApplicationModel CreateModel()
|
||||
{
|
||||
return new PageApplicationModel("/Home.cshtml", "/Home")
|
||||
{
|
||||
Selectors =
|
||||
{
|
||||
new SelectorModel
|
||||
{
|
||||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = "Home",
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static IOptions<TOptions> GetAccessor<TOptions>(TOptions options = null)
|
||||
where TOptions : class, new()
|
||||
{
|
||||
|
|
@ -432,5 +349,30 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
return new DefaultRazorProjectItem(testFileInfo, basePath, path);
|
||||
}
|
||||
|
||||
private class TestPageApplicationModelProvider : IPageApplicationModelProvider
|
||||
{
|
||||
private readonly PageApplicationModel[] _models;
|
||||
|
||||
public TestPageApplicationModelProvider(params PageApplicationModel[] models)
|
||||
{
|
||||
_models = models ?? Array.Empty<PageApplicationModel>();
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (var model in _models)
|
||||
{
|
||||
context.Results.Add(model);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class CompiledPageApplicationModelProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsModelsForCompiledViews()
|
||||
{
|
||||
// Arrange
|
||||
var info = new[]
|
||||
{
|
||||
new CompiledPageInfo("/Pages/About.cshtml", typeof(object), routePrefix: string.Empty),
|
||||
new CompiledPageInfo("/Pages/Home.cshtml", typeof(object), "some-prefix"),
|
||||
};
|
||||
var provider = new TestCompiledPageApplicationModelProvider(info, new RazorPagesOptions());
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/About", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
selector => Assert.Equal("Pages/About", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/Home", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
selector => Assert.Equal("Pages/Home/some-prefix", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage()
|
||||
{
|
||||
// Arrange
|
||||
var info = new[]
|
||||
{
|
||||
new CompiledPageInfo("/Pages/Index.cshtml", typeof(object), routePrefix: string.Empty),
|
||||
new CompiledPageInfo("/Pages/Admin/Index.cshtml", typeof(object), "some-template"),
|
||||
};
|
||||
var provider = new TestCompiledPageApplicationModelProvider(info, new RazorPagesOptions());
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
|
||||
{
|
||||
// Arrange
|
||||
var info = new[]
|
||||
{
|
||||
new CompiledPageInfo("/Pages/Index.cshtml", typeof(object), routePrefix: string.Empty),
|
||||
new CompiledPageInfo("/Pages/Home.cshtml", typeof(object), "/some-prefix"),
|
||||
};
|
||||
var provider = new TestCompiledPageApplicationModelProvider(info, new RazorPagesOptions());
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act & Assert
|
||||
var ex = 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.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
public class TestCompiledPageApplicationModelProvider : CompiledPageApplicationModelProvider
|
||||
{
|
||||
private readonly IEnumerable<CompiledPageInfo> _info;
|
||||
|
||||
public TestCompiledPageApplicationModelProvider(IEnumerable<CompiledPageInfo> info, RazorPagesOptions options)
|
||||
: base(new ApplicationPartManager(), new TestOptionsManager<RazorPagesOptions>(options))
|
||||
{
|
||||
_info = info;
|
||||
}
|
||||
|
||||
protected override IEnumerable<CompiledPageInfo> GetCompiledPages() => _info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -723,8 +723,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartFactories_NoFactoriesForMissingFiles()
|
||||
public void GetViewStartFactories_ReturnsFactoriesForFilesThatDoNotExistInProject()
|
||||
{
|
||||
// The factory provider might have access to _ViewStarts for files that do not exist on disk \ RazorProject.
|
||||
// This test verifies that we query the factory provider correctly.
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor()
|
||||
{
|
||||
|
|
@ -739,6 +741,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||
|
||||
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
||||
pageFactory.Setup(f => f.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
|
||||
.Returns(new RazorPageFactoryResult(() => null, new IChangeToken[0]));
|
||||
pageFactory.Setup(f => f.CreateFactory("/Views/_ViewStart.cshtml"))
|
||||
.Returns(new RazorPageFactoryResult(new IChangeToken[0]));
|
||||
pageFactory.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
|
||||
.Returns(new RazorPageFactoryResult(() => null, new IChangeToken[0]));
|
||||
|
||||
// No files
|
||||
var fileProvider = new TestFileProvider();
|
||||
var razorProject = new TestRazorProject(fileProvider);
|
||||
|
|
@ -748,16 +758,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
actionDescriptorProvider.Object,
|
||||
pageProvider: null,
|
||||
modelProvider: null,
|
||||
razorPageFactoryProvider: CreateRazorPageFactoryProvider(),
|
||||
razorPageFactoryProvider: pageFactory.Object,
|
||||
razorProject: razorProject);
|
||||
|
||||
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);
|
||||
|
||||
// Act
|
||||
var factories = invokerProvider.GetViewStartFactories(compiledDescriptor);
|
||||
var factories = invokerProvider.GetViewStartFactories(compiledDescriptor).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(factories);
|
||||
Assert.Equal(2, factories.Count);
|
||||
}
|
||||
|
||||
private IRazorPageFactoryProvider CreateRazorPageFactoryProvider()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class RazorProjectPageApplicationModelProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ReturnsPagesWithPageDirective()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var file1 = fileProvider.AddFile("/Pages/Home.cshtml", "@page");
|
||||
var file2 = fileProvider.AddFile("/Pages/Test.cshtml", "Hello world");
|
||||
|
||||
var dir1 = fileProvider.AddDirectoryContent("/Pages", new IFileInfo[] { file1, file2 });
|
||||
fileProvider.AddDirectoryContent("/", new[] { dir1 });
|
||||
|
||||
var project = new TestRazorProject(fileProvider);
|
||||
|
||||
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager);
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Home", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Home", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPages()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var file1 = fileProvider.AddFile("/Pages/Index.cshtml", "@page");
|
||||
var file2 = fileProvider.AddFile("/Pages/Test.cshtml", "Hello world");
|
||||
var file3 = fileProvider.AddFile("/Pages/Admin/Index.cshtml", "@page \"test\"");
|
||||
|
||||
var dir2 = fileProvider.AddDirectoryContent("/Pages/Admin", new[] { file3 });
|
||||
var dir1 = fileProvider.AddDirectoryContent("/Pages", new IFileInfo[] { dir2, file1, file2 });
|
||||
fileProvider.AddDirectoryContent("/", new[] { dir1 });
|
||||
|
||||
var project = new TestRazorProject(fileProvider);
|
||||
|
||||
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager);
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Admin/Index", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Admin/Index/test", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages/Admin/test", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Index", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var file = fileProvider.AddFile("/Index.cshtml", "@page \"/custom-route\"");
|
||||
fileProvider.AddDirectoryContent("/", new[] { file });
|
||||
|
||||
var project = new TestRazorProject(fileProvider);
|
||||
|
||||
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager);
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
|
||||
Assert.Equal("The route for the page at '/Index.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_SkipsPagesStartingWithUnderscore()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var dir1 = fileProvider.AddDirectoryContent("/Pages",
|
||||
new[]
|
||||
{
|
||||
fileProvider.AddFile("/Pages/Home.cshtml", "@page"),
|
||||
fileProvider.AddFile("/Pages/_Layout.cshtml", "@page")
|
||||
});
|
||||
fileProvider.AddDirectoryContent("/", new[] { dir1 });
|
||||
|
||||
var project = new TestRazorProject(fileProvider);
|
||||
|
||||
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager);
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DiscoversFilesUnderBasePath()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var dir1 = fileProvider.AddDirectoryContent("/Pages",
|
||||
new[]
|
||||
{
|
||||
fileProvider.AddFile("/Pages/Index.cshtml", "@page"),
|
||||
fileProvider.AddFile("/Pages/_Layout.cshtml", "@page")
|
||||
});
|
||||
var dir2 = fileProvider.AddDirectoryContent("/NotPages",
|
||||
new[]
|
||||
{
|
||||
fileProvider.AddFile("/NotPages/Index.cshtml", "@page"),
|
||||
fileProvider.AddFile("/NotPages/_Layout.cshtml", "@page")
|
||||
});
|
||||
var rootFile = fileProvider.AddFile("/Index.cshtml", "@page");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { rootFile, dir1, dir2 });
|
||||
|
||||
var project = new TestRazorProject(fileProvider);
|
||||
|
||||
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
|
||||
optionsManager.Value.RootDirectory = "/Pages";
|
||||
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager);
|
||||
var context = new PageApplicationModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.Results,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -213,7 +213,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
feature => Assert.IsType<ControllerFeatureProvider>(feature),
|
||||
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
||||
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
||||
feature => Assert.IsType<ViewsFeatureProvider>(feature));
|
||||
feature => Assert.IsType<ViewsFeatureProvider>(feature),
|
||||
feature => Assert.IsType<CompiledPageFeatureProvider>(feature));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -419,6 +420,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
typeof(JsonPatchOperationsArrayProvider),
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(IPageApplicationModelProvider),
|
||||
new[]
|
||||
{
|
||||
typeof(CompiledPageApplicationModelProvider),
|
||||
typeof(RazorProjectPageApplicationModelProvider),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,41 @@
|
|||
// 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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.TestCommon
|
||||
{
|
||||
public class TestDirectoryContent : IDirectoryContents
|
||||
public class TestDirectoryContent : IDirectoryContents, IFileInfo
|
||||
{
|
||||
private readonly IEnumerable<IFileInfo> _files;
|
||||
|
||||
public TestDirectoryContent(IEnumerable<IFileInfo> files)
|
||||
public TestDirectoryContent(string name, IEnumerable<IFileInfo> files)
|
||||
{
|
||||
Name = name;
|
||||
_files = files;
|
||||
}
|
||||
|
||||
public bool Exists => true;
|
||||
|
||||
public long Length => throw new NotSupportedException();
|
||||
|
||||
public string PhysicalPath => throw new NotSupportedException();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public DateTimeOffset LastModified => throw new NotSupportedException();
|
||||
|
||||
public bool IsDirectory => true;
|
||||
|
||||
public Stream CreateReadStream()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public IEnumerator<IFileInfo> GetEnumerator() => _files.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
|
||||
public TestDirectoryContent AddDirectoryContent(string path, IEnumerable<IFileInfo> files)
|
||||
{
|
||||
var directoryContent = new TestDirectoryContent(files);
|
||||
var directoryContent = new TestDirectoryContent(Path.GetFileName(path), files);
|
||||
_directoryContentsLookup[path] = directoryContent;
|
||||
return directoryContent;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue