Add support for specifying filters on page models.

Fixes #6334
This commit is contained in:
Pranav K 2017-06-20 14:25:22 -07:00
parent 7166dfecd7
commit 8df3032540
49 changed files with 2894 additions and 1216 deletions

View File

@ -0,0 +1,17 @@
// 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>
/// Allows customization of the of the <see cref="PageRouteModel"/>.
/// </summary>
public interface IPageRouteModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="PageRouteModel"/>.
/// </summary>
/// <param name="model">The <see cref="PageRouteModel"/>.</param>
void Apply(PageRouteModel model);
}
}

View File

@ -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="PageRouteModelProviderContext"/> for Razor Page routing.
/// </summary>
public interface IPageRouteModelProvider
{
/// <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="PageRouteModel"/> instances. See <see cref="Order"/>.
/// </summary>
/// <param name="context">The <see cref="PageRouteModelProviderContext"/>.</param>
void OnProvidersExecuting(PageRouteModelProviderContext context);
/// <summary>
/// Executed for the second pass of building <see cref="PageRouteModel"/> instances. See <see cref="Order"/>.
/// </summary>
/// <param name="context">The <see cref="PageRouteModelProviderContext"/>.</param>
void OnProvidersExecuted(PageRouteModelProviderContext context);
}
}

View File

@ -4,7 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
@ -16,26 +19,21 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// <summary>
/// Initializes a new instance of <see cref="PageApplicationModel"/>.
/// </summary>
/// <param name="relativePath">The application relative path of the page.</param>
/// <param name="viewEnginePath">The path relative to the base path for page discovery.</param>
public PageApplicationModel(string relativePath, string viewEnginePath)
public PageApplicationModel(
PageActionDescriptor actionDescriptor,
TypeInfo handlerType,
IReadOnlyList<object> handlerAttributes)
{
if (relativePath == null)
{
throw new ArgumentNullException(nameof(relativePath));
}
if (viewEnginePath == null)
{
throw new ArgumentNullException(nameof(viewEnginePath));
}
RelativePath = relativePath;
ViewEnginePath = viewEnginePath;
ActionDescriptor = actionDescriptor ?? throw new ArgumentNullException(nameof(actionDescriptor));
HandlerType = handlerType;
Filters = new List<IFilterMetadata>();
Properties = new Dictionary<object, object>();
Selectors = new List<SelectorModel>();
Properties = new CopyOnWriteDictionary<object, object>(
actionDescriptor.Properties,
EqualityComparer<object>.Default);
HandlerMethods = new List<PageHandlerModel>();
HandlerProperties = new List<PagePropertyModel>();
HandlerTypeAttributes = handlerAttributes;
}
/// <summary>
@ -49,24 +47,38 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
throw new ArgumentNullException(nameof(other));
}
RelativePath = other.RelativePath;
ViewEnginePath = other.ViewEnginePath;
ActionDescriptor = other.ActionDescriptor;
HandlerType = other.HandlerType;
PageType = other.PageType;
ModelType = other.ModelType;
Filters = new List<IFilterMetadata>(other.Filters);
Properties = new Dictionary<object, object>(other.Properties);
Selectors = new List<SelectorModel>(other.Selectors.Select(m => new SelectorModel(m)));
HandlerMethods = new List<PageHandlerModel>(other.HandlerMethods.Select(m => new PageHandlerModel(m)));
HandlerProperties = new List<PagePropertyModel>(other.HandlerProperties.Select(p => new PagePropertyModel(p)));
HandlerTypeAttributes = other.HandlerTypeAttributes;
}
/// <summary>
/// Gets the <see cref="PageActionDescriptor"/>.
/// </summary>
public PageActionDescriptor ActionDescriptor { get; }
/// <summary>
/// Gets the application root relative path for the page.
/// </summary>
public string RelativePath { get; }
public string RelativePath => ActionDescriptor.RelativePath;
/// <summary>
/// Gets the path relative to the base path for page discovery.
/// </summary>
public string ViewEnginePath { get; }
public string ViewEnginePath => ActionDescriptor.ViewEnginePath;
/// <summary>
/// Gets the route template for the page.
/// </summary>
public string RouteTemplate => ActionDescriptor.AttributeRouteInfo?.Template;
/// <summary>
/// Gets the applicable <see cref="IFilterMetadata"/> instances.
@ -79,8 +91,33 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IDictionary<object, object> Properties { get; }
/// <summary>
/// Gets the <see cref="SelectorModel"/> instances.
/// Gets or sets the <see cref="TypeInfo"/> of the Razor page.
/// </summary>
public IList<SelectorModel> Selectors { get; }
public TypeInfo PageType { get; set; }
/// <summary>
/// Gets or sets the <see cref="TypeInfo"/> of the Razor page model.
/// </summary>
public TypeInfo ModelType { get; set; }
/// <summary>
/// Gets the <see cref="TypeInfo"/> of the handler.
/// </summary>
public TypeInfo HandlerType { get; }
/// <summary>
/// Gets the sequence of attributes declared on <see cref="HandlerType"/>.
/// </summary>
public IReadOnlyList<object> HandlerTypeAttributes { get; }
/// <summary>
/// Gets the sequence of <see cref="PageHandlerModel"/> instances.
/// </summary>
public IList<PageHandlerModel> HandlerMethods { get; }
/// <summary>
/// Gets the sequence of <see cref="PagePropertyModel"/> instances on <see cref="PageHandlerModel"/>.
/// </summary>
public IList<PagePropertyModel> HandlerProperties { get; }
}
}

View File

@ -1,7 +1,8 @@
// 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 System.Reflection;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
@ -10,9 +11,25 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// </summary>
public class PageApplicationModelProviderContext
{
public PageApplicationModelProviderContext(PageActionDescriptor descriptor, TypeInfo pageTypeInfo)
{
ActionDescriptor = descriptor;
PageType = pageTypeInfo;
}
/// <summary>
/// Gets the <see cref="PageApplicationModel"/> instances.
/// Gets the <see cref="PageActionDescriptor"/>.
/// </summary>
public IList<PageApplicationModel> Results { get; } = new List<PageApplicationModel>();
public PageActionDescriptor ActionDescriptor { get; }
/// <summary>
/// Gets the page <see cref="TypeInfo"/>.
/// </summary>
public TypeInfo PageType { get; }
/// <summary>
/// Gets or sets the <see cref="ApplicationModels.PageApplicationModel"/>.
/// </summary>
public PageApplicationModel PageApplicationModel { get; set; }
}
}

View File

@ -0,0 +1,98 @@
// 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.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Represents a handler in a <see cref="PageApplicationModel"/>.
/// </summary>
[DebuggerDisplay("PageHandlerModel: Name={" + nameof(PageHandlerModel.Name) + "}")]
public class PageHandlerModel : ICommonModel
{
/// <summary>
/// Creates a new <see cref="PageHandlerModel"/>.
/// </summary>
/// <param name="handlerMethod">The <see cref="System.Reflection.MethodInfo"/> for the handler.</param>
/// <param name="attributes">Any attributes annotated on the handler method.</param>
public PageHandlerModel(
MethodInfo handlerMethod,
IReadOnlyList<object> attributes)
{
MethodInfo = handlerMethod ?? throw new ArgumentNullException(nameof(handlerMethod));
Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
Parameters = new List<PageParameterModel>();
Properties = new Dictionary<object, object>();
}
/// <summary>
/// Creats a new instance of <see cref="PageHandlerModel"/> from a given <see cref="PageHandlerModel"/>.
/// </summary>
/// <param name="other">The <see cref="PageHandlerModel"/> which needs to be copied.</param>
public PageHandlerModel(PageHandlerModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
MethodInfo = other.MethodInfo;
HandlerName = other.HandlerName;
HttpMethod = other.HttpMethod;
Name = other.Name;
Page = other.Page;
// These are just metadata, safe to create new collections
Attributes = new List<object>(other.Attributes);
Properties = new Dictionary<object, object>(other.Properties);
// Make a deep copy of other 'model' types.
Parameters = new List<PageParameterModel>(other.Parameters.Select(p => new PageParameterModel(p) { Handler = this }));
}
/// <summary>
/// Gets the <see cref="System.Reflection.MethodInfo"/> for the handler.
/// </summary>
public MethodInfo MethodInfo { get; }
/// <summary>
/// Gets or sets the HTTP method supported by this handler.
/// </summary>
public string HttpMethod { get; set; }
/// <summary>
/// Gets or sets the handler method name.
/// </summary>
public string HandlerName { get; set; }
/// <summary>
/// Gets or sets a descriptive name for the handler.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets the sequence of <see cref="PageParameterModel"/> instances.
/// </summary>
public IList<PageParameterModel> Parameters { get; }
/// <summary>
/// Gets or sets the <see cref="PageApplicationModel"/>.
/// </summary>
public PageApplicationModel Page { get; set; }
/// <inheritdoc />
public IReadOnlyList<object> Attributes { get; }
/// <inheritdoc />
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo => MethodInfo;
}
}

View File

@ -0,0 +1,61 @@
// 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.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
[DebuggerDisplay("PageParameterModel: Name={ParameterName}")]
public class PageParameterModel : ICommonModel, IBindingModel
{
public PageParameterModel(
ParameterInfo parameterInfo,
IReadOnlyList<object> attributes)
{
ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes);
}
public PageParameterModel(PageParameterModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
Handler = other.Handler;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
Properties = new Dictionary<object, object>(other.Properties);
}
public PageHandlerModel Handler { get; set; }
public IReadOnlyList<object> Attributes { get; }
public IDictionary<object, object> Properties { get; }
MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member;
string ICommonModel.Name => ParameterName;
public ParameterInfo ParameterInfo { get; }
public string ParameterName { get; set; }
public BindingInfo BindingInfo { get; set; }
}
}

View File

@ -0,0 +1,83 @@
// 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.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Represents a property in a <see cref="PageApplicationModel"/>.
/// </summary>
[DebuggerDisplay("PagePropertyModel: Name={PropertyName}")]
public class PagePropertyModel : ICommonModel, IBindingModel
{
/// <summary>
/// Creates a new instance of <see cref="PagePropertyModel"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> for the underlying property.</param>
/// <param name="attributes">Any attributes which are annotated on the property.</param>
public PagePropertyModel(
PropertyInfo propertyInfo,
IReadOnlyList<object> attributes)
{
PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
Properties = new Dictionary<object, object>();
Attributes = new List<object>(attributes) ?? throw new ArgumentNullException(nameof(attributes));
}
/// <summary>
/// Creats a new instance of <see cref="PagePropertyModel"/> from a given <see cref="PagePropertyModel"/>.
/// </summary>
/// <param name="other">The <see cref="PagePropertyModel"/> which needs to be copied.</param>
public PagePropertyModel(PagePropertyModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
Page = other.Page;
Attributes = new List<object>(other.Attributes);
BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo);
PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
Properties = new Dictionary<object, object>(other.Properties);
}
/// <summary>
/// Gets or sets the <see cref="PageApplicationModel"/> this <see cref="PagePropertyModel"/> is associated with.
/// </summary>
public PageApplicationModel Page { get; set; }
/// <summary>
/// Gets any attributes which are annotated on the property.
/// </summary>
public IReadOnlyList<object> Attributes { get; }
/// <inheritdoc />
public IDictionary<object, object> Properties { get; }
/// <summary>
/// Gets or sets the <see cref="BindingInfo"/> associated with this model.
/// </summary>
public BindingInfo BindingInfo { get; set; }
/// <summary>
/// Gets the underlying <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo PropertyInfo { get; }
/// <summary>
/// Gets or sets the name of the property represented by this model.
/// </summary>
public string PropertyName { get; set; }
MemberInfo ICommonModel.MemberInfo => PropertyInfo;
string ICommonModel.Name => PropertyName;
}
}

View File

@ -0,0 +1,77 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// A model component for routing RazorPages.
/// </summary>
public class PageRouteModel
{
/// <summary>
/// Initializes a new instance of <see cref="PageRouteModel"/>.
/// </summary>
/// <param name="relativePath">The application relative path of the page.</param>
/// <param name="viewEnginePath">The path relative to the base path for page discovery.</param>
public PageRouteModel(string relativePath, string viewEnginePath)
{
if (relativePath == null)
{
throw new ArgumentNullException(nameof(relativePath));
}
if (viewEnginePath == null)
{
throw new ArgumentNullException(nameof(viewEnginePath));
}
RelativePath = relativePath;
ViewEnginePath = viewEnginePath;
Properties = new Dictionary<object, object>();
Selectors = new List<SelectorModel>();
}
/// <summary>
/// A copy constructor for <see cref="PageRouteModel"/>.
/// </summary>
/// <param name="other">The <see cref="PageRouteModel"/> to copy from.</param>
public PageRouteModel(PageRouteModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
RelativePath = other.RelativePath;
ViewEnginePath = other.ViewEnginePath;
Properties = new Dictionary<object, object>(other.Properties);
Selectors = new List<SelectorModel>(other.Selectors.Select(m => new SelectorModel(m)));
}
/// <summary>
/// Gets the application root relative path for the page.
/// </summary>
public string RelativePath { get; }
/// <summary>
/// Gets the path relative to the base path for page discovery.
/// </summary>
public string ViewEnginePath { get; }
/// <summary>
/// Stores arbitrary metadata properties associated with the <see cref="PageRouteModel"/>.
/// </summary>
public IDictionary<object, object> Properties { get; }
/// <summary>
/// Gets the <see cref="SelectorModel"/> instances.
/// </summary>
public IList<SelectorModel> Selectors { get; }
}
}

View File

@ -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="IPageRouteModelProvider"/>.
/// </summary>
public class PageRouteModelProviderContext
{
/// <summary>
/// Gets the <see cref="PageRouteModel"/> instances.
/// </summary>
public IList<PageRouteModel> RouteModels { get; } = new List<PageRouteModel>();
}
}

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -88,11 +87,18 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, RazorProjectPageApplicationModelProvider>());
ServiceDescriptor.Singleton<IPageRouteModelProvider, RazorProjectPageRouteModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, CompiledPageApplicationModelProvider>());
ServiceDescriptor.Singleton<IPageRouteModelProvider, CompiledPageRouteModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, PageFilterApplicationModelProvider>());
ServiceDescriptor.Singleton<IPageApplicationModelProvider, DefaultPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, AutoValidateAntiforgeryPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, AuthorizationPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, TempDataFilterPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());

View File

@ -32,7 +32,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(factory));
}
options.Conventions.Add(new FolderConvention("/", model => model.Filters.Add(factory(model))));
options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(factory(model))));
return options;
}
@ -54,7 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(filter));
}
options.Conventions.Add(new FolderConvention("/", model => model.Filters.Add(filter)));
options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(filter)));
return options;
}
@ -77,7 +77,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var anonymousFilter = new AllowAnonymousFilter();
options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(anonymousFilter)));
options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter)));
return options;
}
@ -100,7 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var anonymousFilter = new AllowAnonymousFilter();
options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(anonymousFilter)));
options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter)));
return options;
}
@ -124,7 +124,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var authorizeFilter = new AuthorizeFilter(policy);
options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(authorizeFilter)));
options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter)));
return options;
}
@ -157,7 +157,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
var authorizeFilter = new AuthorizeFilter(policy);
options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(authorizeFilter)));
options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter)));
return options;
}
@ -198,7 +198,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(route));
}
options.Conventions.Add(new PageConvention(pageName, model =>
options.RouteModelConventions.Add(new PageRouteModelConvention(pageName, model =>
{
// Use the route specified in MapPageRoute for outbound routing.
foreach (var selector in model.Selectors)
@ -218,12 +218,54 @@ namespace Microsoft.Extensions.DependencyInjection
return options;
}
private class PageConvention : IPageApplicationModelConvention
private class PageRouteModelConvention : IPageRouteModelConvention
{
private readonly string _path;
private readonly Action<PageRouteModel> _action;
public PageRouteModelConvention(string path, Action<PageRouteModel> action)
{
_path = path;
_action = action;
}
public void Apply(PageRouteModel model)
{
if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase))
{
_action(model);
}
}
}
private class FolderRouteModelConvention : IPageRouteModelConvention
{
private readonly string _folderPath;
private readonly Action<PageRouteModel> _action;
public FolderRouteModelConvention(string folderPath, Action<PageRouteModel> action)
{
_folderPath = folderPath.TrimEnd('/');
_action = action;
}
public void Apply(PageRouteModel model)
{
var viewEnginePath = model.ViewEnginePath;
if (PathBelongsToFolder(_folderPath, viewEnginePath))
{
_action(model);
}
}
}
private class PageApplicationModelConvention : IPageApplicationModelConvention
{
private readonly string _path;
private readonly Action<PageApplicationModel> _action;
public PageConvention(string path, Action<PageApplicationModel> action)
public PageApplicationModelConvention(string path, Action<PageApplicationModel> action)
{
_path = path;
_action = action;
@ -238,12 +280,12 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
private class FolderConvention : IPageApplicationModelConvention
private class FolderApplicationModelConvention : IPageApplicationModelConvention
{
private readonly string _folderPath;
private readonly Action<PageApplicationModel> _action;
public FolderConvention(string folderPath, Action<PageApplicationModel> action)
public FolderApplicationModelConvention(string folderPath, Action<PageApplicationModel> action)
{
_folderPath = folderPath.TrimEnd('/');
_action = action;
@ -253,16 +295,24 @@ namespace Microsoft.Extensions.DependencyInjection
{
var viewEnginePath = model.ViewEnginePath;
var applyConvention = _folderPath == "/" ||
(viewEnginePath.Length > _folderPath.Length &&
viewEnginePath.StartsWith(_folderPath, StringComparison.OrdinalIgnoreCase) &&
viewEnginePath[_folderPath.Length] == '/');
if (applyConvention)
if (PathBelongsToFolder(_folderPath, viewEnginePath))
{
_action(model);
}
}
}
private static bool PathBelongsToFolder(string folderPath, string viewEnginePath)
{
if (folderPath == "/")
{
// Root directory covers everything.
return true;
}
return viewEnginePath.Length > folderPath.Length &&
viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) &&
viewEnginePath[folderPath.Length] == '/';
}
}
}

View File

@ -14,16 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class PageActionDescriptorProvider : IActionDescriptorProvider
{
private readonly List<IPageApplicationModelProvider> _applicationModelProviders;
private readonly IPageRouteModelProvider[] _routeModelProviders;
private readonly MvcOptions _mvcOptions;
private readonly RazorPagesOptions _pagesOptions;
public PageActionDescriptorProvider(
IEnumerable<IPageApplicationModelProvider> pageMetadataProviders,
IEnumerable<IPageRouteModelProvider> pageRouteModelProviders,
IOptions<MvcOptions> mvcOptionsAccessor,
IOptions<RazorPagesOptions> pagesOptionsAccessor)
{
_applicationModelProviders = pageMetadataProviders.OrderBy(p => p.Order).ToList();
_routeModelProviders = pageRouteModelProviders.OrderBy(p => p.Order).ToArray();
_mvcOptions = mvcOptionsAccessor.Value;
_pagesOptions = pagesOptionsAccessor.Value;
}
@ -32,51 +32,40 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
{
var pageApplicationModels = BuildModel();
var pageRouteModels = BuildModel();
for (var i = 0; i < pageApplicationModels.Count; i++)
for (var i = 0; i < pageRouteModels.Count; i++)
{
AddActionDescriptors(context.Results, pageApplicationModels[i]);
AddActionDescriptors(context.Results, pageRouteModels[i]);
}
}
protected IList<PageApplicationModel> BuildModel()
protected IList<PageRouteModel> BuildModel()
{
var context = new PageApplicationModelProviderContext();
var context = new PageRouteModelProviderContext();
for (var i = 0; i < _applicationModelProviders.Count; i++)
for (var i = 0; i < _routeModelProviders.Length; i++)
{
_applicationModelProviders[i].OnProvidersExecuting(context);
_routeModelProviders[i].OnProvidersExecuting(context);
}
for (var i = _applicationModelProviders.Count - 1; i >= 0; i--)
for (var i = _routeModelProviders.Length - 1; i >= 0; i--)
{
_applicationModelProviders[i].OnProvidersExecuted(context);
_routeModelProviders[i].OnProvidersExecuted(context);
}
return context.Results;
return context.RouteModels;
}
public void OnProvidersExecuted(ActionDescriptorProviderContext context)
{
}
private void AddActionDescriptors(IList<ActionDescriptor> actions, PageApplicationModel model)
private void AddActionDescriptors(IList<ActionDescriptor> actions, PageRouteModel model)
{
for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
for (var i = 0; i < _pagesOptions.RouteModelConventions.Count; i++)
{
_pagesOptions.Conventions[i].Apply(model);
}
var filters = new List<FilterDescriptor>(_mvcOptions.Filters.Count + model.Filters.Count);
for (var i = 0; i < _mvcOptions.Filters.Count; i++)
{
filters.Add(new FilterDescriptor(_mvcOptions.Filters[i], FilterScope.Global));
}
for (var i = 0; i < model.Filters.Count; i++)
{
filters.Add(new FilterDescriptor(model.Filters[i], FilterScope.Action));
_pagesOptions.RouteModelConventions[i].Apply(model);
}
foreach (var selector in model.Selectors)
@ -92,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching,
},
DisplayName = $"Page: {model.ViewEnginePath}",
FilterDescriptors = filters,
FilterDescriptors = Array.Empty<FilterDescriptor>(),
Properties = new Dictionary<object, object>(model.Properties),
RelativePath = model.RelativePath,
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)

View File

@ -0,0 +1,48 @@
// 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.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class AuthorizationPageApplicationModelProvider : IPageApplicationModelProvider
{
private readonly IAuthorizationPolicyProvider _policyProvider;
public AuthorizationPageApplicationModelProvider(IAuthorizationPolicyProvider policyProvider)
{
_policyProvider = policyProvider;
}
// The order is set to execute after the DefaultPageApplicationModelProvider.
public int Order => -1000 + 10;
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var pageModel = context.PageApplicationModel;
var authorizeData = pageModel.HandlerTypeAttributes.OfType<IAuthorizeData>().ToArray();
if (authorizeData.Length > 0)
{
pageModel.Filters.Add(AuthorizationApplicationModelProvider.GetFilter(_policyProvider, authorizeData));
}
foreach (var attribute in pageModel.HandlerTypeAttributes.OfType<IAllowAnonymous>())
{
pageModel.Filters.Add(new AllowAnonymousFilter());
}
}
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
}
}
}

View File

@ -0,0 +1,31 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class AutoValidateAntiforgeryPageApplicationModelProvider : IPageApplicationModelProvider
{
// The order is set to execute after the DefaultPageApplicationModelProvider.
public int Order => -1000 + 10;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var pageApplicationModel = context.PageApplicationModel;
// Always require an antiforgery token on post
pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}
}
}

View File

@ -0,0 +1,119 @@
// 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 System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
/// <summary>
/// Constructs a <see cref="CompiledPageActionDescriptor"/> from an <see cref="PageApplicationModel"/>.
/// </summary>
public static class CompiledPageActionDescriptorBuilder
{
/// <summary>
/// Creates a <see cref="CompiledPageActionDescriptor"/> from the specified <paramref name="applicationModel"/>.
/// </summary>
/// <param name="applicationModel">The <see cref="PageApplicationModel"/>.</param>
/// <returns>The <see cref="CompiledPageActionDescriptor"/>.</returns>
public static CompiledPageActionDescriptor Build(PageApplicationModel applicationModel)
{
var boundProperties = CreateBoundProperties(applicationModel);
var filters = applicationModel.Filters
.Select(f => new FilterDescriptor(f, FilterScope.Action))
.ToArray();
var handlerMethods = CreateHandlerMethods(applicationModel);
var actionDescriptor = applicationModel.ActionDescriptor;
return new CompiledPageActionDescriptor(actionDescriptor)
{
ActionConstraints = actionDescriptor.ActionConstraints,
AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
BoundProperties = boundProperties,
FilterDescriptors = filters,
HandlerMethods = handlerMethods,
HandlerTypeInfo = applicationModel.HandlerType,
ModelTypeInfo = applicationModel.ModelType,
RouteValues = actionDescriptor.RouteValues,
PageTypeInfo = applicationModel.PageType,
Properties = applicationModel.Properties,
};
}
// Internal for unit testing
internal static HandlerMethodDescriptor[] CreateHandlerMethods(PageApplicationModel applicationModel)
{
var handlerModels = applicationModel.HandlerMethods;
var handlerDescriptors = new HandlerMethodDescriptor[handlerModels.Count];
for (var i = 0; i < handlerDescriptors.Length; i++)
{
var handlerModel = handlerModels[i];
handlerDescriptors[i] = new HandlerMethodDescriptor
{
HttpMethod = handlerModel.HttpMethod,
Name = handlerModel.HandlerName,
MethodInfo = handlerModel.MethodInfo,
Parameters = CreateHandlerParameters(handlerModel),
};
}
return handlerDescriptors;
}
// internal for unit testing
internal static HandlerParameterDescriptor[] CreateHandlerParameters(PageHandlerModel handlerModel)
{
var methodParameters = handlerModel.Parameters;
var parameters = new HandlerParameterDescriptor[methodParameters.Count];
for (var i = 0; i < parameters.Length; i++)
{
var parameterModel = methodParameters[i];
parameters[i] = new HandlerParameterDescriptor
{
BindingInfo = parameterModel.BindingInfo,
Name = parameterModel.ParameterName,
ParameterInfo = parameterModel.ParameterInfo,
ParameterType = parameterModel.ParameterInfo.ParameterType,
};
}
return parameters;
}
// internal for unit testing
internal static PageBoundPropertyDescriptor[] CreateBoundProperties(PageApplicationModel applicationModel)
{
var results = new List<PageBoundPropertyDescriptor>();
var properties = applicationModel.HandlerProperties;
for (var i = 0; i < properties.Count; i++)
{
var propertyModel = properties[i];
// Only add properties which are explicitly marked to bind.
if (propertyModel.BindingInfo == null)
{
continue;
}
var descriptor = new PageBoundPropertyDescriptor
{
Property = propertyModel.PropertyInfo,
Name = propertyModel.PropertyName,
BindingInfo = propertyModel.BindingInfo,
ParameterType = propertyModel.PropertyInfo.PropertyType,
};
results.Add(descriptor);
}
return results.ToArray();
}
}
}

View File

@ -12,14 +12,14 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class CompiledPageApplicationModelProvider : IPageApplicationModelProvider
public class CompiledPageRouteModelProvider : IPageRouteModelProvider
{
private readonly object _cacheLock = new object();
private readonly ApplicationPartManager _applicationManager;
private readonly RazorPagesOptions _pagesOptions;
private List<PageApplicationModel> _cachedApplicationModels;
private List<PageRouteModel> _cachedModels;
public CompiledPageApplicationModelProvider(
public CompiledPageRouteModelProvider(
ApplicationPartManager applicationManager,
IOptions<RazorPagesOptions> pagesOptionsAccessor)
{
@ -29,17 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public int Order => -1000;
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
EnsureCache();
for (var i = 0; i < _cachedApplicationModels.Count; i++)
for (var i = 0; i < _cachedModels.Count; i++)
{
var pageModel = _cachedApplicationModels[i];
context.Results.Add(new PageApplicationModel(pageModel));
var pageModel = _cachedModels[i];
context.RouteModels.Add(new PageRouteModel(pageModel));
}
}
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
lock (_cacheLock)
{
if (_cachedApplicationModels != null)
if (_cachedModels != null)
{
return;
}
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
rootDirectory = rootDirectory + "/";
}
var cachedApplicationModels = new List<PageApplicationModel>();
var cachedApplicationModels = new List<PageRouteModel>();
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
{
if (!viewDescriptor.RelativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
@ -67,14 +67,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var viewEnginePath = GetViewEnginePath(rootDirectory, viewDescriptor.RelativePath);
var model = new PageApplicationModel(viewDescriptor.RelativePath, viewEnginePath);
var model = new PageRouteModel(viewDescriptor.RelativePath, viewEnginePath);
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
PageSelectorModel.PopulateDefaults(model, pageAttribute.RouteTemplate);
cachedApplicationModels.Add(model);
}
_cachedApplicationModels = cachedApplicationModels;
_cachedModels = cachedApplicationModels;
}
}

View File

@ -0,0 +1,353 @@
// 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.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageApplicationModelProvider : IPageApplicationModelProvider
{
private const string ModelPropertyName = "Model";
private readonly FilterCollection _globalFilters;
/// <summary>
/// Initializes a new instance of <see cref="DefaultPageApplicationModelProvider"/>.
/// </summary>
/// <param name="mvcOptions"></param>
public DefaultPageApplicationModelProvider(IOptions<MvcOptions> mvcOptions)
{
_globalFilters = mvcOptions.Value.Filters;
}
/// <inheritdoc />
public int Order => -1000;
/// <inheritdoc />
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.PageApplicationModel = CreateModel(context.ActionDescriptor, context.PageType);
}
/// <inheritdoc />
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
}
/// <summary>
/// Creates a <see cref="PageApplicationModel"/> for the given <paramref name="pageTypeInfo"/>.
/// </summary>
/// <param name="actionDescriptor">The <see cref="PageActionDescriptor"/>.</param>
/// <param name="pageTypeInfo">The <see cref="TypeInfo"/>.</param>
/// <returns>A <see cref="PageApplicationModel"/> for the given <see cref="TypeInfo"/>.</returns>
protected virtual PageApplicationModel CreateModel(
PageActionDescriptor actionDescriptor,
TypeInfo pageTypeInfo)
{
if (actionDescriptor == null)
{
throw new ArgumentNullException(nameof(actionDescriptor));
}
if (pageTypeInfo == null)
{
throw new ArgumentNullException(nameof(pageTypeInfo));
}
// Pages always have a model type. If it's not set explicitly by the developer using
// @model, it will be the same as the page type.
var modelTypeInfo = pageTypeInfo.GetProperty(ModelPropertyName)?.PropertyType?.GetTypeInfo();
// Now we want to find the handler methods. If the model defines any handlers, then we'll use those,
// otherwise look at the page itself (unless the page IS the model, in which case we already looked).
TypeInfo handlerType;
var handlerModels = modelTypeInfo == null ? null : CreateHandlerModels(modelTypeInfo);
if (handlerModels?.Count > 0)
{
handlerType = modelTypeInfo;
}
else
{
handlerType = pageTypeInfo.GetTypeInfo();
handlerModels = CreateHandlerModels(pageTypeInfo);
}
var handlerTypeAttributes = handlerType.GetCustomAttributes(inherit: true);
var pageModel = new PageApplicationModel(
actionDescriptor,
handlerType,
handlerTypeAttributes)
{
PageType = pageTypeInfo,
ModelType = modelTypeInfo,
};
for (var i = 0; i < handlerModels.Count; i++)
{
var handlerModel = handlerModels[i];
handlerModel.Page = pageModel;
pageModel.HandlerMethods.Add(handlerModel);
}
PopulateHandlerProperties(pageModel);
for (var i = 0; i < _globalFilters.Count; i++)
{
pageModel.Filters.Add(_globalFilters[i]);
}
for (var i = 0; i < handlerTypeAttributes.Length; i++)
{
if (handlerTypeAttributes[i] is IFilterMetadata filter)
{
pageModel.Filters.Add(filter);
}
}
return pageModel;
}
// Internal for unit testing
internal void PopulateHandlerProperties(PageApplicationModel pageModel)
{
var properties = PropertyHelper.GetVisibleProperties(pageModel.HandlerType.AsType());
for (var i = 0; i < properties.Length; i++)
{
var propertyModel = CreatePropertyModel(properties[i].Property);
if (propertyModel != null)
{
propertyModel.Page = pageModel;
pageModel.HandlerProperties.Add(propertyModel);
}
}
}
// Internal for unit testing
internal IList<PageHandlerModel> CreateHandlerModels(TypeInfo handlerTypeInfo)
{
var methods = handlerTypeInfo.GetMethods();
var results = new List<PageHandlerModel>();
for (var i = 0; i < methods.Length; i++)
{
var handler = CreateHandlerModel(methods[i]);
if (handler != null)
{
results.Add(handler);
}
}
return results;
}
/// <summary>
/// Creates a <see cref="PageHandlerModel"/> for the specified <paramref name="method"/>.s
/// </summary>
/// <param name="method">The <see cref="MethodInfo"/>.</param>
/// <returns>The <see cref="PageHandlerModel"/>.</returns>
protected virtual PageHandlerModel CreateHandlerModel(MethodInfo method)
{
if (method == null)
{
throw new ArgumentNullException(nameof(method));
}
if (!IsHandler(method))
{
return null;
}
if (method.IsDefined(typeof(NonHandlerAttribute)))
{
return null;
}
if (method.DeclaringType.GetTypeInfo().IsDefined(typeof(PagesBaseClassAttribute)))
{
return null;
}
if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handlerName))
{
return null;
}
var handlerModel = new PageHandlerModel(
method,
method.GetCustomAttributes(inherit: true))
{
Name = method.Name,
HandlerName = handlerName,
HttpMethod = httpMethod,
};
var methodParameters = handlerModel.MethodInfo.GetParameters();
for (var i = 0; i < methodParameters.Length; i++)
{
var parameter = methodParameters[i];
var parameterModel = CreateParameterModel(parameter);
parameterModel.Handler = handlerModel;
handlerModel.Parameters.Add(parameterModel);
}
return handlerModel;
}
/// <summary>
/// Creates a <see cref="PageParameterModel"/> for the specified <paramref name="parameter"/>.
/// </summary>
/// <param name="parameter">The <see cref="ParameterInfo"/>.</param>
/// <returns>The <see cref="PageParameterModel"/>.</returns>
protected virtual PageParameterModel CreateParameterModel(ParameterInfo parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
return new PageParameterModel(parameter, parameter.GetCustomAttributes(inherit: true))
{
BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes()),
ParameterName = parameter.Name,
};
}
/// <summary>
/// Creates a <see cref="PagePropertyModel"/> for the <paramref name="property"/>.
/// </summary>
/// <param name="property">The <see cref="PropertyInfo"/>.</param>
/// <returns>The <see cref="PagePropertyModel"/>.</returns>
protected virtual PagePropertyModel CreatePropertyModel(PropertyInfo property)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var attributes = property.GetCustomAttributes(inherit: true);
var bindingInfo = BindingInfo.GetBindingInfo(attributes);
var model = new PagePropertyModel(property, property.GetCustomAttributes(inherit: true))
{
PropertyName = property.Name,
BindingInfo = bindingInfo,
};
return model;
}
/// <summary>
/// Determines if the specified <paramref name="methodInfo"/> is a handler.
/// </summary>
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
/// <returns><c>true</c> if the <paramref name="methodInfo"/> is a handler. Otherwise <c>false</c>.</returns>
/// <remarks>
/// Override this method to provide custom logic to determine which methods are considered handlers.
/// </remarks>
protected virtual bool IsHandler(MethodInfo methodInfo)
{
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
// (such as property accessors and operator overloading methods).
if (methodInfo.IsSpecialName)
{
return false;
}
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
{
return false;
}
if (methodInfo.IsStatic)
{
return false;
}
if (methodInfo.IsAbstract)
{
return false;
}
if (methodInfo.IsConstructor)
{
return false;
}
if (methodInfo.IsGenericMethod)
{
return false;
}
return methodInfo.IsPublic;
}
internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
{
httpMethod = null;
handler = null;
// Handler method names always start with "On"
if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
{
return false;
}
// Now we parse the method name according to our conventions to determine the required HTTP method
// and optional 'handler name'.
//
// Valid names look like:
// - OnGet
// - OnPost
// - OnFooBar
// - OnTraceAsync
// - OnPostEditAsync
var start = "On".Length;
var length = methodName.Length;
if (methodName.EndsWith("Async", StringComparison.Ordinal))
{
length -= "Async".Length;
}
if (start == length)
{
// There are no additional characters. This is "On" or "OnAsync".
return false;
}
// The http method follows "On" and is required to be at least one character. We use casing
// to determine where it ends.
var handlerNameStart = start + 1;
for (; handlerNameStart < length; handlerNameStart++)
{
if (char.IsUpper(methodName[handlerNameStart]))
{
break;
}
}
httpMethod = methodName.Substring(start, handlerNameStart - start);
// The handler name follows the http method and is optional. It includes everything up to the end
// excluding the "Async" suffix (if present).
handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
return true;
}
}
}

View File

@ -3,267 +3,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageLoader : IPageLoader
{
private const string ModelPropertyName = "Model";
private readonly IPageApplicationModelProvider[] _applicationModelProviders;
private readonly IViewCompilerProvider _viewCompilerProvider;
private readonly RazorPagesOptions _options;
public DefaultPageLoader(IViewCompilerProvider viewCompilerProvider)
public DefaultPageLoader(
IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
IViewCompilerProvider viewCompilerProvider,
IOptions<RazorPagesOptions> pageOptions)
{
_applicationModelProviders = applicationModelProviders.ToArray();
_viewCompilerProvider = viewCompilerProvider;
_options = pageOptions.Value;
}
private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler();
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
throw new ArgumentNullException(nameof(actionDescriptor));
}
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
var viewDescriptor = compileTask.GetAwaiter().GetResult();
var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
return CreateDescriptor(actionDescriptor, pageAttribute);
}
// Internal for unit testing
internal static CompiledPageActionDescriptor CreateDescriptor(
PageActionDescriptor actionDescriptor,
RazorPageAttribute pageAttribute)
{
var pageType = pageAttribute.ViewType.GetTypeInfo();
// Pages always have a model type. If it's not set explicitly by the developer using
// @model, it will be the same as the page type.
var modelType = pageAttribute.ViewType.GetProperty(ModelPropertyName)?.PropertyType?.GetTypeInfo();
// Now we want to find the handler methods. If the model defines any handlers, then we'll use those,
// otherwise look at the page itself (unless the page IS the model, in which case we already looked).
TypeInfo handlerType;
var handlerMethods = modelType == null ? null : CreateHandlerMethods(modelType);
if (handlerMethods?.Length > 0)
var context = new PageApplicationModelProviderContext(actionDescriptor, pageAttribute.ViewType.GetTypeInfo());
for (var i = 0; i < _applicationModelProviders.Length; i++)
{
handlerType = modelType;
}
else
{
handlerType = pageType;
handlerMethods = CreateHandlerMethods(pageType);
_applicationModelProviders[i].OnProvidersExecuting(context);
}
var boundProperties = CreateBoundProperties(handlerType);
return new CompiledPageActionDescriptor(actionDescriptor)
for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
{
ActionConstraints = actionDescriptor.ActionConstraints,
AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
BoundProperties = boundProperties,
FilterDescriptors = actionDescriptor.FilterDescriptors,
HandlerMethods = handlerMethods,
HandlerTypeInfo = handlerType,
ModelTypeInfo = modelType,
RouteValues = actionDescriptor.RouteValues,
PageTypeInfo = pageType,
Properties = actionDescriptor.Properties,
};
}
internal static HandlerMethodDescriptor[] CreateHandlerMethods(TypeInfo type)
{
var methods = type.GetMethods();
var results = new List<HandlerMethodDescriptor>();
for (var i = 0; i < methods.Length; i++)
{
var method = methods[i];
if (!IsValidHandlerMethod(method))
{
continue;
}
if (method.IsDefined(typeof(NonHandlerAttribute)))
{
continue;
}
if (method.DeclaringType.GetTypeInfo().IsDefined(typeof(PagesBaseClassAttribute)))
{
continue;
}
if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handler))
{
continue;
}
var parameters = CreateHandlerParameters(method);
var handlerMethodDescriptor = new HandlerMethodDescriptor()
{
MethodInfo = method,
Name = handler,
HttpMethod = httpMethod,
Parameters = parameters,
};
results.Add(handlerMethodDescriptor);
_applicationModelProviders[i].OnProvidersExecuted(context);
}
return results.ToArray();
}
// Internal for testing
internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler)
{
httpMethod = null;
handler = null;
// Handler method names always start with "On"
if (!methodName.StartsWith("On") || methodName.Length <= "On".Length)
for (var i = 0; i < _options.ApplicationModelConventions.Count; i++)
{
return false;
_options.ApplicationModelConventions[i].Apply(context.PageApplicationModel);
}
// Now we parse the method name according to our conventions to determine the required HTTP method
// and optional 'handler name'.
//
// Valid names look like:
// - OnGet
// - OnPost
// - OnFooBar
// - OnTraceAsync
// - OnPostEditAsync
var start = "On".Length;
var length = methodName.Length;
if (methodName.EndsWith("Async", StringComparison.Ordinal))
{
length -= "Async".Length;
}
if (start == length)
{
// There are no additional characters. This is "On" or "OnAsync".
return false;
}
// The http method follows "On" and is required to be at least one character. We use casing
// to determine where it ends.
var handlerNameStart = start + 1;
for (; handlerNameStart < length; handlerNameStart++)
{
if (char.IsUpper(methodName[handlerNameStart]))
{
break;
}
}
httpMethod = methodName.Substring(start, handlerNameStart - start);
// The handler name follows the http method and is optional. It includes everything up to the end
// excluding the "Async" suffix (if present).
handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart);
return true;
}
private static bool IsValidHandlerMethod(MethodInfo methodInfo)
{
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
// (such as property accessors and operator overloading methods).
if (methodInfo.IsSpecialName)
{
return false;
}
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
{
return false;
}
if (methodInfo.IsStatic)
{
return false;
}
if (methodInfo.IsAbstract)
{
return false;
}
if (methodInfo.IsConstructor)
{
return false;
}
if (methodInfo.IsGenericMethod)
{
return false;
}
return methodInfo.IsPublic;
}
// Internal for testing
internal static HandlerParameterDescriptor[] CreateHandlerParameters(MethodInfo methodInfo)
{
var methodParameters = methodInfo.GetParameters();
var parameters = new HandlerParameterDescriptor[methodParameters.Length];
for (var i = 0; i < methodParameters.Length; i++)
{
var parameter = methodParameters[i];
parameters[i] = new HandlerParameterDescriptor()
{
BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes()),
Name = parameter.Name,
ParameterInfo = parameter,
ParameterType = parameter.ParameterType,
};
}
return parameters;
}
// Internal for testing
internal static PageBoundPropertyDescriptor[] CreateBoundProperties(TypeInfo type)
{
var properties = PropertyHelper.GetVisibleProperties(type.AsType());
var results = new List<PageBoundPropertyDescriptor>();
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
var bindingInfo = BindingInfo.GetBindingInfo(property.Property.GetCustomAttributes());
// If there's no binding info then that means there are no model binding attributes on the
// property. So we won't bind this property.
if (bindingInfo == null)
{
continue;
}
var descriptor = new PageBoundPropertyDescriptor()
{
BindingInfo = bindingInfo,
Name = property.Name,
Property = property.Property,
ParameterType = property.Property.PropertyType,
};
results.Add(descriptor);
}
return results.ToArray();
return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel);
}
}
}

View File

@ -94,11 +94,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var cache = CurrentCache;
PageActionInvokerCacheEntry cacheEntry;
IFilterMetadata[] filters;
if (!cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
{
actionContext.ActionDescriptor = _loader.Load(actionDescriptor);
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext);
filters = filterFactoryResult.Filters;
cacheEntry = CreateCacheEntry(context, filterFactoryResult.CacheableFilters);
@ -166,8 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ActionInvokerProviderContext context,
FilterItem[] cachedFilters)
{
var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor;
var compiledActionDescriptor = _loader.Load(actionDescriptor);
var compiledActionDescriptor = (CompiledPageActionDescriptor)context.ActionContext.ActionDescriptor;
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(compiledActionDescriptor.ModelTypeInfo);

View File

@ -1,41 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageFilterApplicationModelProvider : IPageApplicationModelProvider
{
/// <remarks>This order ensures that <see cref="PageFilterApplicationModelProvider"/> runs after
/// <see cref="RazorProjectPageApplicationModelProvider"/> and <see cref="CompiledPageApplicationModelProvider"/>.
/// </remarks>
public int Order => -1000 + 10;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
// Do nothing
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
for (var i = 0; i < context.Results.Count; i++)
{
var pageApplicationModel = context.Results[i];
// Support for [TempData] on properties
pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory());
// Always require an antiforgery token on post
pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}
}
}
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
private const string IndexFileName = "Index.cshtml";
public static void PopulateDefaults(PageApplicationModel model, string routeTemplate)
public static void PopulateDefaults(PageRouteModel model, string routeTemplate)
{
if (AttributeRouteModel.IsOverridePattern(routeTemplate))
{

View File

@ -9,29 +9,29 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RazorProjectPageApplicationModelProvider : IPageApplicationModelProvider
public class RazorProjectPageRouteModelProvider : IPageRouteModelProvider
{
private readonly RazorProject _project;
private readonly RazorPagesOptions _pagesOptions;
private readonly ILogger _logger;
public RazorProjectPageApplicationModelProvider(
public RazorProjectPageRouteModelProvider(
RazorProject razorProject,
IOptions<RazorPagesOptions> pagesOptionsAccessor,
ILoggerFactory loggerFactory)
{
_project = razorProject;
_pagesOptions = pagesOptionsAccessor.Value;
_logger = loggerFactory.CreateLogger<RazorProjectPageApplicationModelProvider>();
_logger = loggerFactory.CreateLogger<RazorProjectPageRouteModelProvider>();
}
public int Order => -1000;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
{
@ -47,12 +47,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
continue;
}
var pageApplicationModel = new PageApplicationModel(
var routeModel = new PageRouteModel(
relativePath: item.CombinedPath,
viewEnginePath: item.PathWithoutExtension);
PageSelectorModel.PopulateDefaults(pageApplicationModel, routeTemplate);
PageSelectorModel.PopulateDefaults(routeModel, routeTemplate);
context.Results.Add(pageApplicationModel);
context.RouteModels.Add(routeModel);
}
}
}

View File

@ -0,0 +1,31 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class TempDataFilterPageApplicationModelProvider : IPageApplicationModelProvider
{
// The order is set to execute after the DefaultPageApplicationModelProvider.
public int Order => -1000 + 10;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var pageApplicationModel = context.PageApplicationModel;
// Support for [TempData] on properties
pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory());
}
}
}

View File

@ -14,6 +14,7 @@
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor\Microsoft.AspNetCore.Mvc.Razor.csproj" />
<PackageReference Include="Microsoft.Extensions.ClosedGenericMatcher.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.CopyOnWriteDictionary.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.PropertyActivator.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
</ItemGroup>

View File

@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
@ -15,10 +17,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
private string _root = "/Pages";
/// <summary>
/// Gets a list of <see cref="IPageApplicationModelConvention"/> instances that will be applied to
/// Gets a list of <see cref="IPageRouteModelConvention"/> instances that will be applied to
/// the <see cref="PageModel"/> when discovering Razor Pages.
/// </summary>
public IList<IPageApplicationModelConvention> Conventions { get; } = new List<IPageApplicationModelConvention>();
public IList<IPageRouteModelConvention> RouteModelConventions { get; } = new List<IPageRouteModelConvention>();
/// <summary>
/// Gets a list of <see cref="IPageRouteModelConvention"/> instances that will be applied to
/// the <see cref="PageModel"/> when discovering Razor Pages.
/// </summary>
public IList<IPageApplicationModelConvention> ApplicationModelConventions { get; } = new List<IPageApplicationModelConvention>();
/// <summary>
/// Application relative path used as the root of discovery for Razor Page files.

View File

@ -1050,6 +1050,30 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.StartsWith(expected, response.Trim());
}
[Fact]
public async Task AuthFiltersAppliedToPageModel_AreExecuted()
{
// Act
var response = await Client.GetAsync("/ModelWithAuthFilter");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/Login?ReturnUrl=%2FModelWithAuthFilter", response.Headers.Location.PathAndQuery);
}
[Fact]
public async Task PageFiltersAppliedToPageModel_AreExecuted()
{
// Arrange
var expected = "Hello from OnGetEdit";
// Act
var response = await Client.GetStringAsync("/ModelWithPageFilter");
// Assert
Assert.Equal(expected, response.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -115,6 +115,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuthFolder", response.Headers.Location.PathAndQuery);
}
[Fact]
public async Task AuthConvention_AppliedToFolders_CanByOverridenByFiltersOnModel()
{
// Act
var response = await Client.GetStringAsync("/Conventions/AuthFolder/AnonymousViaModel");
// Assert
Assert.Equal("Hello from Anonymous", response.Trim());
}
[Fact]
public async Task ViewStart_IsDiscoveredWhenRootDirectoryIsSpecified()
{

View File

@ -18,21 +18,23 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
var services = new ServiceCollection().AddOptions();
var expected = Mock.Of<IPageApplicationModelConvention>();
var applicationModelConvention = Mock.Of<IPageApplicationModelConvention>();
var routeModelConvention = Mock.Of<IPageRouteModelConvention>();
var builder = new MvcBuilder(services, new ApplicationPartManager());
builder.AddRazorPagesOptions(options =>
{
options.Conventions.Add(expected);
options.ApplicationModelConventions.Add(applicationModelConvention);
options.RouteModelConventions.Add(routeModelConvention);
});
var serviceProvider = services.BuildServiceProvider();
var accessor = serviceProvider.GetRequiredService<IOptions<RazorPagesOptions>>();
// Act
var conventions = accessor.Value.Conventions;
// Act & Assert
var conventions = accessor.Value.ApplicationModelConventions;
// Assert
Assert.Collection(conventions,
convention => Assert.Same(expected, convention));
convention => Assert.Same(applicationModelConvention, convention));
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
@ -21,9 +22,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -44,9 +45,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -79,9 +80,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -117,9 +118,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -146,9 +147,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -177,9 +178,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -214,9 +215,9 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
};
// Act
@ -249,7 +250,7 @@ namespace Microsoft.Extensions.DependencyInjection
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml")
new PageRouteModel("/Pages/Index.cshtml", "/Index.cshtml")
{
Selectors =
{
@ -257,7 +258,7 @@ namespace Microsoft.Extensions.DependencyInjection
CreateSelectorModel(""),
}
},
new PageApplicationModel("/Pages/About.cshtml", "/About.cshtml")
new PageRouteModel("/Pages/About.cshtml", "/About.cshtml")
{
Selectors =
{
@ -316,9 +317,9 @@ namespace Microsoft.Extensions.DependencyInjection
};
}
private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
private static void ApplyConventions(RazorPagesOptions options, PageRouteModel[] models)
{
foreach (var convention in options.Conventions)
foreach (var convention in options.RouteModelConventions)
{
foreach (var model in models)
{
@ -326,5 +327,26 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
}
private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
{
foreach (var convention in options.ApplicationModelConventions)
{
foreach (var model in models)
{
convention.Apply(model);
}
}
}
private PageApplicationModel CreateApplicationModel(string relativePath, string viewEnginePath)
{
var descriptor = new PageActionDescriptor
{
ViewEnginePath = viewEnginePath,
RelativePath = relativePath,
};
return new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
}
}
}

View File

@ -4,11 +4,8 @@
using System;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.Options;
using Moq;
@ -22,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_DoesNotAddDescriptorsIfNoApplicationModelsAreDiscovered()
{
// Arrange
var applicationModelProvider = new TestPageApplicationModelProvider();
var applicationModelProvider = new TestPageRouteModelProvider();
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
GetAccessor<MvcOptions>(),
@ -40,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsDescriptorsForModelWithSelector()
{
// Arrange
var model = new PageApplicationModel("/Test.cshtml", "/Test")
var model = new PageRouteModel("/Test.cshtml", "/Test")
{
Selectors =
{
@ -53,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}
}
};
var applicationModelProvider = new TestPageApplicationModelProvider(model);
var applicationModelProvider = new TestPageRouteModelProvider(model);
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
GetAccessor<MvcOptions>(),
@ -75,15 +72,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsActionDescriptorForEachSelector()
{
// Arrange
var applicationModelProvider = new TestPageApplicationModelProvider(
new PageApplicationModel("/base-path/Test.cshtml", "/base-path/Test")
var applicationModelProvider = new TestPageRouteModelProvider(
new PageRouteModel("/base-path/Test.cshtml", "/base-path/Test")
{
Selectors =
{
CreateSelectorModel("base-path/Test/Home")
}
},
new PageApplicationModel("/base-path/Index.cshtml", "/base-path/Index")
new PageRouteModel("/base-path/Index.cshtml", "/base-path/Index")
{
Selectors =
{
@ -91,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
CreateSelectorModel("base-path/"),
}
},
new PageApplicationModel("/base-path/Admin/Index.cshtml", "/base-path/Admin/Index")
new PageRouteModel("/base-path/Admin/Index.cshtml", "/base-path/Admin/Index")
{
Selectors =
{
@ -99,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
CreateSelectorModel("base-path/Admin"),
}
},
new PageApplicationModel("/base-path/Admin/User.cshtml", "/base-path/Admin/User")
new PageRouteModel("/base-path/Admin/User.cshtml", "/base-path/Admin/User")
{
Selectors =
{
@ -143,8 +140,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public void GetDescriptors_AddsMultipleDescriptorsForPageWithMultipleSelectors()
{
// Arrange
var applicationModelProvider = new TestPageApplicationModelProvider(
new PageApplicationModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
var applicationModelProvider = new TestPageRouteModelProvider(
new PageRouteModel("/Catalog/Details/Index.cshtml", "/Catalog/Details/Index")
{
Selectors =
{
@ -180,142 +177,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
});
}
[Fact]
public void GetDescriptors_ImplicitFilters()
private static PageRouteModel CreateModel()
{
// Arrange
var options = new MvcOptions();
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
GetRazorPagesOptions());
var context = new ActionDescriptorProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
var result = Assert.Single(context.Results);
var descriptor = Assert.IsType<PageActionDescriptor>(result);
Assert.Collection(
descriptor.FilterDescriptors,
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filterDescriptor.Filter);
});
}
[Fact]
public void GetDescriptors_AddsGlobalFilters()
{
// Arrange
var filter1 = Mock.Of<IFilterMetadata>();
var filter2 = Mock.Of<IFilterMetadata>();
var options = new MvcOptions();
options.Filters.Add(filter1);
options.Filters.Add(filter2);
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
GetRazorPagesOptions());
var context = new ActionDescriptorProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
var result = Assert.Single(context.Results);
var descriptor = Assert.IsType<PageActionDescriptor>(result);
Assert.Collection(
descriptor.FilterDescriptors,
filterDescriptor =>
{
Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
Assert.Same(filter1, filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
Assert.Same(filter2, filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filterDescriptor.Filter);
});
}
[Fact]
public void GetDescriptors_AddsFiltersAddedByConvention()
{
// Arrange
var globalFilter = Mock.Of<IFilterMetadata>();
var localFilter = Mock.Of<IFilterMetadata>();
var options = new MvcOptions();
options.Filters.Add(globalFilter);
var convention = new Mock<IPageApplicationModelConvention>();
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel model) =>
{
model.Filters.Add(localFilter);
});
var razorOptions = GetRazorPagesOptions();
razorOptions.Value.Conventions.Add(convention.Object);
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
razorOptions);
var context = new ActionDescriptorProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
var result = Assert.Single(context.Results);
var descriptor = Assert.IsType<PageActionDescriptor>(result);
Assert.Collection(descriptor.FilterDescriptors,
filterDescriptor =>
{
Assert.Equal(FilterScope.Global, filterDescriptor.Scope);
Assert.Same(globalFilter, filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filterDescriptor.Filter);
},
filterDescriptor =>
{
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
Assert.Same(localFilter, filterDescriptor.Filter);
});
}
private static PageApplicationModel CreateModel()
{
return new PageApplicationModel("/Home.cshtml", "/Home")
return new PageRouteModel("/Home.cshtml", "/Home")
{
Selectors =
{
@ -353,28 +217,27 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return new FileProviderRazorProjectItem(testFileInfo, basePath, path);
}
private class TestPageApplicationModelProvider : IPageApplicationModelProvider
private class TestPageRouteModelProvider : IPageRouteModelProvider
{
private readonly PageApplicationModel[] _models;
private readonly PageRouteModel[] _models;
public TestPageApplicationModelProvider(params PageApplicationModel[] models)
public TestPageRouteModelProvider(params PageRouteModel[] models)
{
_models = models ?? Array.Empty<PageApplicationModel>();
_models = models ?? Array.Empty<PageRouteModel>();
}
public int Order => -1000;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
foreach (var model in _models)
{
context.Results.Add(model);
context.RouteModels.Add(model);
}
}
}
}

View File

@ -0,0 +1,150 @@
// 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 System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class AuthorizationPageApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_IgnoresAttributesOnHandlerMethods()
{
// Arrange
var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager<AuthorizationOptions>());
var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
var typeInfo = typeof(PageWiithAuthorizeHandlers).GetTypeInfo();
var context = GetApplicationProviderContext(typeInfo);
// Act
autorizationProvider.OnProvidersExecuting(context);
// Assert
Assert.Empty(context.PageApplicationModel.Filters);
}
private class PageWiithAuthorizeHandlers
{
public ModelWuthAuthorizeHandlers Model => null;
}
public class ModelWuthAuthorizeHandlers
{
[Authorize]
public void OnGet()
{
}
}
[Fact]
public void OnProvidersExecuting_AddsAuthorizeFilter_IfModelHasAuthorizationAttributes()
{
// Arrange
var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager<AuthorizationOptions>());
var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
var context = GetApplicationProviderContext(typeof(TestPage).GetTypeInfo());
// Act
autorizationProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<AuthorizeFilter>(f));
}
private class TestPage
{
public TestModel Model => null;
}
[Authorize]
private class TestModel
{
public virtual void OnGet()
{
}
}
[Fact]
public void OnProvidersExecuting_CollatesAttributesFromInheritedTypes()
{
// Arrange
var options = new TestOptionsManager<AuthorizationOptions>();
options.Value.AddPolicy("Base", policy => policy.RequireClaim("Basic").RequireClaim("Basic2"));
options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived"));
var policyProvider = new DefaultAuthorizationPolicyProvider(options);
var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
var context = GetApplicationProviderContext(typeof(TestPageWithDerivedModel).GetTypeInfo());
// Act
autorizationProvider.OnProvidersExecuting(context);
// Assert
var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(context.PageApplicationModel.Filters));
// Basic + Basic2 + Derived authorize
Assert.Equal(3, authorizeFilter.Policy.Requirements.Count);
}
private class TestPageWithDerivedModel
{
public DeriviedModel Model => null;
}
[Authorize(Policy = "Base")]
public class BaseModel
{
}
[Authorize(Policy = "Derived")]
private class DeriviedModel : BaseModel
{
public virtual void OnGet()
{
}
}
[Fact]
public void OnProvidersExecuting_AddsAllowAnonymousFilter()
{
// Arrange
var policyProvider = new DefaultAuthorizationPolicyProvider(new TestOptionsManager<AuthorizationOptions>());
var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
var context = GetApplicationProviderContext(typeof(PageWithAnonymousModel).GetTypeInfo());
// Act
autorizationProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<AllowAnonymousFilter>(f));
}
private class PageWithAnonymousModel
{
public AnonymousModel Model => null;
}
[AllowAnonymous]
public class AnonymousModel
{
public void OnGet() { }
}
private static PageApplicationModelProviderContext GetApplicationProviderContext(TypeInfo typeInfo)
{
var defaultProvider = new DefaultPageApplicationModelProvider(new TestOptionsManager<MvcOptions>());
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
defaultProvider.OnProvidersExecuting(context);
return context;
}
}
}

View File

@ -0,0 +1,36 @@
// 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.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class AutoValidateAntiforgeryPageApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsFiltersToModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var applicationModel = new PageApplicationModel(
actionDescriptor,
typeof(object).GetTypeInfo(),
new object[0]);
var applicationModelProvider = new AutoValidateAntiforgeryPageApplicationModelProvider();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo())
{
PageApplicationModel = applicationModel,
};
// Act
applicationModelProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(
applicationModel.Filters,
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
}
}
}

View File

@ -0,0 +1,311 @@
// 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 System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class CompiledPageActionDescriptorBuilderTest
{
[Fact]
public void CreateDescriptor_CopiesPropertiesFromPageActionDescriptor()
{
// Arrange
var actionDescriptor = new PageActionDescriptor
{
ActionConstraints = new List<IActionConstraintMetadata>(),
AttributeRouteInfo = new AttributeRouteInfo(),
FilterDescriptors = new List<FilterDescriptor>(),
RelativePath = "/Foo",
RouteValues = new Dictionary<string, string>(),
ViewEnginePath = "/Pages/Foo",
};
var handlerTypeInfo = typeof(object).GetTypeInfo();
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0]);
// Act
var actual = CompiledPageActionDescriptorBuilder.Build(pageApplicationModel);
// Assert
Assert.Same(actionDescriptor.ActionConstraints, actual.ActionConstraints);
Assert.Same(actionDescriptor.AttributeRouteInfo, actual.AttributeRouteInfo);
Assert.Same(actionDescriptor.RelativePath, actual.RelativePath);
Assert.Same(actionDescriptor.RouteValues, actual.RouteValues);
Assert.Same(actionDescriptor.ViewEnginePath, actual.ViewEnginePath);
}
[Fact]
public void CreateDescriptor_CopiesPropertiesFromPageApplicationModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor
{
ActionConstraints = new List<IActionConstraintMetadata>(),
AttributeRouteInfo = new AttributeRouteInfo(),
FilterDescriptors = new List<FilterDescriptor>(),
RelativePath = "/Foo",
RouteValues = new Dictionary<string, string>(),
ViewEnginePath = "/Pages/Foo",
};
var handlerTypeInfo = typeof(TestModel).GetTypeInfo();
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
{
PageType = typeof(TestPage).GetTypeInfo(),
ModelType = typeof(TestModel).GetTypeInfo(),
Filters =
{
Mock.Of<IFilterMetadata>(),
Mock.Of<IFilterMetadata>(),
},
HandlerMethods =
{
new PageHandlerModel(handlerTypeInfo.GetMethod(nameof(TestModel.OnGet)), new object[0]),
},
HandlerProperties =
{
new PagePropertyModel(handlerTypeInfo.GetProperty(nameof(TestModel.Property)), new object[0])
{
BindingInfo = new BindingInfo(),
},
}
};
// Act
var actual = CompiledPageActionDescriptorBuilder.Build(pageApplicationModel);
// Assert
Assert.Same(pageApplicationModel.PageType, actual.PageTypeInfo);
Assert.Same(pageApplicationModel.ModelType, actual.ModelTypeInfo);
Assert.Same(pageApplicationModel.HandlerType, actual.HandlerTypeInfo);
Assert.Same(pageApplicationModel.Properties, actual.Properties);
Assert.Equal(pageApplicationModel.Filters, actual.FilterDescriptors.Select(f => f.Filter));
Assert.Equal(pageApplicationModel.HandlerMethods.Select(p => p.MethodInfo), actual.HandlerMethods.Select(p => p.MethodInfo));
Assert.Equal(pageApplicationModel.HandlerProperties.Select(p => p.PropertyName), actual.BoundProperties.Select(p => p.Name));
}
private class TestPage
{
public TestModel Model { get; } = new TestModel();
[BindProperty]
public string Property { get; set; }
public void OnGet()
{
}
}
private class TestModel
{
[BindProperty]
public string Property { get; set; }
public void OnGet()
{
}
}
[Fact]
public void CreateHandlerMethods_CopiesPropertiesFromHandlerModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var handlerTypeInfo = typeof(ModelWithHandler).GetTypeInfo();
var handlerModel = new PageHandlerModel(handlerTypeInfo.GetMethod(nameof(ModelWithHandler.OnGetCustomerAsync)), new object[0])
{
HttpMethod = "GET",
HandlerName = "Customer",
};
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
{
HandlerMethods =
{
handlerModel,
}
};
// Act
var handlerDescriptors = CompiledPageActionDescriptorBuilder.CreateHandlerMethods(pageApplicationModel);
// Assert
Assert.Collection(
handlerDescriptors,
d =>
{
Assert.Equal(handlerModel.MethodInfo, d.MethodInfo);
Assert.Equal(handlerModel.HttpMethod, d.HttpMethod);
Assert.Equal(handlerModel.HandlerName, d.Name);
});
}
private class ModelWithHandler
{
public void OnGetCustomerAsync()
{
}
}
[Fact]
public void CreateHandlerMethods_CopiesParameterDecriptorsFromParameterModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var handlerTypeInfo = typeof(HandlerWithParameters).GetTypeInfo();
var handlerMethod = handlerTypeInfo.GetMethod(nameof(HandlerWithParameters.OnPost));
var parameters = handlerMethod.GetParameters();
var parameterModel1 = new PageParameterModel(parameters[0], new object[0])
{
ParameterName = "test-name"
};
var parameterModel2 = new PageParameterModel(parameters[1], new object[0])
{
BindingInfo = new BindingInfo(),
};
var handlerModel = new PageHandlerModel(handlerMethod, new object[0])
{
Parameters =
{
parameterModel1,
parameterModel2,
}
};
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
{
HandlerMethods =
{
handlerModel,
}
};
// Act
var handlerDescriptors = CompiledPageActionDescriptorBuilder.CreateHandlerMethods(pageApplicationModel);
// Assert
Assert.Collection(
Assert.Single(handlerDescriptors).Parameters,
p =>
{
Assert.Equal(parameters[0], p.ParameterInfo);
Assert.Equal(typeof(string), p.ParameterType);
Assert.Equal(parameterModel1.ParameterName, p.Name);
},
p =>
{
Assert.Equal(parameters[1], p.ParameterInfo);
Assert.Equal(typeof(int), p.ParameterType);
Assert.Same(parameterModel2.BindingInfo, p.BindingInfo);
});
}
private class HandlerWithParameters
{
public void OnPost(string param1, [FromRoute(Name = "id")] int param2)
{
}
}
[Fact]
public void CreateBoundProperties_CopiesPropertyDescriptorsFromPagePropertyModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var handlerTypeInfo = typeof(HandlerWithProperty).GetTypeInfo();
var propertyModel = new PagePropertyModel(
handlerTypeInfo.GetProperty(nameof(HandlerWithProperty.Property)),
new object[0])
{
PropertyName = nameof(HandlerWithProperty.Property),
BindingInfo = new BindingInfo(),
};
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
{
HandlerProperties =
{
propertyModel,
}
};
// Act
var propertyDescriptors = CompiledPageActionDescriptorBuilder.CreateBoundProperties(pageApplicationModel);
// Assert
Assert.Collection(
propertyDescriptors,
p =>
{
Assert.Same(propertyModel.PropertyName, p.Name);
Assert.Same(typeof(int), p.ParameterType);
Assert.Same(propertyModel.PropertyInfo, p.Property);
Assert.Same(propertyModel.BindingInfo, p.BindingInfo);
});
}
private class HandlerWithProperty
{
[BindProperty]
public int Property { get; set; }
}
[Fact]
public void CreateBoundProperties_IgnoresPropertiesWithoutBindingInfo()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var handlerTypeInfo = typeof(HandlerWithIgnoredProperties).GetTypeInfo();
var propertyModel1 = new PagePropertyModel(
handlerTypeInfo.GetProperty(nameof(HandlerWithIgnoredProperties.Property)),
new object[0])
{
PropertyName = nameof(HandlerWithIgnoredProperties.Property),
BindingInfo = new BindingInfo(),
};
var propertyModel2 = new PagePropertyModel(
handlerTypeInfo.GetProperty(nameof(HandlerWithIgnoredProperties.IgnoreMe)),
new object[0])
{
PropertyName = nameof(HandlerWithIgnoredProperties.IgnoreMe),
};
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new object[0])
{
HandlerProperties =
{
propertyModel1,
propertyModel2,
}
};
// Act
var propertyDescriptors = CompiledPageActionDescriptorBuilder.CreateBoundProperties(pageApplicationModel);
// Assert
Assert.Collection(
propertyDescriptors,
p =>
{
Assert.Same(propertyModel1.PropertyName, p.Name);
Assert.Same(typeof(int), p.ParameterType);
Assert.Same(propertyModel1.PropertyInfo, p.Property);
Assert.Same(propertyModel1.BindingInfo, p.BindingInfo);
});
}
private class HandlerWithIgnoredProperties
{
[BindProperty]
public int Property { get; set; }
public string IgnoreMe { get; set; }
}
}
}

View File

@ -11,7 +11,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class CompiledPageApplicationModelProviderTest
public class CompiledPageRouteModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsModelsForCompiledViews()
@ -22,14 +22,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/About.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "some-prefix"),
};
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext();
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
@ -55,14 +55,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
};
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
var context = new PageApplicationModelProviderContext();
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
@ -90,14 +90,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
};
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext();
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
@ -125,8 +125,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
GetDescriptor("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "/some-prefix"),
};
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext();
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
var context = new PageRouteModelProviderContext();
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
@ -143,11 +143,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
};
}
public class TestCompiledPageApplicationModelProvider : CompiledPageApplicationModelProvider
public class TestCompiledPageRouteModelProvider : CompiledPageRouteModelProvider
{
private readonly IEnumerable<CompiledViewDescriptor> _descriptors;
public TestCompiledPageApplicationModelProvider(IEnumerable<CompiledViewDescriptor> descriptors, RazorPagesOptions options)
public TestCompiledPageRouteModelProvider(IEnumerable<CompiledViewDescriptor> descriptors, RazorPagesOptions options)
: base(new ApplicationPartManager(), new TestOptionsManager<RazorPagesOptions>(options))
{
_descriptors = descriptors;

View File

@ -0,0 +1,788 @@
// 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.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public partial class DefaultPageApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_SetsPageAsHandlerType_IfModelPropertyDoesNotExist()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(TestPage).GetTypeInfo();
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Same(context.PageApplicationModel.PageType, context.PageApplicationModel.HandlerType);
}
[Fact]
public void OnProvidersExecuting_SetsPageAsHandlerType_IfModelTypeDoesNotHaveAnyHandlers()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo();
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Same(context.PageApplicationModel.PageType, context.PageApplicationModel.HandlerType);
}
[Fact]
public void OnProvidersExecuting_SetsModelAsHandlerType()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithModel).GetTypeInfo();
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Same(typeof(TestPageModel).GetTypeInfo(), context.PageApplicationModel.HandlerType);
}
[Fact]
public void OnProvidersExecuting_DiscoversPropertiesFromPage()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(TestPage).GetTypeInfo();
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Collection(
context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName),
property =>
{
Assert.Equal(typeInfo.GetProperty(nameof(TestPage.Property1)), property.PropertyInfo);
Assert.Null(property.BindingInfo);
Assert.Equal(nameof(TestPage.Property1), property.PropertyName);
},
property =>
{
Assert.Equal(typeInfo.GetProperty(nameof(TestPage.Property2)), property.PropertyInfo);
Assert.Equal(nameof(TestPage.Property2), property.PropertyName);
Assert.NotNull(property.BindingInfo);
Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource);
});
}
[Fact]
public void OnProvidersExecuting_DiscoversHandlersFromPage()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo();
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Collection(
context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name),
handler =>
{
var name = nameof(PageWithModelWithoutHandlers.OnGet);
Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
Assert.Equal(name, handler.Name);
Assert.Equal("Get", handler.HttpMethod);
Assert.Null(handler.HandlerName);
},
handler =>
{
var name = nameof(PageWithModelWithoutHandlers.OnPostAsync);
Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
Assert.Equal(name, handler.Name);
Assert.Equal("Post", handler.HttpMethod);
Assert.Null(handler.HandlerName);
},
handler =>
{
var name = nameof(PageWithModelWithoutHandlers.OnPostDeleteCustomerAsync);
Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo);
Assert.Equal(name, handler.Name);
Assert.Equal("Post", handler.HttpMethod);
Assert.Equal("DeleteCustomer", handler.HandlerName);
});
}
[Fact]
public void OnProvidersExecuting_DiscoversPropertiesFromModel()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithModel).GetTypeInfo();
var modelType = typeof(TestPageModel);
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Collection(
context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName),
property =>
{
var name = nameof(TestPageModel.Property1);
Assert.Equal(modelType.GetProperty(name), property.PropertyInfo);
Assert.Null(property.BindingInfo);
Assert.Equal(name, property.PropertyName);
},
property =>
{
var name = nameof(TestPageModel.Property2);
Assert.Equal(modelType.GetProperty(name), property.PropertyInfo);
Assert.Equal(name, property.PropertyName);
Assert.NotNull(property.BindingInfo);
Assert.Equal(BindingSource.Query, property.BindingInfo.BindingSource);
});
}
[Fact]
public void OnProvidersExecuting_DiscoversHandlersFromModel()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithModel).GetTypeInfo();
var modelType = typeof(TestPageModel);
var descriptor = new PageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.PageApplicationModel);
Assert.Collection(
context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name),
handler =>
{
var name = nameof(TestPageModel.OnGetUser);
Assert.Equal(modelType.GetMethod(name), handler.MethodInfo);
Assert.Equal(name, handler.Name);
Assert.Equal("Get", handler.HttpMethod);
Assert.Equal("User", handler.HandlerName);
});
}
// We want to test the the 'empty' page has no bound properties, and no handler methods.
[Fact]
public void OnProvidersExecuting_EmptyPage()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(EmptyPage).GetTypeInfo();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
var pageModel = context.PageApplicationModel;
Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null));
Assert.Empty(pageModel.HandlerMethods);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.HandlerType);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.ModelType);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.PageType);
}
// We want to test the the 'empty' page and pagemodel has no bound properties, and no handler methods.
[Fact]
public void OnProvidersExecuting_EmptyPageModel()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(EmptyPageWithPageModel).GetTypeInfo();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
var pageModel = context.PageApplicationModel;
Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null));
Assert.Empty(pageModel.HandlerMethods);
Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), pageModel.HandlerType);
Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), pageModel.ModelType);
Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), pageModel.PageType);
}
private class EmptyPage : Page
{
// Copied from generated code
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<EmptyPage> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<EmptyPage> ViewData => null;
public EmptyPage Model => ViewData.Model;
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class EmptyPageWithPageModel : Page
{
// Copied from generated code
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<EmptyPageModel> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<EmptyPageModel> ViewData => null;
public EmptyPageModel Model => ViewData.Model;
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class EmptyPageModel : PageModel
{
}
[Fact] // If the model has handler methods, we prefer those.
public void CreateDescriptor_FindsHandlerMethod_OnModel()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo();
var modelType = typeof(ModelWithHandler);
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
var pageModel = context.PageApplicationModel;
Assert.Collection(
pageModel.HandlerProperties,
p => Assert.Equal(modelType.GetProperty(nameof(ModelWithHandler.BindMe)), p.PropertyInfo));
Assert.Collection(
pageModel.HandlerMethods,
p => Assert.Equal(modelType.GetMethod(nameof(ModelWithHandler.OnGet)), p.MethodInfo));
Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.HandlerType);
Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.ModelType);
Assert.Same(typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(), pageModel.PageType);
}
private class ModelWithHandler
{
[ModelBinder]
public int BindMe { get; set; }
public void OnGet() { }
}
private class PageWithHandlerThatGetsIgnored
{
public ModelWithHandler Model => null;
[ModelBinder]
public int IgnoreMe { get; set; }
public void OnPost() { }
}
[Fact] // If the model has no handler methods, we look at the page instead.
public void OnProvidersExecuting_FindsHandlerMethodOnPage_WhenModelHasNoHandlers()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithHandler).GetTypeInfo();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
// Act
provider.OnProvidersExecuting(context);
// Assert
var pageModel = context.PageApplicationModel;
Assert.Collection(
pageModel.HandlerProperties.OrderBy(p => p.PropertyName),
p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.BindMe)), p.PropertyInfo),
p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.Model)), p.PropertyInfo));
Assert.Collection(
pageModel.HandlerMethods,
p => Assert.Equal(typeInfo.GetMethod(nameof(PageWithHandler.OnGet)), p.MethodInfo));
Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.HandlerType);
Assert.Same(typeof(PocoModel).GetTypeInfo(), pageModel.ModelType);
Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.PageType);
}
private class PageWithHandler
{
public PocoModel Model => null;
[ModelBinder]
public int BindMe { get; set; }
public void OnGet() { }
}
[Fact]
public void CreateHandlerModels_DiscoversHandlersFromBaseType()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(InheritsMethods).GetTypeInfo();
var baseType = typeof(TestSetPageModel);
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Collection(
handlerModels.OrderBy(h => h.MethodInfo.DeclaringType.Name).ThenBy(h => h.MethodInfo.Name),
handler =>
{
Assert.Equal(nameof(InheritsMethods.OnGet), handler.MethodInfo.Name);
Assert.Equal(typeInfo, handler.MethodInfo.DeclaringType.GetTypeInfo());
},
handler =>
{
Assert.Equal(nameof(TestSetPageModel.OnGet), handler.MethodInfo.Name);
Assert.Equal(baseType, handler.MethodInfo.DeclaringType);
},
handler =>
{
Assert.Equal(nameof(TestSetPageModel.OnPost), handler.MethodInfo.Name);
Assert.Equal(baseType, handler.MethodInfo.DeclaringType);
});
}
private class TestSetPageModel
{
public void OnGet()
{
}
public void OnPost()
{
}
}
private class InheritsMethods : TestSetPageModel
{
public new void OnGet()
{
}
}
[Fact]
public void CreateHandlerModels_IgnoresNonPublicMethods()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(ProtectedModel).GetTypeInfo();
var baseType = typeof(TestSetPageModel);
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Empty(handlerModels);
}
private class ProtectedModel
{
protected void OnGet()
{
}
private void OnPost()
{
}
}
[Fact]
public void CreateHandlerModels_IgnoreGenericTypeParameters()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(GenericClassModel).GetTypeInfo();
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Empty(handlerModels);
}
private class GenericClassModel
{
public void OnGet<T>()
{
}
}
[Fact]
public void CreateHandlerModels_IgnoresStaticMethods()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Collection(
handlerModels,
handler => Assert.Same(expected, handler.MethodInfo));
}
private class PageModelWithStaticHandler
{
public static void OnGet(string name)
{
}
public void OnGet()
{
}
}
[Fact]
public void CreateHandlerModels_IgnoresAbstractMethods()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Collection(
handlerModels,
handler => Assert.Same(expected, handler.MethodInfo));
}
private abstract class PageModelWithAbstractMethod
{
public abstract void OnPost(string name);
public void OnGet()
{
}
}
[Fact]
public void CreateHandlerModels_IgnoresMethodWithNonHandlerAttribute()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithNonHandlerMethod).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Collection(
handlerModels,
handler => Assert.Same(expected, handler.MethodInfo));
}
private class PageWithNonHandlerMethod
{
[NonHandler]
public void OnPost(string name) { }
public void OnGet()
{
}
}
// There are more tests for the parsing elsewhere, this is just testing that it's wired
// up to the model.
[Fact]
public void CreateHandlerModel_ParsesMethod()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageModelWithHandlerNames).GetTypeInfo();
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
Assert.Collection(
handlerModels.OrderBy(h => h.MethodInfo.Name),
handler =>
{
Assert.Same(typeInfo.GetMethod(nameof(PageModelWithHandlerNames.OnPutDeleteAsync)), handler.MethodInfo);
Assert.Equal("Put", handler.HttpMethod);
Assert.Equal("Delete", handler.HandlerName);
});
}
private class PageModelWithHandlerNames
{
public void OnPutDeleteAsync()
{
}
public void Foo() // This isn't a valid handler name.
{
}
}
[Fact]
public void CreateHandlerMethods_AddsParameterDescriptors()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost));
// Act
var handlerModels = provider.CreateHandlerModels(typeInfo);
// Assert
var handler = Assert.Single(handlerModels);
Assert.Collection(
handler.Parameters,
p =>
{
Assert.NotNull(p.ParameterInfo);
Assert.Equal(typeof(string), p.ParameterInfo.ParameterType);
Assert.Equal("name", p.ParameterName);
},
p =>
{
Assert.NotNull(p.ParameterInfo);
Assert.Equal(typeof(int), p.ParameterInfo.ParameterType);
Assert.Equal("id", p.ParameterName);
Assert.Equal("personId", p.BindingInfo.BinderModelName);
});
}
private class PageWithHandlerParameters
{
public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
}
// We're using PropertyHelper from Common to find the properties here, which implements
// out standard set of semantics for properties that the framework interacts with.
//
// One of the desirable consequences of that is we only find 'visible' properties. We're not
// retesting all of the details of PropertyHelper here, just the visibility part as a quick check
// that we're using PropertyHelper as expected.
[Fact]
public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(HidesAProperty).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
// Act
provider.PopulateHandlerProperties(pageModel);
// Assert
var properties = pageModel.HandlerProperties;
Assert.Collection(
properties,
p =>
{
Assert.Equal(typeof(HidesAProperty).GetTypeInfo(), p.PropertyInfo.DeclaringType.GetTypeInfo());
});
}
private class HasAHiddenProperty
{
[BindProperty]
public int Property { get; set; }
}
private class HidesAProperty : HasAHiddenProperty
{
[BindProperty]
public new int Property { get; set; }
}
[Fact]
public void PopulateHandlerProperties_SupportsGet_OnProperty()
{
// Arrange
var provider = new TestPageApplicationModelProvider();
var typeInfo = typeof(ModelSupportsGetOnProperty).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
// Act
provider.PopulateHandlerProperties(pageModel);
// Assert
var properties = pageModel.HandlerProperties;
Assert.Collection(
properties.OrderBy(p => p.PropertyName),
p =>
{
Assert.Equal(typeInfo.GetProperty(nameof(ModelSupportsGetOnProperty.Property)), p.PropertyInfo);
Assert.NotNull(p.BindingInfo.RequestPredicate);
Assert.True(p.BindingInfo.RequestPredicate(new ActionContext
{
HttpContext = new DefaultHttpContext
{
Request =
{
Method ="GET",
}
}
}));
});
}
private class ModelSupportsGetOnProperty
{
[BindProperty(SupportsGet = true)]
public int Property { get; set; }
}
[Theory]
[InlineData("Foo")]
[InlineData("On")]
[InlineData("OnAsync")]
[InlineData("Async")]
public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
{
// Act
var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
// Assert
Assert.False(result);
Assert.Null(httpMethod);
Assert.Null(handler);
}
[Theory]
[InlineData("OnG", "G", null)]
[InlineData("OnGAsync", "G", null)]
[InlineData("OnPOST", "P", "OST")]
[InlineData("OnPOSTAsync", "P", "OST")]
[InlineData("OnDeleteFoo", "Delete", "Foo")]
[InlineData("OnDeleteFooAsync", "Delete", "Foo")]
[InlineData("OnMadeupLongHandlerName", "Madeup", "LongHandlerName")]
[InlineData("OnMadeupLongHandlerNameAsync", "Madeup", "LongHandlerName")]
public void TryParseHandler_ParsesHandlerNames_ValidData(string methodName, string expectedHttpMethod, string expectedHandler)
{
// Arrange
// Act
var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
// Assert
Assert.True(result);
Assert.Equal(expectedHttpMethod, httpMethod);
Assert.Equal(expectedHandler, handler);
}
private class TestPageApplicationModelProvider : DefaultPageApplicationModelProvider
{
public TestPageApplicationModelProvider(IOptions<MvcOptions> mvcOptions = null)
: base(mvcOptions : new TestOptionsManager<MvcOptions>())
{
}
}
private class TestPage
{
public string Property1 { get; set; }
[FromRoute]
public object Property2 { get; set; }
}
private class PageWithModelWithoutHandlers : Page
{
public ModelWithoutHandler Model { get; }
public override Task ExecuteAsync() => throw new NotImplementedException();
public void OnGet() { }
public void OnPostAsync() { }
public void OnPostDeleteCustomerAsync() { }
public class ModelWithoutHandler
{
}
}
private class PageWithModel : Page
{
public TestPageModel Model { get; }
public override Task ExecuteAsync() => throw new NotImplementedException();
}
private class TestPageModel
{
public string Property1 { get; set; }
[FromQuery]
public string Property2 { get; set; }
public void OnGetUser() { }
}
}
}

View File

@ -1,16 +1,11 @@
// 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 System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@ -18,600 +13,126 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public class DefaultPageLoaderTest
{
[Fact]
public void CreateDescriptor_CopiesPropertiesFromBaseClass()
public void Load_InvokesApplicationModelProviders()
{
// Arrange
var expected = new PageActionDescriptor() // We only copy the properties that are meaningful for pages.
var descriptor = new PageActionDescriptor();
var compilerProvider = GetCompilerProvider();
var options = new TestOptionsManager<RazorPagesOptions>();
var provider1 = new Mock<IPageApplicationModelProvider>();
var provider2 = new Mock<IPageApplicationModelProvider>();
var sequence = 0;
var pageApplicationModel1 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
var pageApplicationModel2 = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
provider1.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal(0, sequence++);
Assert.Null(c.PageApplicationModel);
c.PageApplicationModel = pageApplicationModel1;
})
.Verifiable();
provider2.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal(1, sequence++);
Assert.Same(pageApplicationModel1, c.PageApplicationModel);
c.PageApplicationModel = pageApplicationModel2;
})
.Verifiable();
provider1.Setup(p => p.OnProvidersExecuted(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal(3, sequence++);
Assert.Same(pageApplicationModel2, c.PageApplicationModel);
})
.Verifiable();
provider2.Setup(p => p.OnProvidersExecuted(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal(2, sequence++);
Assert.Same(pageApplicationModel2, c.PageApplicationModel);
})
.Verifiable();
var providers = new[]
{
ActionConstraints = new List<IActionConstraintMetadata>(),
AttributeRouteInfo = new AttributeRouteInfo(),
FilterDescriptors = new List<FilterDescriptor>(),
RelativePath = "/Foo",
RouteValues = new Dictionary<string, string>(),
ViewEnginePath = "/Pages/Foo",
provider1.Object, provider2.Object
};
// Act
var actual = DefaultPageLoader.CreateDescriptor(expected,
new RazorPageAttribute(expected.RelativePath, typeof(EmptyPage), ""));
// Assert
Assert.Same(expected.ActionConstraints, actual.ActionConstraints);
Assert.Same(expected.AttributeRouteInfo, actual.AttributeRouteInfo);
Assert.Same(expected.FilterDescriptors, actual.FilterDescriptors);
Assert.Same(expected.Properties, actual.Properties);
Assert.Same(expected.RelativePath, actual.RelativePath);
Assert.Same(expected.RouteValues, actual.RouteValues);
Assert.Same(expected.ViewEnginePath, actual.ViewEnginePath);
}
// We want to test the the 'empty' page has no bound properties, and no handler methods.
[Fact]
public void CreateDescriptor_EmptyPage()
{
// Arrange
var type = typeof(EmptyPage);
var loader = new DefaultPageLoader(
providers,
compilerProvider,
options);
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
new RazorPageAttribute("/Pages/Index", type, ""));
var result = loader.Load(new PageActionDescriptor());
// Assert
Assert.Empty(result.BoundProperties);
Assert.Empty(result.HandlerMethods);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.HandlerTypeInfo);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.ModelTypeInfo);
Assert.Same(typeof(EmptyPage).GetTypeInfo(), result.PageTypeInfo);
}
// We want to test the the 'empty' page and pagemodel has no bound properties, and no handler methods.
[Fact]
public void CreateDescriptor_EmptyPageModel()
{
// Arrange
var type = typeof(EmptyPageWithPageModel);
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
new RazorPageAttribute("/Pages/Index", type, ""));
// Assert
Assert.Empty(result.BoundProperties);
Assert.Empty(result.HandlerMethods);
Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), result.HandlerTypeInfo);
Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), result.ModelTypeInfo);
Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), result.PageTypeInfo);
}
private class EmptyPage : Page
{
// Copied from generated code
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<EmptyPage> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<EmptyPage> ViewData => null;
public EmptyPage Model => ViewData.Model;
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class EmptyPageWithPageModel : Page
{
// Copied from generated code
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<EmptyPageModel> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<EmptyPageModel> ViewData => null;
public EmptyPageModel Model => ViewData.Model;
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class EmptyPageModel : PageModel
{
}
[Fact] // If the model has handler methods, we prefer those.
public void CreateDescriptor_FindsHandlerMethod_OnModel()
{
// Arrange
var type = typeof(PageWithHandlerThatGetsIgnored);
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
new RazorPageAttribute("/Pages/Index", type, ""));
// Assert
Assert.Collection(result.BoundProperties, p => Assert.Equal("BindMe", p.Name));
Assert.Collection(result.HandlerMethods, h => Assert.Equal("OnGet", h.MethodInfo.Name));
Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), result.HandlerTypeInfo);
Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), result.ModelTypeInfo);
Assert.Same(typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(), result.PageTypeInfo);
}
private class ModelWithHandler
{
[ModelBinder]
public int BindMe { get; set; }
public void OnGet() { }
}
private class PageWithHandlerThatGetsIgnored
{
public ModelWithHandler Model => null;
[ModelBinder]
public int IgnoreMe { get; set; }
public void OnPost() { }
}
[Fact] // If the model has no handler methods, we look at the page instead.
public void CreateDescriptor_FindsHandlerMethodOnPage_WhenModelHasNoHandlers()
{
// Arrange
var type = typeof(PageWithHandler);
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(),
new RazorPageAttribute("/Pages/Index", type, ""));
// Assert
Assert.Collection(result.BoundProperties, p => Assert.Equal("BindMe", p.Name));
Assert.Collection(result.HandlerMethods, h => Assert.Equal("OnGet", h.MethodInfo.Name));
Assert.Same(typeof(PageWithHandler).GetTypeInfo(), result.HandlerTypeInfo);
Assert.Same(typeof(PocoModel).GetTypeInfo(), result.ModelTypeInfo);
Assert.Same(typeof(PageWithHandler).GetTypeInfo(), result.PageTypeInfo);
}
private class PocoModel
{
// Just a plain ol' model, nothing to see here.
[ModelBinder]
public int IgnoreMe { get; set; }
}
private class PageWithHandler
{
public PocoModel Model => null;
[ModelBinder]
public int BindMe { get; set; }
public void OnGet() { }
provider1.Verify();
provider2.Verify();
}
[Fact]
public void CreateHandlerMethods_DiscoversHandlersFromBaseType()
public void Load_InvokesApplicationModelConventions()
{
// Arrange
var type = typeof(InheritsMethods).GetTypeInfo();
var descriptor = new PageActionDescriptor();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results.OrderBy(h => h.MethodInfo.Name).ToArray(),
(handler) =>
var compilerProvider = GetCompilerProvider();
var model = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
var provider = new Mock<IPageApplicationModelProvider>();
provider.Setup(p => p.OnProvidersExecuting(It.IsAny<PageApplicationModelProviderContext>()))
.Callback((PageApplicationModelProviderContext c) =>
{
Assert.Equal("OnGet", handler.MethodInfo.Name);
Assert.Equal(typeof(InheritsMethods), handler.MethodInfo.DeclaringType);
},
(handler) =>
{
Assert.Equal("OnGet", handler.MethodInfo.Name);
Assert.Equal(typeof(TestSetPageModel), handler.MethodInfo.DeclaringType);
},
(handler) =>
{
Assert.Equal("OnPost", handler.MethodInfo.Name);
Assert.Equal(typeof(TestSetPageModel), handler.MethodInfo.DeclaringType);
c.PageApplicationModel = model;
});
}
var providers = new[] { provider.Object };
private class TestSetPageModel
{
public void OnGet()
{
}
public void OnPost()
{
}
}
private class TestSetPageWithModel
{
public TestSetPageModel Model { get; set; }
}
private class InheritsMethods : TestSetPageModel
{
public new void OnGet()
{
}
}
[Fact]
public void CreateHandlerMethods_IgnoresNonPublicMethods()
{
// Arrange
var type = typeof(ProtectedModel).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Empty(results);
}
private class ProtectedModel
{
protected void OnGet()
{
}
private void OnPost()
{
}
}
[Fact]
public void CreateHandlerMethods_IgnoreGenericTypeParameters()
{
// Arrange
var type = typeof(GenericClassModel).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Empty(results);
}
private class GenericClassModel
{
public void OnGet<T>()
{
}
}
[Fact]
public void CreateHandlerMethods_IgnoresStaticMethods()
{
// Arrange
var type = typeof(PageModelWithStaticHandler).GetTypeInfo();
var expected = type.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results,
handler => Assert.Same(expected, handler.MethodInfo));
}
private class PageModelWithStaticHandler
{
public static void OnGet(string name)
{
}
public void OnGet()
{
}
}
[Fact]
public void CreateHandlerMethods_IgnoresAbstractMethods()
{
// Arrange
var type = typeof(PageModelWithAbstractMethod).GetTypeInfo();
var expected = type.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results,
handler => Assert.Same(expected, handler.MethodInfo));
}
private abstract class PageModelWithAbstractMethod
{
public abstract void OnPost(string name);
public void OnGet()
{
}
}
[Fact]
public void CreateHandlerMethods_IgnoresMethodWithNonHandlerAttribute()
{
// Arrange
var type = typeof(PageWithNonHandlerMethod).GetTypeInfo();
var expected = type.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance);
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results,
handler => Assert.Same(expected, handler.MethodInfo));
}
private class PageWithNonHandlerMethod
{
[NonHandler]
public void OnPost(string name) { }
public void OnGet()
{
}
}
// There are more tests for the parsing elsewhere, this is just testing that it's wired
// up to the descriptor.
[Fact]
public void CreateHandlerMethods_ParsesMethod()
{
// Arrange
var type = typeof(PageModelWithHandlerNames).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results.OrderBy(h => h.MethodInfo.Name),
handler =>
var options = new TestOptionsManager<RazorPagesOptions>();
var convention = new Mock<IPageApplicationModelConvention>();
convention.Setup(c => c.Apply(It.IsAny<PageApplicationModel>()))
.Callback((PageApplicationModel m) =>
{
Assert.Same(type.GetMethod(nameof(PageModelWithHandlerNames.OnPutDeleteAsync)), handler.MethodInfo);
Assert.Equal("Put", handler.HttpMethod);
Assert.Equal("Delete", handler.Name.ToString());
Assert.Same(model, m);
});
options.Value.ApplicationModelConventions.Add(convention.Object);
var loader = new DefaultPageLoader(
providers,
compilerProvider,
options);
// Act
var result = loader.Load(new PageActionDescriptor());
// Assert
convention.Verify();
}
private class PageModelWithHandlerNames
private static IViewCompilerProvider GetCompilerProvider()
{
public void OnPutDeleteAsync()
var descriptor = new CompiledViewDescriptor
{
}
ViewAttribute = new RazorPageAttribute("/Views/Index.cshtml", typeof(object), null),
};
public void Foo() // This isn't a valid handler name.
{
}
}
[Fact]
public void CreateHandlerMethods_AddsParameterDescriptors()
{
// Arrange
var type = typeof(PageWithHandlerParameters).GetTypeInfo();
var expected = type.GetMethod(nameof(PageWithHandlerParameters.OnPost), BindingFlags.Public | BindingFlags.Instance);
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
var handler = Assert.Single(results);
Assert.Collection(
handler.Parameters,
p =>
{
Assert.Equal(typeof(string), p.ParameterType);
Assert.NotNull(p.ParameterInfo);
Assert.Equal("name", p.Name);
},
p =>
{
Assert.Equal(typeof(int), p.ParameterType);
Assert.NotNull(p.ParameterInfo);
Assert.Equal("id", p.Name);
Assert.Equal("personId", p.BindingInfo.BinderModelName);
});
}
private class PageWithHandlerParameters
{
public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
}
// We're using PropertyHelper from Common to find the properties here, which implements
// out standard set of semantics for properties that the framework interacts with.
//
// One of the desirable consequences of that is we only find 'visible' properties. We're not
// retesting all of the details of PropertyHelper here, just the visibility part as a quick check
// that we're using PropertyHelper as expected.
[Fact]
public void CreateBoundProperties_UsesPropertyHelpers_ToFindProperties()
{
// Arrange
var type = typeof(HidesAProperty).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal(typeof(HidesAProperty).GetTypeInfo(), p.Property.DeclaringType.GetTypeInfo());
});
}
private class HasAHiddenProperty
{
[BindProperty]
public int Property { get; set; }
}
private class HidesAProperty : HasAHiddenProperty
{
[BindProperty]
public new int Property { get; set; }
}
// We're using BindingInfo to make property binding opt-in here. We're not going to retest
// all of the semantics of BindingInfo here, as that's covered elsewhere.
[Fact]
public void CreateBoundProperties_UsesBindingInfo_ToFindProperties()
{
// Arrange
var type = typeof(ModelWithBindingInfoProperty).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
});
}
private class ModelWithBindingInfoProperty
{
[ModelBinder]
public int Property { get; set; }
public int IgnoreMe { get; set; }
}
// Additionally [BindProperty] on a property can opt-in a property
[Fact]
public void CreateBoundProperties_UsesBindPropertyAttribute_ToFindProperties()
{
// Arrange
var type = typeof(ModelWithBindProperty).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
});
}
private class ModelWithBindProperty
{
[BindProperty]
public int Property { get; set; }
public int IgnoreMe { get; set; }
}
[Fact]
public void CreateBoundProperties_SupportsGet_OnProperty()
{
// Arrange
var type = typeof(ModelSupportsGetOnProperty).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
Assert.NotNull(p.BindingInfo.RequestPredicate);
Assert.True(p.BindingInfo.RequestPredicate(new ActionContext()
{
HttpContext = new DefaultHttpContext()
{
Request =
{
Method ="GET",
}
}
}));
});
}
private class ModelSupportsGetOnProperty
{
[BindProperty(SupportsGet = true)]
public int Property { get; set; }
public int IgnoreMe { get; set; }
}
[Theory]
[InlineData("Foo")]
[InlineData("On")]
[InlineData("OnAsync")]
[InlineData("Async")]
public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName)
{
// Arrange
// Act
var result = DefaultPageLoader.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
// Assert
Assert.False(result);
Assert.Null(httpMethod);
Assert.Null(handler);
}
[Theory]
[InlineData("OnG", "G", null)]
[InlineData("OnGAsync", "G", null)]
[InlineData("OnPOST", "P", "OST")]
[InlineData("OnPOSTAsync", "P", "OST")]
[InlineData("OnDeleteFoo", "Delete", "Foo")]
[InlineData("OnDeleteFooAsync", "Delete", "Foo")]
[InlineData("OnMadeupLongHandlerName", "Madeup", "LongHandlerName")]
[InlineData("OnMadeupLongHandlerNameAsync", "Madeup", "LongHandlerName")]
public void TryParseHandler_ParsesHandlerNames_ValidData(string methodName, string expectedHttpMethod, string expectedHandler)
{
// Arrange
// Act
var result = DefaultPageLoader.TryParseHandlerMethod(methodName, out var httpMethod, out var handler);
// Assert
Assert.True(result);
Assert.Equal(expectedHttpMethod, httpMethod);
Assert.Equal(expectedHandler, handler);
var compiler = new Mock<IViewCompiler>();
compiler.Setup(c => c.CompileAsync(It.IsAny<string>()))
.ReturnsAsync(descriptor);
var compilerProvider = new Mock<IViewCompilerProvider>();
compilerProvider.Setup(p => p.GetCompiler())
.Returns(compiler.Object);
return compilerProvider.Object;
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
@ -18,11 +17,9 @@ using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
@ -235,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
loader.Object,
CreateActionDescriptorCollection(descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
var context = new ActionInvokerProviderContext(new ActionContext
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
@ -251,6 +248,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var entry1 = actionInvoker.CacheEntry;
// Act - 2
context = new ActionInvokerProviderContext(new ActionContext
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
invokerProvider.OnProvidersExecuting(context);
// Assert - 2
@ -445,6 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
HandlerTypeInfo = modelTypeInfo ?? pageTypeInfo,
ModelTypeInfo = modelTypeInfo ?? pageTypeInfo,
PageTypeInfo = pageTypeInfo,
FilterDescriptors = Array.Empty<FilterDescriptor>(),
};
}

View File

@ -1,41 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageFilterApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsFiltersToModels()
{
// Arrange
var applicationModel1 = new PageApplicationModel("/Home.cshtml", "/Home.cshtml");
var applicationModel2 = new PageApplicationModel("/About.cshtml", "/About.cshtml");
var modelProvider = new PageFilterApplicationModelProvider();
var context = new PageApplicationModelProviderContext
{
Results =
{
applicationModel1,
applicationModel2,
}
};
// Act
modelProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(applicationModel1.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter),
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
Assert.Collection(applicationModel2.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter),
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
}
}
}

