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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
|
|
@ -15,9 +13,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the suffix for the view assembly.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -30,19 +25,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";
|
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";
|
||||||
|
|
||||||
|
private static readonly string FullyQualifiedManifestTypeName = ViewInfoContainerNamespace + "." + ViewInfoContainerTypeName;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||||
{
|
{
|
||||||
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
||||||
{
|
{
|
||||||
var viewInfoContainerTypeName = GetViewInfoContainerType(assemblyPart);
|
var viewContainer = GetManifest(assemblyPart);
|
||||||
if (viewInfoContainerTypeName == null)
|
if (viewContainer == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewContainer = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);
|
|
||||||
|
|
||||||
foreach (var item in viewContainer.ViewInfos)
|
foreach (var item in viewContainer.ViewInfos)
|
||||||
{
|
{
|
||||||
feature.Views[item.Path] = item.Type;
|
feature.Views[item.Path] = item.Type;
|
||||||
|
|
@ -55,40 +50,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
|
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
|
||||||
/// <returns>The <see cref="ViewInfoContainer"/> <see cref="Type"/>.</returns>
|
/// <returns>The <see cref="ViewInfoContainer"/> <see cref="Type"/>.</returns>
|
||||||
protected virtual Type GetViewInfoContainerType(AssemblyPart assemblyPart)
|
protected virtual ViewInfoContainer GetManifest(AssemblyPart assemblyPart)
|
||||||
{
|
{
|
||||||
#if NETSTANDARD1_6
|
var type = CompiledViewManfiest.GetManifestType(assemblyPart, FullyQualifiedManifestTypeName);
|
||||||
if (!assemblyPart.Assembly.IsDynamic && !string.IsNullOrEmpty(assemblyPart.Assembly.Location))
|
if (type != null)
|
||||||
{
|
{
|
||||||
var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name
|
return (ViewInfoContainer)Activator.CreateInstance(type);
|
||||||
+ 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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#elif NET46
|
|
||||||
#else
|
|
||||||
#error target frameworks needs to be updated.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var precompiledAssemblyName = new AssemblyName(assemblyPart.Assembly.FullName);
|
return null;
|
||||||
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;
|
|
||||||
|
|
||||||
var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
|
|
||||||
return Type.GetType(typeName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
|
||||||
// DependencyContextRazorViewEngineOptionsSetup needs to run after RazorViewEngineOptionsSetup.
|
// DependencyContextRazorViewEngineOptionsSetup needs to run after RazorViewEngineOptionsSetup.
|
||||||
// The ordering of the following two lines is important to ensure this behavior.
|
// The ordering of the following two lines is important to ensure this behavior.
|
||||||
#pragma warning disable 0618
|
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||||
#pragma warning restore 0618
|
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<
|
ServiceDescriptor.Transient<
|
||||||
IConfigureOptions<RazorViewEngineOptions>,
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the application root relative path for the page.
|
/// Gets the application root relative path for the page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RelativePath { get; }
|
public string RelativePath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public string ViewEnginePath { get; }
|
public string ViewEnginePath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the applicable <see cref="IFilterMetadata"/> instances.
|
/// Gets the applicable <see cref="IFilterMetadata"/> instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<IFilterMetadata> Filters { get; }
|
public IList<IFilterMetadata> Filters { get; }
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||||
public IDictionary<object, object> Properties { get; }
|
public IDictionary<object, object> Properties { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="SelectorModel"/> instances.
|
/// Gets the <see cref="SelectorModel"/> instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<SelectorModel> Selectors { get; }
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
|
|
@ -24,13 +26,16 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddRazorViewEngine();
|
builder.AddRazorViewEngine();
|
||||||
|
|
||||||
|
AddFeatureProviders(builder);
|
||||||
AddServices(builder.Services);
|
AddServices(builder.Services);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMvcCoreBuilder AddRazorPages(
|
public static IMvcCoreBuilder AddRazorPages(
|
||||||
this IMvcCoreBuilder builder,
|
this IMvcCoreBuilder builder,
|
||||||
Action<RazorViewEngineOptions> setupAction)
|
Action<RazorPagesOptions> setupAction)
|
||||||
{
|
{
|
||||||
if (builder == null)
|
if (builder == null)
|
||||||
{
|
{
|
||||||
|
|
@ -43,6 +48,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddRazorViewEngine();
|
builder.AddRazorViewEngine();
|
||||||
|
|
||||||
|
AddFeatureProviders(builder);
|
||||||
AddServices(builder.Services);
|
AddServices(builder.Services);
|
||||||
|
|
||||||
builder.Services.Configure(setupAction);
|
builder.Services.Configure(setupAction);
|
||||||
|
|
@ -50,6 +57,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return builder;
|
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 for testing.
|
||||||
internal static void AddServices(IServiceCollection services)
|
internal static void AddServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
|
@ -57,10 +72,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
|
||||||
|
|
||||||
// Action Invoker
|
// Action description and invocation
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||||
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
|
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Singleton<IPageApplicationModelProvider, RazorProjectPageApplicationModelProvider>());
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Singleton<IPageApplicationModelProvider, CompiledPageApplicationModelProvider>());
|
||||||
|
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
public class PageActionDescriptorProvider : IActionDescriptorProvider
|
public class PageActionDescriptorProvider : IActionDescriptorProvider
|
||||||
{
|
{
|
||||||
private static readonly string IndexFileName = "Index.cshtml";
|
private readonly List<IPageApplicationModelProvider> _applicationModelProviders;
|
||||||
private readonly RazorProject _project;
|
|
||||||
private readonly MvcOptions _mvcOptions;
|
private readonly MvcOptions _mvcOptions;
|
||||||
private readonly RazorPagesOptions _pagesOptions;
|
private readonly RazorPagesOptions _pagesOptions;
|
||||||
|
|
||||||
public PageActionDescriptorProvider(
|
public PageActionDescriptorProvider(
|
||||||
RazorProject project,
|
IEnumerable<IPageApplicationModelProvider> pageMetadataProviders,
|
||||||
IOptions<MvcOptions> mvcOptionsAccessor,
|
IOptions<MvcOptions> mvcOptionsAccessor,
|
||||||
IOptions<RazorPagesOptions> pagesOptionsAccessor)
|
IOptions<RazorPagesOptions> pagesOptionsAccessor)
|
||||||
{
|
{
|
||||||
_project = project;
|
_applicationModelProviders = pageMetadataProviders.OrderBy(p => p.Order).ToList();
|
||||||
_mvcOptions = mvcOptionsAccessor.Value;
|
_mvcOptions = mvcOptionsAccessor.Value;
|
||||||
_pagesOptions = pagesOptionsAccessor.Value;
|
_pagesOptions = pagesOptionsAccessor.Value;
|
||||||
}
|
}
|
||||||
|
|
@ -34,57 +32,37 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
|
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("_"))
|
AddActionDescriptors(context.Results, pageApplicationModels[i]);
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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++)
|
for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
|
||||||
{
|
{
|
||||||
_pagesOptions.Conventions[i].Apply(model);
|
_pagesOptions.Conventions[i].Apply(model);
|
||||||
|
|
@ -111,28 +89,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
Order = selector.AttributeRouteModel.Order ?? 0,
|
Order = selector.AttributeRouteModel.Order ?? 0,
|
||||||
Template = selector.AttributeRouteModel.Template,
|
Template = selector.AttributeRouteModel.Template,
|
||||||
},
|
},
|
||||||
DisplayName = $"Page: {item.Path}",
|
DisplayName = $"Page: {model.ViewEnginePath}",
|
||||||
FilterDescriptors = filters,
|
FilterDescriptors = filters,
|
||||||
Properties = new Dictionary<object, object>(model.Properties),
|
Properties = new Dictionary<object, object>(model.Properties),
|
||||||
RelativePath = item.CombinedPath,
|
RelativePath = model.RelativePath,
|
||||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
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));
|
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";
|
const string PageDirective = "@page";
|
||||||
|
|
||||||
var stream = projectItem.Read();
|
string content;
|
||||||
|
using (var streamReader = new StreamReader(streamFactory()))
|
||||||
string content = null;
|
|
||||||
using (var streamReader = new StreamReader(stream))
|
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
content = streamReader.ReadLine();
|
content = streamReader.ReadLine();
|
||||||
|
|
||||||
} while (content != null && string.IsNullOrWhiteSpace(content));
|
} while (content != null && string.IsNullOrWhiteSpace(content));
|
||||||
content = content?.Trim();
|
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);
|
ViewStartFileName);
|
||||||
foreach (var item in viewStartItems)
|
foreach (var item in viewStartItems)
|
||||||
{
|
{
|
||||||
if (item.Exists)
|
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
|
||||||
|
if (factoryResult.Success)
|
||||||
{
|
{
|
||||||
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path);
|
viewStartFactories.Insert(0, factoryResult.RazorPageFactory);
|
||||||
if (factoryResult.Success)
|
|
||||||
{
|
|
||||||
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;
|
_containerLookup = containerLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Type GetViewInfoContainerType(AssemblyPart assemblyPart) =>
|
protected override ViewInfoContainer GetManifest(AssemblyPart assemblyPart)
|
||||||
_containerLookup[assemblyPart];
|
{
|
||||||
|
var type = _containerLookup[assemblyPart];
|
||||||
|
return (ViewInfoContainer)Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewInfoContainer1 : ViewInfoContainer
|
private class ViewInfoContainer1 : ViewInfoContainer
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public class PageActionDescriptorProviderTest
|
public class PageActionDescriptorProviderTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDescriptors_DoesNotAddDescriptorsForFilesWithoutDirectives()
|
public void GetDescriptors_DoesNotAddDescriptorsIfNoApplicationModelsAreDiscovered()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var razorProject = new Mock<RazorProject>();
|
var applicationModelProvider = new TestPageApplicationModelProvider();
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
|
||||||
.Returns(new[]
|
|
||||||
{
|
|
||||||
GetProjectItem("/", "/Index.cshtml", "<h1>Hello world</h1>"),
|
|
||||||
});
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor<MvcOptions>(),
|
GetAccessor<MvcOptions>(),
|
||||||
GetRazorPagesOptions());
|
GetRazorPagesOptions());
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -42,17 +37,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirective()
|
public void GetDescriptors_AddsDescriptorsForModelWithSelector()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var razorProject = new Mock<RazorProject>();
|
var model = new PageApplicationModel("/Test.cshtml", "/Test")
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
{
|
||||||
.Returns(new[]
|
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(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor<MvcOptions>(),
|
GetAccessor<MvcOptions>(),
|
||||||
GetRazorPagesOptions());
|
GetRazorPagesOptions());
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -65,53 +68,49 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/Test.cshtml", descriptor.RelativePath);
|
Assert.Equal("/Test.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Test", descriptor.RouteValues["page"]);
|
Assert.Equal("/Test", descriptor.RouteValues["page"]);
|
||||||
Assert.Equal("Test", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("/Test/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDescriptors_AddsDescriptorsForFileWithPageDirectiveAndRouteTemplate()
|
public void GetDescriptors_AddsActionDescriptorForEachSelector()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var razorProject = new Mock<RazorProject>();
|
var applicationModelProvider = new TestPageApplicationModelProvider(
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
new PageApplicationModel("/base-path/Test.cshtml", "/base-path/Test")
|
||||||
.Returns(new[]
|
|
||||||
{
|
{
|
||||||
GetProjectItem("/", "/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
Selectors =
|
||||||
});
|
{
|
||||||
var provider = new PageActionDescriptorProvider(
|
CreateSelectorModel("base-path/Test/Home")
|
||||||
razorProject.Object,
|
}
|
||||||
GetAccessor<MvcOptions>(),
|
},
|
||||||
GetRazorPagesOptions());
|
new PageApplicationModel("/base-path/Index.cshtml", "/base-path/Index")
|
||||||
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[]
|
|
||||||
{
|
{
|
||||||
GetProjectItem("/", "/base-path/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
Selectors =
|
||||||
GetProjectItem("/", "/base-path/Index.cshtml", $"@page {Environment.NewLine}"),
|
{
|
||||||
GetProjectItem("/", "/base-path/Admin/Index.cshtml", $"@page{Environment.NewLine}"),
|
CreateSelectorModel("base-path/Index"),
|
||||||
GetProjectItem("/", "/base-path/Admin/User.cshtml", $"@page{Environment.NewLine}"),
|
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 options = GetRazorPagesOptions();
|
||||||
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor<MvcOptions>(),
|
GetAccessor<MvcOptions>(),
|
||||||
options);
|
options);
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -123,122 +122,39 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
Assert.Collection(context.Results,
|
Assert.Collection(context.Results,
|
||||||
result => Assert.Equal("base-path/Test/Home", result.AttributeRouteInfo.Template),
|
result => Assert.Equal("base-path/Test/Home", result.AttributeRouteInfo.Template),
|
||||||
result => Assert.Equal("base-path/Index", 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/Index", result.AttributeRouteInfo.Template),
|
||||||
result => Assert.Equal("base-path/Admin", result.AttributeRouteInfo.Template),
|
result => Assert.Equal("base-path/Admin", result.AttributeRouteInfo.Template),
|
||||||
result => Assert.Equal("base-path/Admin/User", result.AttributeRouteInfo.Template));
|
result => Assert.Equal("base-path/Admin/User", result.AttributeRouteInfo.Template));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
private static SelectorModel CreateSelectorModel(string template)
|
||||||
public void GetDescriptors_UsesBasePathOption_WhenGeneratingRouteTemplate()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
return new SelectorModel
|
||||||
var razorProject = new Mock<RazorProject>(MockBehavior.Strict);
|
{
|
||||||
razorProject.Setup(p => p.EnumerateItems("/base-path"))
|
AttributeRouteModel = new AttributeRouteModel
|
||||||
.Returns(new[]
|
|
||||||
{
|
{
|
||||||
GetProjectItem("/base-path", "/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
Template = template,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDescriptors_WithEmptyPageDirective_MapsIndexToEmptySegment()
|
public void GetDescriptors_AddsMultipleDescriptorsForPageWithMultipleSelectors()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var razorProject = new Mock<RazorProject>();
|
var applicationModelProvider = new TestPageApplicationModelProvider(
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
new PageApplicationModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
|
||||||
.Returns(new[]
|
|
||||||
{
|
{
|
||||||
GetProjectItem("", "/About/Index.cshtml", $"@page {Environment.NewLine}"),
|
Selectors =
|
||||||
|
{
|
||||||
|
CreateSelectorModel("/Catalog/Details/Index/{id:int?}"),
|
||||||
|
CreateSelectorModel("/Catalog/Details/{id:int?}"),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
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,
|
|
||||||
GetAccessor<MvcOptions>(),
|
GetAccessor<MvcOptions>(),
|
||||||
GetRazorPagesOptions());
|
GetRazorPagesOptions());
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -253,14 +169,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
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 =>
|
result =>
|
||||||
{
|
{
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
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
|
// Arrange
|
||||||
var options = new MvcOptions();
|
var options = new MvcOptions();
|
||||||
var razorProject = new Mock<RazorProject>();
|
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
|
||||||
.Returns(new[]
|
|
||||||
{
|
|
||||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
|
||||||
});
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor(options),
|
GetAccessor(options),
|
||||||
GetRazorPagesOptions());
|
GetRazorPagesOptions());
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -310,14 +221,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var options = new MvcOptions();
|
var options = new MvcOptions();
|
||||||
options.Filters.Add(filter1);
|
options.Filters.Add(filter1);
|
||||||
options.Filters.Add(filter2);
|
options.Filters.Add(filter2);
|
||||||
var razorProject = new Mock<RazorProject>();
|
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
|
||||||
.Returns(new[]
|
|
||||||
{
|
|
||||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
|
||||||
});
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor(options),
|
GetAccessor(options),
|
||||||
GetRazorPagesOptions());
|
GetRazorPagesOptions());
|
||||||
var context = new ActionDescriptorProviderContext();
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
@ -368,15 +274,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
});
|
});
|
||||||
var razorOptions = GetRazorPagesOptions();
|
var razorOptions = GetRazorPagesOptions();
|
||||||
razorOptions.Value.Conventions.Add(convention.Object);
|
razorOptions.Value.Conventions.Add(convention.Object);
|
||||||
|
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
|
||||||
var razorProject = new Mock<RazorProject>();
|
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
|
||||||
.Returns(new[]
|
|
||||||
{
|
|
||||||
GetProjectItem("/", "/Home.cshtml", $"@page {Environment.NewLine}"),
|
|
||||||
});
|
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
new[] { applicationModelProvider },
|
||||||
GetAccessor(options),
|
GetAccessor(options),
|
||||||
razorOptions);
|
razorOptions);
|
||||||
var context = new ActionDescriptorProviderContext();
|
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)
|
private static IOptions<TOptions> GetAccessor<TOptions>(TOptions options = null)
|
||||||
where TOptions : class, new()
|
where TOptions : class, new()
|
||||||
{
|
{
|
||||||
|
|
@ -432,5 +349,30 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
return new DefaultRazorProjectItem(testFileInfo, basePath, path);
|
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]
|
[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
|
// Arrange
|
||||||
var descriptor = new PageActionDescriptor()
|
var descriptor = new PageActionDescriptor()
|
||||||
{
|
{
|
||||||
|
|
@ -739,6 +741,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
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
|
// No files
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var razorProject = new TestRazorProject(fileProvider);
|
var razorProject = new TestRazorProject(fileProvider);
|
||||||
|
|
@ -748,16 +758,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
actionDescriptorProvider.Object,
|
actionDescriptorProvider.Object,
|
||||||
pageProvider: null,
|
pageProvider: null,
|
||||||
modelProvider: null,
|
modelProvider: null,
|
||||||
razorPageFactoryProvider: CreateRazorPageFactoryProvider(),
|
razorPageFactoryProvider: pageFactory.Object,
|
||||||
razorProject: razorProject);
|
razorProject: razorProject);
|
||||||
|
|
||||||
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);
|
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var factories = invokerProvider.GetViewStartFactories(compiledDescriptor);
|
var factories = invokerProvider.GetViewStartFactories(compiledDescriptor).ToList();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(factories);
|
Assert.Equal(2, factories.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRazorPageFactoryProvider CreateRazorPageFactoryProvider()
|
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<ControllerFeatureProvider>(feature),
|
||||||
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
||||||
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
||||||
feature => Assert.IsType<ViewsFeatureProvider>(feature));
|
feature => Assert.IsType<ViewsFeatureProvider>(feature),
|
||||||
|
feature => Assert.IsType<CompiledPageFeatureProvider>(feature));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -419,6 +420,14 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
typeof(JsonPatchOperationsArrayProvider),
|
typeof(JsonPatchOperationsArrayProvider),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
typeof(IPageApplicationModelProvider),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
typeof(CompiledPageApplicationModelProvider),
|
||||||
|
typeof(RazorProjectPageApplicationModelProvider),
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,41 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.TestCommon
|
namespace Microsoft.AspNetCore.Mvc.TestCommon
|
||||||
{
|
{
|
||||||
public class TestDirectoryContent : IDirectoryContents
|
public class TestDirectoryContent : IDirectoryContents, IFileInfo
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IFileInfo> _files;
|
private readonly IEnumerable<IFileInfo> _files;
|
||||||
|
|
||||||
public TestDirectoryContent(IEnumerable<IFileInfo> files)
|
public TestDirectoryContent(string name, IEnumerable<IFileInfo> files)
|
||||||
{
|
{
|
||||||
|
Name = name;
|
||||||
_files = files;
|
_files = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists => true;
|
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();
|
public IEnumerator<IFileInfo> GetEnumerator() => _files.GetEnumerator();
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
|
|
||||||
public TestDirectoryContent AddDirectoryContent(string path, IEnumerable<IFileInfo> files)
|
public TestDirectoryContent AddDirectoryContent(string path, IEnumerable<IFileInfo> files)
|
||||||
{
|
{
|
||||||
var directoryContent = new TestDirectoryContent(files);
|
var directoryContent = new TestDirectoryContent(Path.GetFileName(path), files);
|
||||||
_directoryContentsLookup[path] = directoryContent;
|
_directoryContentsLookup[path] = directoryContent;
|
||||||
return directoryContent;
|
return directoryContent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue