Design updates to handler methods and binding

Implements #6091, #6072, #6090, #5927
This commit is contained in:
Ryan Nowak 2017-04-20 17:06:09 -07:00
parent 908b4c8d62
commit 297196baa0
25 changed files with 1538 additions and 1155 deletions

View File

@ -0,0 +1,39 @@
// 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.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class BindPropertyAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata
{
private BindingSource _bindingSource;
public bool SupportsGet { get; set; }
public Type BinderType { get; set; }
/// <inheritdoc />
public virtual BindingSource BindingSource
{
get
{
if (_bindingSource == null && BinderType != null)
{
return BindingSource.Custom;
}
return _bindingSource;
}
protected set
{
_bindingSource = value;
}
}
/// <inheritdoc />
public string Name { get; set; }
}
}

View File

@ -30,15 +30,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
/// <summary>
/// Gets or sets the <see cref="TypeInfo"/> of the page.
/// Gets the list of handler methods for the page.
/// </summary>
public TypeInfo PageTypeInfo { get; set; }
public IList<HandlerMethodDescriptor> HandlerMethods { get; set; }
/// <summary>
/// Gets or sets the <see cref="TypeInfo"/> of the type that defines handler methods for the page. This can be
/// the same as <see cref="PageTypeInfo"/> and <see cref="ModelTypeInfo"/> if the page does not have an
/// explicit model type defined.
/// </summary>
public TypeInfo HandlerTypeInfo { get; set; }
/// <summary>
/// Gets or sets the <see cref="TypeInfo"/> of the model.
/// </summary>
public TypeInfo ModelTypeInfo { get; set; }
public IList<HandlerMethodDescriptor> HandlerMethods { get; } = new List<HandlerMethodDescriptor>();
/// <summary>
/// Gets or sets the <see cref="TypeInfo"/> of the page.
/// </summary>
public TypeInfo PageTypeInfo { get; set; }
}
}

View File

@ -1,25 +1,19 @@
// 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 System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class HandlerMethodDescriptor
{
public MethodInfo Method { get; set; }
public Func<object, object[], Task<IActionResult>> Executor { get; set; }
public MethodInfo MethodInfo { get; set; }
public string HttpMethod { get; set; }
public StringSegment FormAction { get; set; }
public string FormAction { get; set; }
public HandlerParameterDescriptor[] Parameters { get; set; }
public bool OnPage { get; set; }
public IList<HandlerParameterDescriptor> Parameters { get; set; }
}
}

View File

@ -1,7 +1,6 @@
// 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.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -9,8 +8,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class HandlerParameterDescriptor : ParameterDescriptor
{
public object DefaultValue { get; set; }
public ParameterInfo Parameter { get; set; }
public ParameterInfo ParameterInfo { get; set; }
}
}

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 System.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class PageBoundPropertyDescriptor : ParameterDescriptor
{
public PropertyInfo Property { get; set; }
public bool SupportsGet { get; set; }
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
/// <summary>
/// An attribute for base classes for Pages and PageModels. Applying this attribute to a type
/// suppresses discovery of handler methods and bound properties for that type.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class PagesBaseClassAttribute : Attribute
{
}
}

View File

@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
if (ambiguousMatches != null)
{
var ambiguousMethods = string.Join(", ", ambiguousMatches.Select(m => m.Method));
var ambiguousMethods = string.Join(", ", ambiguousMatches.Select(m => m.MethodInfo));
throw new InvalidOperationException(Resources.FormatAmbiguousHandler(Environment.NewLine, ambiguousMethods));
}
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
continue;
}
else if (handler.FormAction.HasValue &&
else if (handler.FormAction != null &&
!handler.FormAction.Equals(formAction, StringComparison.OrdinalIgnoreCase))
{
continue;

View File

@ -1,9 +1,13 @@
// 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.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
@ -20,21 +24,249 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
{
var compilationResult = _compiler.Compile(actionDescriptor.RelativePath);
var compiledTypeInfo = compilationResult.CompiledType.GetTypeInfo();
// If a model type wasn't set in code then the model property's type will be the same
// as the compiled type.
var modelTypeInfo = compiledTypeInfo.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
if (modelTypeInfo == compiledTypeInfo)
var result = _compiler.Compile(actionDescriptor.RelativePath);
return CreateDescriptor(actionDescriptor, result.CompiledType.GetTypeInfo());
}
// Internal for unit testing
internal static CompiledPageActionDescriptor CreateDescriptor(PageActionDescriptor actionDescriptor, TypeInfo pageType)
{
// 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.
//
// However, we allow it to be null here for ease of testing.
var modelType = pageType.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)
{
modelTypeInfo = null;
handlerType = modelType;
}
else
{
handlerType = pageType;
handlerMethods = CreateHandlerMethods(pageType);
}
var boundProperties = CreateBoundProperties(handlerType);
return new CompiledPageActionDescriptor(actionDescriptor)
{
PageTypeInfo = compiledTypeInfo,
ModelTypeInfo = modelTypeInfo,
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 formAction))
{
continue;
}
var parameters = CreateHandlerParameters(method);
var handlerMethodDescriptor = new HandlerMethodDescriptor()
{
MethodInfo = method,
FormAction = formAction,
HttpMethod = httpMethod,
Parameters = parameters,
};
results.Add(handlerMethodDescriptor);
}
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)
{
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;
}
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());
// If the type has a [BindPropertyAttribute] then we'll consider any and all public properties bindable.
var bindPropertyOnType = type.GetCustomAttribute<BindPropertyAttribute>();
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 (bindingInfo == null && bindPropertyOnType == null)
{
continue;
}
if (property.Property.DeclaringType.GetTypeInfo().IsDefined(typeof(PagesBaseClassAttribute)))
{
continue;
}
var bindPropertyOnProperty = property.Property.GetCustomAttribute<BindPropertyAttribute>();
var supportsGet = bindPropertyOnProperty?.SupportsGet ?? bindPropertyOnType?.SupportsGet ?? false;
var descriptor = new PageBoundPropertyDescriptor()
{
BindingInfo = bindingInfo ?? new BindingInfo(),
Name = property.Name,
Property = property.Property,
ParameterType = property.Property.PropertyType,
SupportsGet = supportsGet,
};
results.Add(descriptor);
}
return results.ToArray();
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
@ -12,38 +13,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public static class ExecutorFactory
{
public static Func<object, object[], Task<IActionResult>> CreateExecutor(
CompiledPageActionDescriptor actionDescriptor,
MethodInfo method,
HandlerParameterDescriptor[] parameters)
public static Func<object, object[], Task<IActionResult>> CreateExecutor(HandlerMethodDescriptor handlerDescriptor)
{
if (actionDescriptor == null)
if (handlerDescriptor == null)
{
throw new ArgumentNullException(nameof(actionDescriptor));
throw new ArgumentNullException(nameof(handlerDescriptor));
}
var handler = CreateHandlerMethod(handlerDescriptor);
if (method == null)
{
throw new ArgumentNullException(nameof(method));
}
if (parameters == null)
{
throw new ArgumentNullException(nameof(parameters));
}
var methodIsDeclaredOnPage = method.DeclaringType.GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo);
var handler = CreateHandlerMethod(method, parameters);
return async (receiver, arguments) =>
{
var result = await handler.Execute(receiver, arguments);
return result;
};
return handler.Execute;
}
private static HandlerMethod CreateHandlerMethod(MethodInfo method, HandlerParameterDescriptor[] parameters)
private static HandlerMethod CreateHandlerMethod(HandlerMethodDescriptor handlerDescriptor)
{
var method = handlerDescriptor.MethodInfo;
var parameters = handlerDescriptor.Parameters.ToArray();
var methodParameters = method.GetParameters();
var returnType = method.ReturnType;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -327,7 +328,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
_pageContext.ViewStarts = viewStarts;
if (actionDescriptor.ModelTypeInfo == null)
if (actionDescriptor.ModelTypeInfo == actionDescriptor.PageTypeInfo)
{
_model = _page;
}
@ -341,10 +342,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_pageContext.ViewData.Model = _model;
}
if (CacheEntry.PropertyBinder != null &&
!string.Equals(_pageContext.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
if (CacheEntry.PropertyBinder != null)
{
// Don't bind properties on GET requests
await CacheEntry.PropertyBinder(_page, _model);
}
@ -379,8 +378,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
var arguments = await GetArguments(handler);
var executor = handler.Executor;
result = await executor(handler.OnPage ? _page : _model, arguments);
Func<object, object[], Task<IActionResult>> executor = null;
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
{
if (object.ReferenceEquals(handler, actionDescriptor.HandlerMethods[i]))
{
executor = CacheEntry.Executors[i];
break;
}
}
var instance = actionDescriptor.ModelTypeInfo == actionDescriptor.HandlerTypeInfo ? _model : _page;
result = await executor(instance, arguments);
}
if (result == null)
@ -393,10 +402,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private async Task<object[]> GetArguments(HandlerMethodDescriptor handler)
{
var arguments = new object[handler.Parameters.Length];
var arguments = new object[handler.Parameters.Count];
var valueProvider = await CompositeValueProvider.CreateAsync(_pageContext, _pageContext.ValueProviderFactories);
for (var i = 0; i < handler.Parameters.Length; i++)
for (var i = 0; i < handler.Parameters.Count; i++)
{
var parameter = handler.Parameters[i];
@ -406,7 +415,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
parameter,
value: null);
arguments[i] = result.IsModelSet ? result.Model : parameter.DefaultValue;
if (result.IsModelSet)
{
arguments[i] = result.Model;
}
else if (parameter.ParameterInfo.HasDefaultValue)
{
arguments[i] = parameter.ParameterInfo.DefaultValue;
}
else if (parameter.ParameterType.GetTypeInfo().IsValueType)
{
arguments[i] = Activator.CreateInstance(parameter.ParameterType);
}
}
return arguments;

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@ -19,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Func<PageContext, object> modelFactory,
Action<PageContext, object> releaseModel,
Func<Page, object, Task> propertyBinder,
Func<object, object[], Task<IActionResult>>[] executors,
IReadOnlyList<Func<IRazorPage>> viewStartFactories,
FilterItem[] cacheableFilters)
{
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ModelFactory = modelFactory;
ReleaseModel = releaseModel;
PropertyBinder = propertyBinder;
Executors = executors;
ViewStartFactories = viewStartFactories;
CacheableFilters = cacheableFilters;
}
@ -54,6 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
/// </summary>
public Func<Page, object, Task> PropertyBinder { get; }
public Func<object, object[], Task<IActionResult>>[] Executors { get; }
/// <summary>
/// Gets the applicable ViewStart pages.
/// </summary>

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@ -181,20 +182,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Func<PageContext, object> modelFactory = null;
Action<PageContext, object> modelReleaser = null;
if (compiledActionDescriptor.ModelTypeInfo == null)
if (compiledActionDescriptor.ModelTypeInfo != compiledActionDescriptor.PageTypeInfo)
{
PopulateHandlerMethodDescriptors(compiledActionDescriptor.PageTypeInfo, compiledActionDescriptor);
}
else
{
PopulateHandlerMethodDescriptors(compiledActionDescriptor.ModelTypeInfo, compiledActionDescriptor);
modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor);
}
var viewStartFactories = GetViewStartFactories(compiledActionDescriptor);
var executors = GetExecutors(compiledActionDescriptor);
return new PageActionInvokerCacheEntry(
compiledActionDescriptor,
pageFactory,
@ -202,6 +199,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelFactory,
modelReleaser,
propertyBinder,
executors,
viewStartFactories,
cachedFilters);
}
@ -226,82 +224,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return viewStartFactories;
}
// Internal for testing.
internal static void PopulateHandlerMethodDescriptors(TypeInfo type, CompiledPageActionDescriptor actionDescriptor)
{
var methods = type.GetMethods();
for (var i = 0; i < methods.Length; i++)
{
var method = methods[i];
if (!IsValidHandler(method))
{
continue;
}
string httpMethod;
int formActionStart;
if (method.Name.StartsWith("OnGet", StringComparison.Ordinal))
{
httpMethod = "GET";
formActionStart = "OnGet".Length;
}
else if (method.Name.StartsWith("OnPost", StringComparison.Ordinal))
{
httpMethod = "POST";
formActionStart = "OnPost".Length;
}
else
{
continue;
}
var formActionLength = method.Name.Length - formActionStart;
if (method.Name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
{
formActionLength -= "Async".Length;
}
var formAction = new StringSegment(method.Name, formActionStart, formActionLength);
var parameters = GetHandlerParameters(method);
var handlerMethodDescriptor = new HandlerMethodDescriptor
{
Method = method,
Executor = ExecutorFactory.CreateExecutor(actionDescriptor, method, parameters),
FormAction = formAction,
HttpMethod = httpMethod,
Parameters = parameters,
OnPage = actionDescriptor.PageTypeInfo == type,
};
actionDescriptor.HandlerMethods.Add(handlerMethodDescriptor);
}
}
private static HandlerParameterDescriptor[] GetHandlerParameters(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()),
DefaultValue = GetDefaultValue(parameter),
Name = parameter.Name,
Parameter = parameter,
ParameterType = parameter.ParameterType,
};
}
return parameters;
}
private static object GetDefaultValue(ParameterInfo methodParameter)
{
object defaultValue = null;
@ -317,42 +239,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return defaultValue;
}
private static bool IsValidHandler(MethodInfo methodInfo)
private static Func<object, object[], Task<IActionResult>>[] GetExecutors(CompiledPageActionDescriptor actionDescriptor)
{
// 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)
if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0)
{
return false;
return Array.Empty<Func<object, object[], Task<IActionResult>>>();
}
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
var results = new Func<object, object[], Task<IActionResult>>[actionDescriptor.HandlerMethods.Count];
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
{
return false;
results[i] = ExecutorFactory.CreateExecutor(actionDescriptor.HandlerMethods[i]);
}
if (methodInfo.IsStatic)
{
return false;
}
if (methodInfo.IsAbstract)
{
return false;
}
if (methodInfo.IsConstructor)
{
return false;
}
if (methodInfo.IsGenericMethod)
{
return false;
}
return methodInfo.IsPublic;
return results;
}
internal class InnerCache

View File

@ -3,12 +3,11 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
@ -29,15 +28,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
throw new ArgumentNullException(nameof(actionDescriptor));
}
var bindPropertiesOnPage = actionDescriptor.ModelTypeInfo == null;
var target = bindPropertiesOnPage ? actionDescriptor.PageTypeInfo : actionDescriptor.ModelTypeInfo;
var propertiesToBind = GetPropertiesToBind(modelMetadataProvider, target);
if (propertiesToBind.Count == 0)
var properties = actionDescriptor.BoundProperties;
if (properties == null || properties.Count == 0)
{
return null;
}
var isHandlerThePage = actionDescriptor.HandlerTypeInfo == actionDescriptor.PageTypeInfo;
var type = actionDescriptor.HandlerTypeInfo.AsType();
var metadata = new ModelMetadata[properties.Count];
for (var i = 0; i < properties.Count; i++)
{
metadata[i] = modelMetadataProvider.GetMetadataForProperty(type, properties[i].Name);
}
return Bind;
Task Bind(Page page, object model)
@ -47,14 +52,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
throw new ArgumentNullException(nameof(page));
}
if (!bindPropertiesOnPage && model == null)
if (!isHandlerThePage && model == null)
{
throw new ArgumentNullException(nameof(model));
}
var pageContext = page.PageContext;
var instance = bindPropertiesOnPage ? page : model;
return BindPropertiesAsync(parameterBinder, pageContext, instance, propertiesToBind);
var instance = isHandlerThePage ? page : model;
return BindPropertiesAsync(parameterBinder, pageContext, instance, properties, metadata);
}
}
@ -62,92 +67,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ParameterBinder parameterBinder,
PageContext pageContext,
object instance,
IList<PropertyBindingInfo> propertiesToBind)
IList<ParameterDescriptor> properties,
IList<ModelMetadata> metadata)
{
var valueProvider = await GetCompositeValueProvider(pageContext);
for (var i = 0; i < propertiesToBind.Count; i++)
var isGet = string.Equals("GET", pageContext.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase);
var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories);
for (var i = 0; i < properties.Count; i++)
{
var propertyBindingInfo = propertiesToBind[i];
var modelBindingResult = await parameterBinder.BindModelAsync(
pageContext,
valueProvider,
propertyBindingInfo.ParameterDescriptor);
if (modelBindingResult.IsModelSet)
{
var modelMetadata = propertyBindingInfo.ModelMetadata;
PropertyValueSetter.SetValue(
modelMetadata,
instance,
modelBindingResult.Model);
}
}
}
private static IList<PropertyBindingInfo> GetPropertiesToBind(
IModelMetadataProvider modelMetadataProvider,
TypeInfo handlerSourceTypeInfo)
{
var handlerType = handlerSourceTypeInfo.AsType();
var properties = PropertyHelper.GetVisibleProperties(type: handlerType);
var typeMetadata = modelMetadataProvider.GetMetadataForType(handlerType);
var propertyBindingInfo = new List<PropertyBindingInfo>();
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
var bindingInfo = BindingInfo.GetBindingInfo(property.Property.GetCustomAttributes());
if (bindingInfo == null)
if (isGet && !((PageBoundPropertyDescriptor)properties[i]).SupportsGet)
{
continue;
}
var propertyMetadata = typeMetadata.Properties[property.Name] ??
modelMetadataProvider.GetMetadataForProperty(handlerType, property.Name);
if (propertyMetadata == null)
var result = await parameterBinder.BindModelAsync(pageContext, valueProvider, properties[i]);
if (result.IsModelSet)
{
continue;
PropertyValueSetter.SetValue(metadata[i], instance, result.Model);
}
var parameterDescriptor = new ParameterDescriptor
{
BindingInfo = bindingInfo,
Name = property.Name,
ParameterType = property.Property.PropertyType,
};
propertyBindingInfo.Add(new PropertyBindingInfo(parameterDescriptor, propertyMetadata));
}
return propertyBindingInfo;
}
private static async Task<CompositeValueProvider> GetCompositeValueProvider(PageContext pageContext)
{
var factories = pageContext.ValueProviderFactories;
var valueProviderFactoryContext = new ValueProviderFactoryContext(pageContext);
for (var i = 0; i < factories.Count; i++)
{
var factory = factories[i];
await factory.CreateValueProviderAsync(valueProviderFactoryContext);
}
return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders);
}
private struct PropertyBindingInfo
{
public PropertyBindingInfo(
ParameterDescriptor parameterDescriptor,
ModelMetadata modelMetadata)
{
ParameterDescriptor = parameterDescriptor;
ModelMetadata = modelMetadata;
}
public ParameterDescriptor ParameterDescriptor { get; }
public ModelMetadata ModelMetadata { get; }
}
}
}

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 System;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
/// <summary>
/// Specifies that the targeted method is not a page handler method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NonHandlerAttribute : Attribute
{
}
}

View File

@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <summary>
/// A base class for a Razor page.
/// </summary>
[PagesBaseClass]
public abstract class Page : RazorPageBase, IRazorPage
{
private IObjectModelValidator _objectValidator;

View File

@ -22,6 +22,7 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
[PagesBaseClass]
public abstract class PageModel
{
private IObjectModelValidator _objectValidator;

View File

@ -716,6 +716,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.DoesNotContain(validationError, content);
}
[Fact]
public async Task PageProperty_WithSupportsGet_BoundInGet()
{
// Arrange
var expected = "<p>11</p>";
var request = new HttpRequestMessage(HttpMethod.Get, "Pages/PropertyBinding/BindPropertyWithGet?value=11");
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.StartsWith(expected, content.Trim());
}
[Fact]
public async Task PagePropertiesAreInjected()
{

View File

@ -2,6 +2,8 @@
// 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.Http;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Routing;
@ -30,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor,
},
@ -109,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -140,20 +142,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
FormAction = "Delete",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -190,20 +192,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
FormAction = "Delete",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -240,20 +242,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
FormAction = "Delete",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -285,20 +287,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
FormAction = "Delete",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -336,20 +338,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Delete"),
FormAction = "Delete",
};
var pageContext = new PageContext
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -381,7 +383,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var descriptor1 = new HandlerMethodDescriptor
{
HttpMethod = "POST",
FormAction = new StringSegment("Subscribe"),
FormAction = "Subscribe",
};
var descriptor2 = new HandlerMethodDescriptor
@ -393,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -429,13 +431,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(Post)),
MethodInfo = GetType().GetMethod(nameof(Post)),
HttpMethod = "POST",
};
var descriptor2 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(PostAsync)),
MethodInfo = GetType().GetMethod(nameof(PostAsync)),
HttpMethod = "POST",
};
@ -448,7 +450,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -468,7 +470,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
var methods = descriptor1.Method + ", " + descriptor2.Method;
var methods = descriptor1.MethodInfo + ", " + descriptor2.MethodInfo;
var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
Environment.NewLine + Environment.NewLine + methods;
@ -481,16 +483,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange
var descriptor1 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(Post)),
MethodInfo = GetType().GetMethod(nameof(Post)),
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor2 = new HandlerMethodDescriptor
{
Method = GetType().GetMethod(nameof(PostAsync)),
MethodInfo = GetType().GetMethod(nameof(PostAsync)),
HttpMethod = "POST",
FormAction = new StringSegment("Add"),
FormAction = "Add",
};
var descriptor3 = new HandlerMethodDescriptor
@ -502,7 +504,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
HandlerMethods =
HandlerMethods = new List<HandlerMethodDescriptor>()
{
descriptor1,
descriptor2,
@ -528,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
var methods = descriptor1.Method + ", " + descriptor2.Method;
var methods = descriptor1.MethodInfo + ", " + descriptor2.MethodInfo;
var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
Environment.NewLine + Environment.NewLine + methods;

View File

@ -0,0 +1,676 @@
// 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.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class DefaultPageLoaderTest
{
[Fact]
public void CreateDescriptor_CopiesPropertiesFromBaseClass()
{
// Arrange
var expected = new PageActionDescriptor() // We only copy the properties that are meaningful for pages.
{
ActionConstraints = new List<IActionConstraintMetadata>(),
AttributeRouteInfo = new AttributeRouteInfo(),
FilterDescriptors = new List<FilterDescriptor>(),
RelativePath = "/Foo",
RouteValues = new Dictionary<string, string>(),
ViewEnginePath = "/Pages/Foo",
};
// Act
var actual = DefaultPageLoader.CreateDescriptor(expected, typeof(EmptyPage).GetTypeInfo());
// 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).GetTypeInfo();
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(), type);
// 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).GetTypeInfo();
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(), 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).GetTypeInfo();
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(), 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).GetTypeInfo();
// Act
var result = DefaultPageLoader.CreateDescriptor(new PageActionDescriptor(), 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() { }
}
[Fact]
public void CreateHandlerMethods_DiscoversHandlersFromBaseType()
{
// Arrange
var type = typeof(InheritsMethods).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results.OrderBy(h => h.MethodInfo.Name).ToArray(),
(handler) =>
{
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);
});
}
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(PageModelWithFormActions).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateHandlerMethods(type);
// Assert
Assert.Collection(
results.OrderBy(h => h.MethodInfo.Name),
handler =>
{
Assert.Same(type.GetMethod(nameof(PageModelWithFormActions.OnPutDeleteAsync)), handler.MethodInfo);
Assert.Equal("Put", handler.HttpMethod);
Assert.Equal("Delete", handler.FormAction.ToString());
});
}
private class PageModelWithFormActions
{
public void OnPutDeleteAsync()
{
}
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; }
}
// Additionally [BindProperty] on a property can opt-in a property
[Fact]
public void CreateBoundProperties_BindPropertyAttributeOnModel_OptsInAllProperties()
{
// Arrange
var type = typeof(ModelWithBindPropertyOnClass).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
});
}
[BindProperty]
private class ModelWithBindPropertyOnClass : EmptyPageModel
{
public int Property { 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.True(p.SupportsGet);
});
}
private class ModelSupportsGetOnProperty
{
[BindProperty(SupportsGet = true)]
public int Property { get; set; }
public int IgnoreMe { get; set; }
}
[Fact]
public void CreateBoundProperties_SupportsGet_OnClass()
{
// Arrange
var type = typeof(ModelSupportsGetOnClass).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
Assert.True(p.SupportsGet);
});
}
[BindProperty(SupportsGet = true)]
private class ModelSupportsGetOnClass : EmptyPageModel
{
public int Property { get; set; }
}
[Fact]
public void CreateBoundProperties_SupportsGet_Override()
{
// Arrange
var type = typeof(ModelSupportsGetOverride).GetTypeInfo();
// Act
var results = DefaultPageLoader.CreateBoundProperties(type);
// Assert
Assert.Collection(
results.OrderBy(p => p.Property.Name),
p =>
{
Assert.Equal("Property", p.Property.Name);
Assert.False(p.SupportsGet);
});
}
[BindProperty(SupportsGet = true)]
private class ModelSupportsGetOverride : EmptyPageModel
{
[BindProperty(SupportsGet = false)]
public int Property { 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);
}
}
}

View File

@ -16,17 +16,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
public class ExecutorFactoryTest
{
[Fact]
public async Task CreateExecutor_ForActionResultMethod_OnPage()
public async Task CreateExecutor_ForActionResultMethod()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
var handler = new HandlerMethodDescriptor()
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
MethodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ActionResultReturningHandler)),
Parameters = new HandlerParameterDescriptor[0],
};
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ActionResultReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -36,17 +36,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForMethodReturningConcreteSubtypeOfIActionResult_OnPage()
public async Task CreateExecutor_ForMethodReturningConcreteSubtypeOfIActionResult()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
var handler = new HandlerMethodDescriptor()
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
MethodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ConcreteActionResult)),
Parameters = new HandlerParameterDescriptor[0],
};
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ConcreteActionResult));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -56,18 +56,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForActionResultReturningMethod_WithParameters_OnPage()
public async Task CreateExecutor_ForActionResultReturningMethod_WithParameters()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ActionResultReturnHandlerWithParameters));
var parameters = CreateParameters(methodInfo);
var handler = new HandlerMethodDescriptor()
{
MethodInfo = methodInfo,
Parameters = CreateParameters(methodInfo),
};
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, parameters);
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -78,18 +78,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForVoidReturningMethod_OnPage()
public async Task CreateExecutor_ForVoidReturningMethod()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
var handler = new HandlerMethodDescriptor()
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
MethodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidReturningHandler)),
Parameters = new HandlerParameterDescriptor[0],
};
var page = new TestPage();
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -100,18 +101,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForVoidTaskReturningMethod_OnPage()
public async Task CreateExecutor_ForVoidTaskReturningMethod()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
var handler = new HandlerMethodDescriptor()
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
MethodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidTaskReturningHandler)),
Parameters = new HandlerParameterDescriptor[0],
};
var page = new TestPage();
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidTaskReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -122,17 +124,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForTaskOfIActionResultReturningMethod_OnPage()
public async Task CreateExecutor_ForTaskOfIActionResultReturningMethod()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.GenericTaskHandler));
var handler = new HandlerMethodDescriptor()
{
MethodInfo = methodInfo,
Parameters = CreateParameters(methodInfo),
};
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -142,17 +145,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
}
[Fact]
public async Task CreateExecutor_ForTaskOfConcreteActionResultReturningMethod_OnPage()
public async Task CreateExecutor_ForTaskOfConcreteActionResultReturningMethod()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.TaskReturningConcreteSubtype));
var handler = new HandlerMethodDescriptor()
{
MethodInfo = methodInfo,
Parameters = CreateParameters(methodInfo),
};
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
var executor = ExecutorFactory.CreateExecutor(handler);
// Assert
Assert.NotNull(executor);
@ -162,159 +166,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
Assert.Equal("value", contentResult.Content);
}
[Fact]
public async Task CreateExecutor_ForActionResultMethod_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.ActionResultReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(new TestPageModel(), null);
var actionResult = await actionResultTask;
Assert.IsType<EmptyResult>(actionResult);
}
[Fact]
public async Task CreateExecutor_ForMethodReturningConcreteSubtypeOfIActionResult_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.ConcreteActionResult));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(new TestPageModel(), null);
var actionResult = await actionResultTask;
Assert.IsType<ViewResult>(actionResult);
}
[Fact]
public async Task CreateExecutor_ForActionResultReturningMethod_WithParameters_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.ActionResultReturnHandlerWithParameters));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(new TestPageModel(), CreateArguments(methodInfo));
var actionResult = await actionResultTask;
var contentResult = Assert.IsType<ContentResult>(actionResult);
Assert.Equal("Hello 0", contentResult.Content);
}
[Fact]
public async Task CreateExecutor_ForVoidReturningMethod_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var model = new TestPageModel();
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.VoidReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(model, null);
var actionResult = await actionResultTask;
Assert.Null(actionResult);
Assert.True(model.SideEffects);
}
[Fact]
public async Task CreateExecutor_ForVoidTaskReturningMethod_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var model = new TestPageModel();
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.VoidTaskReturningHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]);
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(model, null);
var actionResult = await actionResultTask;
Assert.Null(actionResult);
Assert.True(model.SideEffects);
}
[Fact]
public async Task CreateExecutor_ForTaskOfIActionResultReturningMethod_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.GenericTaskHandler));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(new TestPageModel(), null);
var actionResult = await actionResultTask;
Assert.IsType<EmptyResult>(actionResult);
}
[Fact]
public async Task CreateExecutor_ForTaskOfConcreteActionResultReturningMethod_OnPageModel()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.TaskReturningConcreteSubtype));
// Act
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, CreateParameters(methodInfo));
// Assert
Assert.NotNull(executor);
var actionResultTask = executor(new TestPageModel(), CreateArguments(methodInfo));
var actionResult = await actionResultTask;
var contentResult = Assert.IsType<ContentResult>(actionResult);
Assert.Equal("value", contentResult.Content);
}
[Theory]
[InlineData(nameof(TestPageModel.StringResult))]
[InlineData(nameof(TestPageModel.TaskOfObject))]
@ -322,16 +173,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
public void CreateExecutor_ThrowsIfTypeIsNotAValidReturnType(string methodName)
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
};
var methodInfo = typeof(TestPageModel).GetMethod(methodName);
var handler = new HandlerMethodDescriptor()
{
MethodInfo = methodInfo,
Parameters = CreateParameters(methodInfo),
};
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]));
var ex = Assert.Throws<InvalidOperationException>(() => ExecutorFactory.CreateExecutor(handler));
Assert.Equal($"Unsupported handler method return type '{methodInfo.ReturnType}'.", ex.Message);
}
@ -365,7 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
{
BindingInfo = BindingInfo.GetBindingInfo(p.GetCustomAttributes()),
Name = p.Name,
Parameter = p,
ParameterInfo = p,
ParameterType = p.ParameterType,
}).ToArray();
}

View File

@ -36,27 +36,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
Func<PageContext, object> factory = _ => null;
Action<PageContext, object> releaser = (_, __) => { };
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
pageFactoryProvider
.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(factory);
pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
pageFactoryProvider
.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(releaser);
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
pageFactoryProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
// Act
invokerProvider.OnProvidersExecuting(context);
@ -65,106 +72,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var entry = actionInvoker.CacheEntry;
var compiledPageActionDescriptor = Assert.IsType<CompiledPageActionDescriptor>(entry.ActionDescriptor);
Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath);
Assert.Equal(descriptor.RelativePath, entry.ActionDescriptor.RelativePath);
Assert.Same(factory, entry.PageFactory);
Assert.Same(releaser, entry.ReleasePage);
Assert.Null(entry.ModelFactory);
Assert.Null(entry.ReleaseModel);
}
[Fact]
public void OnProvidersExecuting_CachesModelBinderFactory()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
FilterDescriptors = new FilterDescriptor[0],
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(PageWithBoundProperties).GetTypeInfo(),
});
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactoryProvider = Mock.Of<IPageFactoryProvider>();
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
pageFactoryProvider);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
// Act
invokerProvider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var entry = actionInvoker.CacheEntry;
Assert.NotNull(entry.PropertyBinder);
}
[Fact]
public void OnProvidersExecuting_SetsHandlers()
{
// Arrange
var descriptor = new PageActionDescriptor
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
Func<PageContext, object> factory = _ => null;
Action<PageContext, object> releaser = (_, __) => { };
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestSetPageWithModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(factory);
pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(releaser);
var modelFactoryProvider = new Mock<IPageModelFactoryProvider>();
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
pageFactoryProvider.Object,
modelFactoryProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
// Act
invokerProvider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var entry = actionInvoker.CacheEntry;
Assert.Collection(entry.ActionDescriptor.HandlerMethods,
handlerDescriptor =>
{
Assert.Equal(nameof(TestSetPageModel.OnGet), handlerDescriptor.Method.Name);
Assert.NotNull(handlerDescriptor.Executor);
},
handlerDescriptor =>
{
Assert.Equal(nameof(TestSetPageModel.OnPost), handlerDescriptor.Method.Name);
Assert.NotNull(handlerDescriptor.Executor);
});
}
[Fact]
public void OnProvidersExecuting_WithModel_PopulatesCacheEntry()
{
@ -174,36 +88,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
Func<PageContext, object> factory = _ => null;
Action<PageContext, object> releaser = (_, __) => { };
Func<PageContext, object> modelFactory = _ => null;
Action<PageContext, object> modelDisposer = (_, __) => { };
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactoryProvider = new Mock<IPageFactoryProvider>();
pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
pageFactoryProvider
.Setup(f => f.CreatePageFactory(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(factory);
pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
pageFactoryProvider
.Setup(f => f.CreatePageDisposer(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(releaser);
var modelFactoryProvider = new Mock<IPageModelFactoryProvider>();
modelFactoryProvider.Setup(f => f.CreateModelFactory(It.IsAny<CompiledPageActionDescriptor>()))
modelFactoryProvider
.Setup(f => f.CreateModelFactory(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(modelFactory);
modelFactoryProvider.Setup(f => f.CreateModelDisposer(It.IsAny<CompiledPageActionDescriptor>()))
modelFactoryProvider
.Setup(f => f.CreateModelDisposer(It.IsAny<CompiledPageActionDescriptor>()))
.Returns(modelDisposer);
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
pageFactoryProvider.Object,
modelFactoryProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
// Act
invokerProvider.OnProvidersExecuting(context);
@ -232,18 +155,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
Func<IRazorPage> factory1 = () => null;
Func<IRazorPage> factory2 = () => null;
razorPageFactoryProvider
.Setup(f => f.CreateFactory("/Home/Path1/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(factory1, new IChangeToken[0]));
razorPageFactoryProvider.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
razorPageFactoryProvider
.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(factory2, new[] { Mock.Of<IChangeToken>() }));
var fileProvider = new TestFileProvider();
@ -254,11 +179,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
razorPageFactoryProvider: razorPageFactoryProvider.Object,
razorProject: defaultRazorProject);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
// Act
invokerProvider.OnProvidersExecuting(context);
@ -270,49 +200,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(new[] { factory2, factory1 }, entry.ViewStartFactories);
}
[Fact]
public void OnProvidersExecuting_CachesExecutor()
{
// Arrange
var descriptor = new PageActionDescriptor
{
RelativePath = "/Home/Path1/File.cshtml",
ViewEnginePath = "/Home/Path1/File.cshtml",
FilterDescriptors = new FilterDescriptor[0],
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
var fileProvider = new TestFileProvider();
var defaultRazorProject = new TestRazorProject(fileProvider);
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
razorPageFactoryProvider: razorPageFactoryProvider.Object,
razorProject: defaultRazorProject);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
// Act
invokerProvider.OnProvidersExecuting(context);
// Assert
Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var actionDescriptor = actionInvoker.CacheEntry.ActionDescriptor;
Assert.Collection(actionDescriptor.HandlerMethods,
handlerDescriptor =>
{
Assert.Equal(nameof(TestPageModel.OnGet), handlerDescriptor.Method.Name);
Assert.NotNull(handlerDescriptor.Executor);
});
}
[Fact]
public void OnProvidersExecuting_CachesEntries()
@ -323,18 +210,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
CreateActionDescriptorCollection(descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
// Act - 1
invokerProvider.OnProvidersExecuting(context);
@ -363,21 +254,31 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
var descriptorCollection1 = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var descriptorCollection2 = new ActionDescriptorCollection(new[] { descriptor }, version: 2);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.SetupSequence(p => p.ActionDescriptors)
actionDescriptorProvider
.SetupSequence(p => p.ActionDescriptors)
.Returns(descriptorCollection1)
.Returns(descriptorCollection2);
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor));
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
var context = new ActionInvokerProviderContext(new ActionContext()
{
ActionDescriptor = descriptor,
HttpContext = new DefaultHttpContext(),
RouteData = new RouteData(),
});
// Act - 1
invokerProvider.OnProvidersExecuting(context);
@ -397,217 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.NotSame(entry1, entry2);
}
[Fact]
public void PopulateHandlerMethodDescriptors_DiscoversHandlersFromBaseType()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var actionDescriptor = CreateCompiledPageActionDescriptor(descriptor, typeof(InheritsMethods));
var type = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(type, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods,
(handler) =>
{
Assert.Equal("OnGet", handler.Method.Name);
Assert.Equal(typeof(InheritsMethods), handler.Method.DeclaringType);
},
(handler) =>
{
Assert.Equal("OnGet", handler.Method.Name);
Assert.Equal(typeof(TestSetPageModel), handler.Method.DeclaringType);
},
(handler) =>
{
Assert.Equal("OnPost", handler.Method.Name);
Assert.Equal(typeof(TestSetPageModel), handler.Method.DeclaringType);
});
}
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoresNonPublicMethods()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var actionDescriptor = CreateCompiledPageActionDescriptor(descriptor, typeof(ProtectedModel));
var type = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(type, actionDescriptor);
// Assert
Assert.Empty(actionDescriptor.HandlerMethods);
}
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoreGenericTypeParameters()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var actionDescriptor = CreateCompiledPageActionDescriptor(descriptor, typeof(GenericClassModel));
var type = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(type, actionDescriptor);
// Assert
Assert.Empty(actionDescriptor.HandlerMethods);
}
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoresStaticMethods()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo();
var expected = modelTypeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance);
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods,
handler => Assert.Same(expected, handler.Method));
}
[Fact]
public void PopulateHandlerMethodDescriptors_IgnoresAbstractMethods()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo();
var expected = modelTypeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet));
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods,
handler => Assert.Same(expected, handler.Method));
}
[Fact]
public void PopulateHandlerMethodDescriptors_DiscoversMethodsWithFormActions()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Index.cshtml"
};
var modelTypeInfo = typeof(PageModelWithFormActions).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor(descriptor)
{
ModelTypeInfo = modelTypeInfo,
PageTypeInfo = typeof(object).GetTypeInfo(),
};
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(modelTypeInfo, actionDescriptor);
// Assert
Assert.Collection(actionDescriptor.HandlerMethods.OrderBy(h => h.Method.Name),
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnGet)), handler.Method);
Assert.Equal("GET", handler.HttpMethod);
Assert.Equal(0, handler.FormAction.Length);
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostAdd)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("Add", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostAddCustomer)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("AddCustomer", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
},
handler =>
{
Assert.Same(modelTypeInfo.GetMethod(nameof(PageModelWithFormActions.OnPostDeleteAsync)), handler.Method);
Assert.Equal("POST", handler.HttpMethod);
Assert.Equal("Delete", handler.FormAction.ToString());
Assert.NotNull(handler.Executor);
});
}
[Fact]
public void PopulateHandlerMethodDescriptors_AllowOnlyOneMethod()
{
// Arrange
var descriptor = new PageActionDescriptor()
{
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var actionDescriptor = CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel));
var type = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
// Act
PageActionInvokerProvider.PopulateHandlerMethodDescriptors(type, actionDescriptor);
// Assert
var handler = Assert.Single(actionDescriptor.HandlerMethods);
Assert.Equal("OnGet", handler.Method.Name);
}
[Fact]
public void GetViewStartFactories_FindsFullHeirarchy()
{
@ -618,12 +308,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var fileProvider = new TestFileProvider();
fileProvider.AddFile("/View/Deeper/Not_ViewStart.cshtml", "page content");
@ -635,20 +324,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var razorProject = new TestRazorProject(fileProvider);
var mock = new Mock<IRazorPageFactoryProvider>();
mock.Setup(p => p.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
mock
.Setup(p => p.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()))
.Verifiable();
mock.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
mock
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()))
.Verifiable();
mock.Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
mock
.Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()))
.Verifiable();
var razorPageFactoryProvider = mock.Object;
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
pageProvider: null,
modelProvider: null,
razorPageFactoryProvider: razorPageFactoryProvider,
@ -675,16 +368,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Pages/Level1/Level2/Index.cshtml"
};
var compiledPageDescriptor = new CompiledPageActionDescriptor(descriptor)
{
PageTypeInfo = typeof(object).GetTypeInfo(),
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(compiledPageDescriptor);
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var fileProvider = new TestFileProvider();
fileProvider.AddFile("/_ViewStart.cshtml", "page content");
@ -696,13 +389,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var razorProject = new TestRazorProject(fileProvider);
var mock = new Mock<IRazorPageFactoryProvider>(MockBehavior.Strict);
mock.Setup(p => p.CreateFactory("/Pages/Level1/Level2/_ViewStart.cshtml"))
mock
.Setup(p => p.CreateFactory("/Pages/Level1/Level2/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()))
.Verifiable();
mock.Setup(p => p.CreateFactory("/Pages/Level1/_ViewStart.cshtml"))
mock
.Setup(p => p.CreateFactory("/Pages/Level1/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()))
.Verifiable();
var razorPageFactoryProvider = mock.Object;
var options = new RazorPagesOptions
{
RootDirectory = rootDirectory,
@ -710,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
razorPageFactoryProvider: razorPageFactoryProvider,
razorProject: razorProject,
razorPagesOptions: options);
@ -734,19 +430,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
FilterDescriptors = new FilterDescriptor[0],
ViewEnginePath = "/Views/Deeper/Index.cshtml"
};
var loader = new Mock<IPageLoader>();
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
loader
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactory = new Mock<IRazorPageFactoryProvider>();
pageFactory.Setup(f => f.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
pageFactory
.Setup(f => f.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new IChangeToken[0]));
pageFactory.Setup(f => f.CreateFactory("/Views/_ViewStart.cshtml"))
pageFactory
.Setup(f => f.CreateFactory("/Views/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(new IChangeToken[0]));
pageFactory.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
pageFactory
.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new IChangeToken[0]));
// No files
@ -755,7 +453,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
CreateActionDescriptorCollection(descriptor),
pageProvider: null,
modelProvider: null,
razorPageFactoryProvider: pageFactory.Object,
@ -770,14 +468,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(2, factories.Count);
}
private IRazorPageFactoryProvider CreateRazorPageFactoryProvider()
{
var mock = new Mock<IRazorPageFactoryProvider>();
mock.Setup(p => p.CreateFactory(It.IsAny<string>()))
.Returns(new RazorPageFactoryResult(() => null, new List<IChangeToken>()));
return mock.Object;
}
private static CompiledPageActionDescriptor CreateCompiledPageActionDescriptor(
PageActionDescriptor descriptor,
Type pageType = null)
@ -805,7 +495,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RazorPagesOptions razorPagesOptions = null)
{
var tempDataFactory = new Mock<ITempDataDictionaryFactory>();
tempDataFactory.Setup(t => t.GetTempData(It.IsAny<HttpContext>()))
tempDataFactory
.Setup(t => t.GetTempData(It.IsAny<HttpContext>()))
.Returns((HttpContext context) => new TempDataDictionary(context, Mock.Of<ITempDataProvider>()));
if (razorProject == null)
@ -838,102 +529,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
NullLoggerFactory.Instance);
}
private class GenericClassModel
private IActionDescriptorCollectionProvider CreateActionDescriptorCollection(PageActionDescriptor descriptor)
{
public void OnGet<T>()
{
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
actionDescriptorProvider
.Setup(p => p.ActionDescriptors)
.Returns(descriptorCollection);
}
}
private class TestSetPageWithModel
{
public TestSetPageModel Model { get; set; }
}
private class InheritsMethods : TestSetPageModel
{
public new void OnGet()
{
}
}
private class PageModelWithStaticHandler
{
public static void OnGet(string name)
{
}
public void OnGet()
{
}
}
private abstract class PageModelWithAbstractMethod
{
public abstract void OnPost(string name);
public void OnGet()
{
}
}
private class PageModelWithFormActions
{
public void OnGet()
{
}
public void OnPostAdd()
{
}
public void OnPostAddCustomer()
{
}
public void OnPostDeleteAsync()
{
}
protected void OnPostDelete()
{
}
}
private class ProtectedModel
{
protected void OnGet()
{
}
private void OnPost()
{
}
}
private class TestSetPageModel
{
public void OnGet()
{
}
public void OnPost()
{
}
return actionDescriptorProvider.Object;
}
private class PageWithModel
@ -947,11 +551,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
}
}
private class PageWithBoundProperties
{
[ModelBinder]
public string Id { get; set; }
}
}
}