View File

@ -0,0 +1,17 @@
// 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.RazorPages.Internal
{
public partial class DefaultPageApplicationModelProviderTest
{
private class PocoModel
{
// Just a plain ol' model, nothing to see here.
[ModelBinder]
public int IgnoreMe { get; set; }
}
}
}

View File

@ -10,7 +10,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RazorProjectPageApplicationModelProviderTest
public class RazorProjectPageRouteModelProviderTest
{
[Fact]
public void OnProvidersExecuting_ReturnsPagesWithPageDirective()
@ -27,14 +27,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
optionsManager.Value.RootDirectory = "/";
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageApplicationModelProviderContext();
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
@ -61,14 +61,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
optionsManager.Value.RootDirectory = "/";
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageApplicationModelProviderContext();
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath);
@ -99,12 +99,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
optionsManager.Value.RootDirectory = "/";
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageApplicationModelProviderContext();
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
// 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.",
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);
}
@ -125,14 +125,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
optionsManager.Value.RootDirectory = "/";
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageApplicationModelProviderContext();
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
@ -163,14 +163,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var optionsManager = new TestOptionsManager<RazorPagesOptions>();
optionsManager.Value.RootDirectory = "/Pages";
var provider = new RazorProjectPageApplicationModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageApplicationModelProviderContext();
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
Assert.Collection(context.RouteModels,
model =>
{
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);

View File

@ -0,0 +1,36 @@
// 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.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class TempDataFilterPageApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsFiltersToModel()
{
// Arrange
var actionDescriptor = new PageActionDescriptor();
var applicationModel = new PageApplicationModel(
actionDescriptor,
typeof(object).GetTypeInfo(),
new object[0]);
var applicationModelProvider = new TempDataFilterPageApplicationModelProvider();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo())
{
PageApplicationModel = applicationModel,
};
// Act
applicationModelProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(
applicationModel.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter));
}
}
}

View File

@ -422,13 +422,22 @@ namespace Microsoft.AspNetCore.Mvc
typeof(JsonPatchOperationsArrayProvider),
}
},
{
typeof(IPageRouteModelProvider),
new[]
{
typeof(CompiledPageRouteModelProvider),
typeof(RazorProjectPageRouteModelProvider),
}
},
{
typeof(IPageApplicationModelProvider),
new[]
{
typeof(CompiledPageApplicationModelProvider),
typeof(RazorProjectPageApplicationModelProvider),
typeof(PageFilterApplicationModelProvider),
typeof(AuthorizationPageApplicationModelProvider),
typeof(AuthorizationPageApplicationModelProvider),
typeof(DefaultPageApplicationModelProvider),
typeof(TempDataFilterPageApplicationModelProvider),
}
},
};

View File

@ -1,6 +1,7 @@
// 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.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite

View File

@ -0,0 +1,15 @@
// 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.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
[Authorize]
public class ModelWithAuthFilter : PageModel
{
public IActionResult OnGet() => Page();
}
}

View File

@ -0,0 +1,4 @@
@page
@model RazorPagesWebSite.ModelWithAuthFilter
Can't see me

View File

@ -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.
using System;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
[HandlerChangingPageFilter]
public class ModelWithPageFilter
{
public string Message { get; private set; }
public void OnGet()
{
Message = $"Hello from {nameof(OnGet)}";
}
public void OnGetEdit()
{
Message = $"Hello from {nameof(OnGetEdit)}";
}
}
[AttributeUsage(AttributeTargets.Class)]
public class HandlerChangingPageFilterAttribute : Attribute, IPageFilter
{
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
context.HandlerMethod = context.ActionDescriptor.HandlerMethods.First(m => m.Name == "Edit");
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
}
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}

View File

@ -0,0 +1,4 @@
@page
@model RazorPagesWebSite.ModelWithPageFilter
@Model.Message

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Authorization;
namespace RazorPagesWebSite
{
[AllowAnonymous]
public class AnonymousModel
{
public void OnGet()
{
}
}
}

View File

@ -0,0 +1,3 @@
@page
@model AnonymousModel
Hello from Anonymous