diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/BindPropertyAttribute.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/BindPropertyAttribute.cs
new file mode 100644
index 0000000000..9146b33ec6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/BindPropertyAttribute.cs
@@ -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; }
+
+ ///
+ public virtual BindingSource BindingSource
+ {
+ get
+ {
+ if (_bindingSource == null && BinderType != null)
+ {
+ return BindingSource.Custom;
+ }
+
+ return _bindingSource;
+ }
+ protected set
+ {
+ _bindingSource = value;
+ }
+ }
+
+ ///
+ public string Name { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
index feb6298f9b..8eecaa6772 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs
@@ -30,15 +30,25 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
///
- /// Gets or sets the of the page.
+ /// Gets the list of handler methods for the page.
///
- public TypeInfo PageTypeInfo { get; set; }
+ public IList HandlerMethods { get; set; }
+
+ ///
+ /// Gets or sets the of the type that defines handler methods for the page. This can be
+ /// the same as and if the page does not have an
+ /// explicit model type defined.
+ ///
+ public TypeInfo HandlerTypeInfo { get; set; }
///
/// Gets or sets the of the model.
///
public TypeInfo ModelTypeInfo { get; set; }
- public IList HandlerMethods { get; } = new List();
+ ///
+ /// Gets or sets the of the page.
+ ///
+ public TypeInfo PageTypeInfo { get; set; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs
index a4165252af..8291970a4a 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs
@@ -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> 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 Parameters { get; set; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs
index a11410eeb5..dcf212e077 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs
@@ -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; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs
new file mode 100644
index 0000000000..37f97138f4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs
@@ -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; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PagesBaseClassAttribute.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PagesBaseClassAttribute.cs
new file mode 100644
index 0000000000..d16e84c72d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PagesBaseClassAttribute.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ public class PagesBaseClassAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
index 3ef2339520..e2f07618ab 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs
@@ -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;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
index 6da72a0661..c031444c98 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs
@@ -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();
+
+ 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();
+
+ var results = new List();
+ 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();
+ 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();
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
index 8d99d701da..3b21dd7778 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs
@@ -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> CreateExecutor(
- CompiledPageActionDescriptor actionDescriptor,
- MethodInfo method,
- HandlerParameterDescriptor[] parameters)
+ public static Func> 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;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
index 104c53af00..d15507be68 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
@@ -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> 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 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;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs
index 6e148ce7b1..0894825fad 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs
@@ -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 modelFactory,
Action releaseModel,
Func propertyBinder,
+ Func>[] executors,
IReadOnlyList> 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
///
public Func PropertyBinder { get; }
+ public Func>[] Executors { get; }
+
///
/// Gets the applicable ViewStart pages.
///
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
index 6b4e12e057..a11933d492 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
@@ -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 modelFactory = null;
Action 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>[] 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>>();
}
- // Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
- if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object))
+ var results = new Func>[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
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs
index ab8f551848..15ec5e1684 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs
@@ -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 propertiesToBind)
+ IList properties,
+ IList 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 GetPropertiesToBind(
- IModelMetadataProvider modelMetadataProvider,
- TypeInfo handlerSourceTypeInfo)
- {
- var handlerType = handlerSourceTypeInfo.AsType();
- var properties = PropertyHelper.GetVisibleProperties(type: handlerType);
- var typeMetadata = modelMetadataProvider.GetMetadataForType(handlerType);
-
- var propertyBindingInfo = new List();
- 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 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; }
}
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs
new file mode 100644
index 0000000000..5c9cf53994
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs
@@ -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
+{
+ ///
+ /// Specifies that the targeted method is not a page handler method.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class NonHandlerAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
index a906de2141..f90b3365d4 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
@@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
///
/// A base class for a Razor page.
///
+ [PagesBaseClass]
public abstract class Page : RazorPageBase, IRazorPage
{
private IObjectModelValidator _objectValidator;
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
index 0438afc757..54ec258e24 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
@@ -22,6 +22,7 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
+ [PagesBaseClass]
public abstract class PageModel
{
private IObjectModelValidator _objectValidator;
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index bd705a348a..7624f5335a 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -716,6 +716,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.DoesNotContain(validationError, content);
}
+ [Fact]
+ public async Task PageProperty_WithSupportsGet_BoundInGet()
+ {
+ // Arrange
+ var expected = "11
";
+ 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()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs
index d5536d1209..a940e3b1bf 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs
@@ -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()
{
descriptor1,
descriptor2,
@@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
- HandlerMethods =
+ HandlerMethods = new List()
{
descriptor,
},
@@ -109,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
ActionDescriptor = new CompiledPageActionDescriptor
{
- HandlerMethods =
+ HandlerMethods = new List()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
descriptor1,
descriptor2,
@@ -468,7 +470,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Act & Assert
var ex = Assert.Throws(() => 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()
{
descriptor1,
descriptor2,
@@ -528,7 +530,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Act & Assert
var ex = Assert.Throws(() => 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;
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs
new file mode 100644
index 0000000000..4b27eafd17
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs
@@ -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(),
+ AttributeRouteInfo = new AttributeRouteInfo(),
+ FilterDescriptors = new List(),
+ RelativePath = "/Foo",
+ RouteValues = new Dictionary(),
+ 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 Html { get; private set; }
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary 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 Html { get; private set; }
+ public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary 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()
+ {
+ }
+ }
+
+ [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);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs
index fdd54c378b..f0339d2018 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs
@@ -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(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(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(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(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(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(() =>
- ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo, new HandlerParameterDescriptor[0]));
+ var ex = Assert.Throws(() => 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();
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
index 34a728e223..cd38a42cb9 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
@@ -36,27 +36,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
RelativePath = "Path1",
FilterDescriptors = new FilterDescriptor[0],
};
+
Func factory = _ => null;
Action releaser = (_, __) => { };
var loader = new Mock();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
+
var pageFactoryProvider = new Mock();
- pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
+ pageFactoryProvider
+ .Setup(f => f.CreatePageFactory(It.IsAny()))
.Returns(factory);
- pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
+ pageFactoryProvider
+ .Setup(f => f.CreatePageDisposer(It.IsAny()))
.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(context.Result);
var entry = actionInvoker.CacheEntry;
- var compiledPageActionDescriptor = Assert.IsType(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();
- loader.Setup(l => l.Load(It.IsAny()))
- .Returns(new CompiledPageActionDescriptor
- {
- PageTypeInfo = typeof(PageWithBoundProperties).GetTypeInfo(),
- });
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
- var pageFactoryProvider = Mock.Of();
-
- 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(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 factory = _ => null;
- Action releaser = (_, __) => { };
-
- var loader = new Mock();
- loader.Setup(l => l.Load(It.IsAny()))
- .Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestSetPageWithModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
- var pageFactoryProvider = new Mock();
- pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
- .Returns(factory);
- pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
- .Returns(releaser);
-
- var modelFactoryProvider = new Mock();
-
- 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(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 factory = _ => null;
Action releaser = (_, __) => { };
Func modelFactory = _ => null;
Action modelDisposer = (_, __) => { };
var loader = new Mock();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
+
var pageFactoryProvider = new Mock();
- pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
+ pageFactoryProvider
+ .Setup(f => f.CreatePageFactory(It.IsAny()))
.Returns(factory);
- pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
+ pageFactoryProvider
+ .Setup(f => f.CreatePageDisposer(It.IsAny()))
.Returns(releaser);
var modelFactoryProvider = new Mock();
- modelFactoryProvider.Setup(f => f.CreateModelFactory(It.IsAny()))
+ modelFactoryProvider
+ .Setup(f => f.CreateModelFactory(It.IsAny()))
.Returns(modelFactory);
- modelFactoryProvider.Setup(f => f.CreateModelDisposer(It.IsAny()))
+ modelFactoryProvider
+ .Setup(f => f.CreateModelDisposer(It.IsAny()))
.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();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
+
var razorPageFactoryProvider = new Mock();
+
Func factory1 = () => null;
Func 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() }));
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();
- loader.Setup(l => l.Load(It.IsAny()))
- .Returns(CreateCompiledPageActionDescriptor(descriptor, pageType: typeof(PageWithModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
- var razorPageFactoryProvider = new Mock();
- 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(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();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- 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();
- actionDescriptorProvider.SetupSequence(p => p.ActionDescriptors)
+ actionDescriptorProvider
+ .SetupSequence(p => p.ActionDescriptors)
.Returns(descriptorCollection1)
.Returns(descriptorCollection2);
var loader = new Mock();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.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();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- 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();
- mock.Setup(p => p.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
+ mock
+ .Setup(p => p.CreateFactory("/Views/Deeper/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List()))
.Verifiable();
- mock.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
+ mock
+ .Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List()))
.Verifiable();
- mock.Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
+ mock
+ .Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
.Returns(new RazorPageFactoryResult(() => null, new List()))
.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();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(compiledPageDescriptor);
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- 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(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()))
.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()))
.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();
- loader.Setup(l => l.Load(It.IsAny()))
+ loader
+ .Setup(l => l.Load(It.IsAny()))
.Returns(CreateCompiledPageActionDescriptor(descriptor, typeof(TestPageModel)));
- var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
- var actionDescriptorProvider = new Mock();
- actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
var pageFactory = new Mock();
- 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();
- mock.Setup(p => p.CreateFactory(It.IsAny()))
- .Returns(new RazorPageFactoryResult(() => null, new List()));
- 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();
- tempDataFactory.Setup(t => t.GetTempData(It.IsAny()))
+ tempDataFactory
+ .Setup(t => t.GetTempData(It.IsAny()))
.Returns((HttpContext context) => new TempDataDictionary(context, Mock.Of()));
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()
- {
+ var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
+ var actionDescriptorProvider = new Mock();
+ 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; }
- }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs
index 1273c54b63..8ea8d6a64b 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs
@@ -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(
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs
index 60e60a1985..618385a4f3 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs
@@ -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
{
{ 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
{
{ 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
- {
- { 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());
+
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(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());
- 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
+
+ var binder = new TestParameterBinder(new Dictionary()
{
- { 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()
+ {
+ { "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; }
+ }
}
}
diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml
new file mode 100644
index 0000000000..9f9ef25452
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml
@@ -0,0 +1,9 @@
+@page
+@using Microsoft.AspNetCore.Mvc.RazorPages
+
+@functions
+{
+ [BindProperty(SupportsGet=true)]
+ public int Value { get; set; }
+}
+@Value
\ No newline at end of file
diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs
index b482274be7..1d436d9e79 100644
--- a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs
+++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs
@@ -13,5 +13,7 @@ namespace RazorPagesWebSite
[FromRoute]
public int Id { get; set; }
+
+ public void OnGet() { }
}
}