View File

@ -353,6 +353,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
null,
null,
null,
null,
new FilterItem[0]);
var invoker = CreateInvoker(
new[] { filter1.Object, filter2.Object, filter3.Object },
@ -408,6 +409,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
null,
null,
null,
null,
new FilterItem[0]);
var invoker = CreateInvoker(
new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object },
@ -521,6 +523,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ViewEnginePath = "/Index.cshtml",
RelativePath = "/Index.cshtml",
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
@ -607,6 +611,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
(c, model) => { (model as IDisposable)?.Dispose(); },
null,
null,
null,
new FilterItem[0]);
var invoker = new PageActionInvoker(

View File

@ -5,9 +5,11 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Moq;
using Xunit;
@ -149,20 +151,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public async Task ModelBinderFactory_BindsPropertiesOnPage()
{
// Arrange
var type = typeof(PageWithProperty).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
BoundProperties = new []
{
new PageBoundPropertyDescriptor()
{
Name = nameof(PageWithProperty.Id),
ParameterType = typeof(int),
Property = type.GetProperty(nameof(PageWithProperty.Id)),
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageWithProperty.RouteDifferentValue),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageWithProperty.RouteDifferentValue)),
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageWithProperty.PropertyWithNoValue),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageWithProperty.PropertyWithNoValue)),
}
},
HandlerTypeInfo = type,
PageTypeInfo = type,
};
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var binder = new TestParameterBinder(new Dictionary<string, object>
{
{ nameof(PageWithProperty.Id), 10 },
{ nameof(PageWithProperty.RouteDifferentValue), "route-value" }
});
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
PageContext = new PageContext()
{
HttpContext = new DefaultHttpContext(),
},
};
// Act
@ -172,56 +205,60 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(10, page.Id);
Assert.Equal("route-value", page.RouteDifferentValue);
Assert.Null(page.PropertyWithNoValue);
Assert.Collection(binder.Descriptors,
descriptor =>
{
Assert.Equal(nameof(PageWithProperty.Id), descriptor.Name);
Assert.Null(descriptor.BindingInfo.BinderModelName);
Assert.Equal(BindingSource.Query, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(int), descriptor.ParameterType);
},
descriptor =>
{
Assert.Equal(nameof(PageWithProperty.RouteDifferentValue), descriptor.Name);
Assert.Equal("route-value", descriptor.BindingInfo.BinderModelName);
Assert.Equal(BindingSource.Path, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(string), descriptor.ParameterType);
},
descriptor =>
{
Assert.Equal(nameof(PageWithProperty.PropertyWithNoValue), descriptor.Name);
Assert.Null(descriptor.BindingInfo.BinderModelName);
Assert.Equal(BindingSource.Form, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(string), descriptor.ParameterType);
});
}
[Fact]
public async Task ModelBinderFactory_BindsPropertiesOnPageModel()
{
// Arrange
var type = typeof(PageModelWithProperty).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
BoundProperties = new[]
{
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithProperty.Id),
ParameterType = typeof(int),
Property = type.GetProperty(nameof(PageModelWithProperty.Id)),
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithProperty.RouteDifferentValue),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithProperty.RouteDifferentValue)),
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithProperty.PropertyWithNoValue),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithProperty.PropertyWithNoValue)),
}
},
HandlerTypeInfo = typeof(PageModelWithProperty).GetTypeInfo(),
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = typeof(PageModelWithProperty).GetTypeInfo(),
};
var binder = new TestParameterBinder(new Dictionary<string, object>
{
{ nameof(PageModelWithProperty.Id), 10 },
{ nameof(PageModelWithProperty.RouteDifferentValue), "route-value" }
});
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
PageContext = new PageContext()
{
HttpContext = new DefaultHttpContext(),
}
};
var model = new PageModelWithProperty();
// Act
@ -235,123 +272,44 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(10, model.Id);
Assert.Equal("route-value", model.RouteDifferentValue);
Assert.Null(model.PropertyWithNoValue);
}
Assert.Collection(binder.Descriptors,
descriptor =>
[Fact]
public async Task ModelBinderFactory_PreservesExistingValueIfModelBindingFailed()
{
// Arrange
var type = typeof(PageModelWithDefaultValue).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
BoundProperties = new[]
{
Assert.Equal(nameof(PageModelWithProperty.Id), descriptor.Name);
Assert.Equal(BindingSource.Query, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(int), descriptor.ParameterType);
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithDefaultValue.PropertyWithDefaultValue),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithDefaultValue.PropertyWithDefaultValue)),
},
},
descriptor =>
{
Assert.Equal(nameof(PageModelWithProperty.RouteDifferentValue), descriptor.Name);
Assert.Equal("route-value", descriptor.BindingInfo.BinderModelName);
Assert.Equal(BindingSource.Path, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(string), descriptor.ParameterType);
},
descriptor =>
{
Assert.Equal(nameof(PageModelWithProperty.PropertyWithNoValue), descriptor.Name);
Assert.Null(descriptor.BindingInfo.BinderModelName);
Assert.Equal(BindingSource.Form, descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(string), descriptor.ParameterType);
});
}
[Fact]
public async Task ModelBinderFactory_DiscoversBinderType()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
HandlerTypeInfo = type,
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = typeof(PageModelWithModelBinderAttribute).GetTypeInfo(),
ModelTypeInfo = type,
};
var expected = Guid.NewGuid();
var binder = new TestParameterBinder(new Dictionary<string, object>
{
{ nameof(PageModelWithModelBinderAttribute.PropertyWithBinderType), expected },
});
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
};
var model = new PageModelWithModelBinderAttribute();
// Act
await factory(page, model);
// Assert
Assert.Equal(expected, model.PropertyWithBinderType);
Assert.Collection(binder.Descriptors,
descriptor =>
{
Assert.Equal(nameof(PageModelWithModelBinderAttribute.PropertyWithBinderType), descriptor.Name);
Assert.Equal(BindingSource.Custom, descriptor.BindingInfo.BindingSource);
Assert.Equal(typeof(DeclarativeSecurityAction), descriptor.BindingInfo.BinderType);
Assert.Null(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(Guid), descriptor.ParameterType);
});
}
[Fact]
public async Task ModelBinderFactory_DiscoversPropertyFilter()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = typeof(PageModelWithPropertyFilterAttribute).GetTypeInfo(),
};
var binder = new TestParameterBinder(new Dictionary<string, object>());
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
};
var model = new PageModelWithPropertyFilterAttribute();
// Act
await factory(page, model);
// Assert
Assert.Collection(binder.Descriptors,
descriptor =>
PageContext = new PageContext()
{
Assert.Equal(nameof(PageModelWithPropertyFilterAttribute.PropertyWithFilter), descriptor.Name);
Assert.Null(descriptor.BindingInfo.BindingSource);
Assert.Null(descriptor.BindingInfo.BinderType);
Assert.IsType<TestPropertyFilterProvider>(descriptor.BindingInfo.PropertyFilterProvider);
Assert.Equal(typeof(object), descriptor.ParameterType);
});
}
HttpContext = new DefaultHttpContext(),
}
};
[Fact]
public async Task ModelBinderFactory_UsesDefaultValueIfModelBindingFailed()
{
// Arrange
var actionDescriptor = new CompiledPageActionDescriptor
{
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = typeof(PageModelWithDefaultValue).GetTypeInfo(),
};
var binder = new TestParameterBinder(new Dictionary<string, object>());
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
};
var model = new PageModelWithDefaultValue();
var defaultValue = model.PropertyWithDefaultValue;
@ -362,34 +320,125 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(defaultValue, model.PropertyWithDefaultValue);
}
[Fact]
public async Task ModelBinderFactory_OverwritesDefaultValue()
[Theory]
[InlineData("Get")]
[InlineData("GET")]
[InlineData("gET")]
public async Task ModelBinderFactory_IgnoresPropertyWithoutSupportsGet_WhenRequestIsGet(string method)
{
// Arrange
var type = typeof(PageModelWithSupportsGetProperty).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
BoundProperties = new[]
{
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithSupportsGetProperty.SupportsGet),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithSupportsGetProperty.SupportsGet)),
SupportsGet = true,
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithSupportsGetProperty.Default),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithSupportsGetProperty.Default)),
},
},
HandlerTypeInfo = type,
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = typeof(PageModelWithDefaultValue).GetTypeInfo(),
ModelTypeInfo = type,
};
var expected = "not-default-value";
var binder = new TestParameterBinder(new Dictionary<string, object>
var binder = new TestParameterBinder(new Dictionary<string, object>()
{
{ nameof(PageModelWithDefaultValue.PropertyWithDefaultValue), expected },
{ "SupportsGet", "value" },
{ "Default", "ignored" },
});
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext(),
PageContext = new PageContext()
{
HttpContext = new DefaultHttpContext(),
}
};
var model = new PageModelWithDefaultValue();
var defaultValue = model.PropertyWithDefaultValue;
page.HttpContext.Request.Method = method;
var model = new PageModelWithSupportsGetProperty();
// Act
await factory(page, model);
// Assert
Assert.Equal(expected, model.PropertyWithDefaultValue);
Assert.Equal("value", model.SupportsGet);
Assert.Null(model.Default);
}
[Fact]
public async Task ModelBinderFactory_BindsPropertyWithoutSupportsGet_WhenRequestIsNotGet()
{
// Arrange
var type = typeof(PageModelWithSupportsGetProperty).GetTypeInfo();
var actionDescriptor = new CompiledPageActionDescriptor
{
BoundProperties = new[]
{
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithSupportsGetProperty.SupportsGet),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithSupportsGetProperty.SupportsGet)),
SupportsGet = true,
},
new PageBoundPropertyDescriptor()
{
Name = nameof(PageModelWithSupportsGetProperty.Default),
ParameterType = typeof(string),
Property = type.GetProperty(nameof(PageModelWithSupportsGetProperty.Default)),
},
},
HandlerTypeInfo = type,
PageTypeInfo = typeof(PageWithProperty).GetTypeInfo(),
ModelTypeInfo = type,
};
var binder = new TestParameterBinder(new Dictionary<string, object>()
{
{ "SupportsGet", "value" },
{ "Default", "value" },
});
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var factory = PagePropertyBinderFactory.CreateBinder(binder, modelMetadataProvider, actionDescriptor);
var page = new PageWithProperty
{
PageContext = new PageContext()
{
HttpContext = new DefaultHttpContext(),
}
};
page.HttpContext.Request.Method = "Post";
var model = new PageModelWithSupportsGetProperty();
// Act
await factory(page, model);
// Assert
Assert.Equal("value", model.SupportsGet);
Assert.Equal("value", model.Default);
}
private class TestParameterBinder : ParameterBinder
@ -518,5 +567,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
[ModelBinder]
public string PropertyWithDefaultValue { get; set; } = "Hello world";
}
private class PageModelWithSupportsGetProperty
{
[BindProperty(SupportsGet = true)]
public string SupportsGet { get; set; }
public string Default { get; set; }
}
}
}

View File

@ -0,0 +1,9 @@
@page
@using Microsoft.AspNetCore.Mvc.RazorPages
@functions
{
[BindProperty(SupportsGet=true)]
public int Value { get; set; }
}
<p>@Value</p>

View File

@ -13,5 +13,7 @@ namespace RazorPagesWebSite
[FromRoute]
public int Id { get; set; }
public void OnGet() { }
}
}