* Cache ExecutorFactory as part of CompiledActionDescriptor
* Add tests for ExecutorFactory
This commit is contained in:
parent
498c1c570d
commit
de30c5822a
|
|
@ -170,38 +170,6 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
||||||
return GetString("BindingSource_Services");
|
return GetString("BindingSource_Services");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Special
|
|
||||||
/// </summary>
|
|
||||||
internal static string BindingSource_Special
|
|
||||||
{
|
|
||||||
get { return GetString("BindingSource_Special"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Special
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatBindingSource_Special()
|
|
||||||
{
|
|
||||||
return GetString("BindingSource_Special");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// FormFile
|
|
||||||
/// </summary>
|
|
||||||
internal static string BindingSource_FormFile
|
|
||||||
{
|
|
||||||
get { return GetString("BindingSource_FormFile"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// FormFile
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatBindingSource_FormFile()
|
|
||||||
{
|
|
||||||
return GetString("BindingSource_FormFile");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ModelBinding
|
/// ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -314,6 +282,38 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special
|
||||||
|
/// </summary>
|
||||||
|
internal static string BindingSource_Special
|
||||||
|
{
|
||||||
|
get { return GetString("BindingSource_Special"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatBindingSource_Special()
|
||||||
|
{
|
||||||
|
return GetString("BindingSource_Special");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FormFile
|
||||||
|
/// </summary>
|
||||||
|
internal static string BindingSource_FormFile
|
||||||
|
{
|
||||||
|
get { return GetString("BindingSource_FormFile"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FormFile
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatBindingSource_FormFile()
|
||||||
|
{
|
||||||
|
return GetString("BindingSource_FormFile");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
public class HandlerMethodDescriptor
|
public class HandlerMethodDescriptor
|
||||||
{
|
{
|
||||||
public MethodInfo Method { get; set; }
|
public MethodInfo Method { get; set; }
|
||||||
|
|
||||||
|
public Func<Page, object, Task<IActionResult>> Executor { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,29 +5,31 @@ using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public static class ExecutorFactory
|
public static class ExecutorFactory
|
||||||
{
|
{
|
||||||
public static Func<Page, object, Task<IActionResult>> Create(MethodInfo method)
|
public static Func<Page, object, Task<IActionResult>> CreateExecutor(
|
||||||
|
CompiledPageActionDescriptor actionDescriptor,
|
||||||
|
MethodInfo method)
|
||||||
{
|
{
|
||||||
return new Executor()
|
if (actionDescriptor == null)
|
||||||
{
|
{
|
||||||
Method = method,
|
throw new ArgumentNullException(nameof(actionDescriptor));
|
||||||
}.Execute;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private class Executor
|
if (method == null)
|
||||||
{
|
|
||||||
public MethodInfo Method { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> Execute(Page page, object model)
|
|
||||||
{
|
{
|
||||||
var handler = HandlerMethod.Create(Method);
|
throw new ArgumentNullException(nameof(method));
|
||||||
|
}
|
||||||
|
|
||||||
var receiver = Method.DeclaringType.IsAssignableFrom(page.GetType()) ? page : model;
|
var methodIsDeclaredOnPage = method.DeclaringType.GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo);
|
||||||
|
var handler = CreateHandlerMethod(method);
|
||||||
|
|
||||||
|
return async (page, model) =>
|
||||||
|
{
|
||||||
var arguments = new object[handler.Parameters.Length];
|
var arguments = new object[handler.Parameters.Length];
|
||||||
for (var i = 0; i < handler.Parameters.Length; i++)
|
for (var i = 0; i < handler.Parameters.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -39,68 +41,69 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
parameter.Name);
|
parameter.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var receiver = methodIsDeclaredOnPage ? page : model;
|
||||||
var result = await handler.Execute(receiver, arguments);
|
var result = await handler.Execute(receiver, arguments);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HandlerParameter
|
private static HandlerMethod CreateHandlerMethod(MethodInfo method)
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
var methodParameters = method.GetParameters();
|
||||||
|
var parameters = new HandlerParameter[methodParameters.Length];
|
||||||
|
|
||||||
public Type Type { get; set; }
|
for (var i = 0; i < methodParameters.Length; i++)
|
||||||
|
{
|
||||||
|
var methodParameter = methodParameters[i];
|
||||||
|
object defaultValue = null;
|
||||||
|
if (methodParameter.HasDefaultValue)
|
||||||
|
{
|
||||||
|
defaultValue = methodParameter.DefaultValue;
|
||||||
|
}
|
||||||
|
else if (methodParameter.ParameterType.GetTypeInfo().IsValueType)
|
||||||
|
{
|
||||||
|
defaultValue = Activator.CreateInstance(methodParameter.ParameterType);
|
||||||
|
}
|
||||||
|
|
||||||
public object DefaultValue { get; set; }
|
parameters[i] = new HandlerParameter(methodParameter.Name, methodParameter.ParameterType, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnType = method.ReturnType;
|
||||||
|
var returnTypeInfo = method.ReturnType.GetTypeInfo();
|
||||||
|
if (returnType == typeof(void))
|
||||||
|
{
|
||||||
|
return new VoidHandlerMethod(parameters, method);
|
||||||
|
}
|
||||||
|
else if (typeof(IActionResult).IsAssignableFrom(returnType))
|
||||||
|
{
|
||||||
|
return new ActionResultHandlerMethod(parameters, method);
|
||||||
|
}
|
||||||
|
else if (returnType == typeof(Task))
|
||||||
|
{
|
||||||
|
return new NonGenericTaskHandlerMethod(parameters, method);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var taskType = ClosedGenericMatcher.ExtractGenericInterface(returnType, typeof(Task<>));
|
||||||
|
if (taskType != null && typeof(IActionResult).IsAssignableFrom(taskType.GenericTypeArguments[0]))
|
||||||
|
{
|
||||||
|
return new GenericTaskHandlerMethod(parameters, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(Resources.FormatUnsupportedHandlerMethodType(returnType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class HandlerMethod
|
private abstract class HandlerMethod
|
||||||
{
|
{
|
||||||
public static HandlerMethod Create(MethodInfo method)
|
|
||||||
{
|
|
||||||
var methodParameters = method.GetParameters();
|
|
||||||
var parameters = new HandlerParameter[methodParameters.Length];
|
|
||||||
|
|
||||||
for (var i = 0; i < methodParameters.Length; i++)
|
|
||||||
{
|
|
||||||
parameters[i] = new HandlerParameter()
|
|
||||||
{
|
|
||||||
DefaultValue = methodParameters[i].HasDefaultValue ? methodParameters[i].DefaultValue : null,
|
|
||||||
Name = methodParameters[i].Name,
|
|
||||||
Type = methodParameters[i].ParameterType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.ReturnType == typeof(Task))
|
|
||||||
{
|
|
||||||
return new NonGenericTaskHandlerMethod(parameters, method);
|
|
||||||
}
|
|
||||||
else if (method.ReturnType == typeof(void))
|
|
||||||
{
|
|
||||||
return new VoidHandlerMethod(parameters, method);
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
method.ReturnType.IsConstructedGenericType &&
|
|
||||||
method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) &&
|
|
||||||
typeof(IActionResult).IsAssignableFrom(method.ReturnType.GetTypeInfo().GetGenericArguments()[0]))
|
|
||||||
{
|
|
||||||
return new GenericTaskHandlerMethod(parameters, method);
|
|
||||||
}
|
|
||||||
else if (typeof(IActionResult).IsAssignableFrom(method.ReturnType))
|
|
||||||
{
|
|
||||||
return new ActionResultHandlerMethod(parameters, method);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("unsupported handler method return type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Expression[] Unpack(Expression arguments, HandlerParameter[] parameters)
|
protected static Expression[] Unpack(Expression arguments, HandlerParameter[] parameters)
|
||||||
{
|
{
|
||||||
var unpackExpressions = new Expression[parameters.Length];
|
var unpackExpressions = new Expression[parameters.Length];
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
unpackExpressions[i] = Expression.Convert(Expression.ArrayIndex(arguments, Expression.Constant(i)), parameters[i].Type);
|
unpackExpressions[i] = Expression.Convert(
|
||||||
|
Expression.ArrayIndex(arguments, Expression.Constant(i)),
|
||||||
|
parameters[i].Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return unpackExpressions;
|
return unpackExpressions;
|
||||||
|
|
@ -178,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
private static async Task<object> Convert<T>(object taskAsObject)
|
private static async Task<object> Convert<T>(object taskAsObject)
|
||||||
{
|
{
|
||||||
var task = (Task<T>)taskAsObject;
|
var task = (Task<T>)taskAsObject;
|
||||||
return (object)await task;
|
return await task;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,5 +237,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return Task.FromResult(_thunk(receiver, arguments));
|
return Task.FromResult(_thunk(receiver, arguments));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct HandlerParameter
|
||||||
|
{
|
||||||
|
public HandlerParameter(string name, Type type, object defaultValue)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public Type Type { get; }
|
||||||
|
|
||||||
|
public object DefaultValue { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
var handler = _selector.Select(_pageContext);
|
var handler = _selector.Select(_pageContext);
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var executor = ExecutorFactory.Create(handler.Method);
|
var executor = handler.Executor;
|
||||||
result = await executor(_page, _model);
|
result = await executor(_page, _model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor()
|
actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor()
|
||||||
{
|
{
|
||||||
Method = method,
|
Method = method,
|
||||||
|
Executor = ExecutorFactory.CreateExecutor(actionDescriptor, method),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
return GetString("ArgumentCannotBeNullOrEmpty");
|
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported handler method type '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string UnsupportedHandlerMethodType
|
||||||
|
{
|
||||||
|
get { return GetString("UnsupportedHandlerMethodType"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported handler method type '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatUnsupportedHandlerMethodType(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -132,4 +132,7 @@
|
||||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
<value>Value cannot be null or empty.</value>
|
<value>Value cannot be null or empty.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UnsupportedHandlerMethodType" xml:space="preserve">
|
||||||
|
<value>Unsupported handler method return type '{0}'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -33,6 +33,10 @@
|
||||||
"version": "1.2.0-*",
|
"version": "1.2.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.ClosedGenericMatcher.Sources": {
|
||||||
|
"version": "1.2.0-*",
|
||||||
|
"type": "build"
|
||||||
|
},
|
||||||
"NETStandard.Library": "1.6.1"
|
"NETStandard.Library": "1.6.1"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,454 @@
|
||||||
|
// 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 System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Test.Internal
|
||||||
|
{
|
||||||
|
public class ExecutorFactoryTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForActionResultMethod_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ActionResultReturningHandler));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new TestPage(), null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<EmptyResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForMethodReturningConcreteSubtypeOfIActionResult_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ConcreteActionResult));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new TestPage(), null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<ViewResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForActionResultReturningMethod_WithParameters_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.ActionResultReturnHandlerWithParameters));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new TestPage(), null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
var contentResult = Assert.IsType<ContentResult>(actionResult);
|
||||||
|
Assert.Equal("Hello 0", contentResult.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForVoidReturningMethod_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var page = new TestPage();
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidReturningHandler));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(page, null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.Null(actionResult);
|
||||||
|
Assert.True(page.SideEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForVoidTaskReturningMethod_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var page = new TestPage();
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.VoidTaskReturningHandler));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(page, null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.Null(actionResult);
|
||||||
|
Assert.True(page.SideEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForTaskOfIActionResultReturningMethod_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.GenericTaskHandler));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new TestPage(), null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<EmptyResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForTaskOfConcreteActionResultReturningMethod_OnPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPage).GetMethod(nameof(TestPage.TaskReturningConcreteSubtype));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new TestPage(), null);
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
var contentResult = Assert.IsType<ContentResult>(actionResult);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), new TestPageModel());
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<EmptyResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForMethodReturningConcreteSubtypeOfIActionResult_OnPageModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.ConcreteActionResult));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), new TestPageModel());
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<ViewResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForActionResultReturningMethod_WithParameters_OnPageModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.ActionResultReturnHandlerWithParameters));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), new TestPageModel());
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
var contentResult = Assert.IsType<ContentResult>(actionResult);
|
||||||
|
Assert.Equal("Hello 0", contentResult.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForVoidReturningMethod_OnPageModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var model = new TestPageModel();
|
||||||
|
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.VoidReturningHandler));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), model);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), model);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), new TestPageModel());
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
Assert.IsType<EmptyResult>(actionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateExecutor_ForTaskOfConcreteActionResultReturningMethod_OnPageModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = new CompiledPageActionDescriptor
|
||||||
|
{
|
||||||
|
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
|
||||||
|
ModelTypeInfo = typeof(PageModel).GetTypeInfo(),
|
||||||
|
};
|
||||||
|
var methodInfo = typeof(TestPageModel).GetMethod(nameof(TestPageModel.TaskReturningConcreteSubtype));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var executor = ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(executor);
|
||||||
|
var actionResultTask = executor(new EmptyPage(), new TestPageModel());
|
||||||
|
var actionResult = await actionResultTask;
|
||||||
|
var contentResult = Assert.IsType<ContentResult>(actionResult);
|
||||||
|
Assert.Equal("value", contentResult.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(nameof(TestPageModel.StringResult))]
|
||||||
|
[InlineData(nameof(TestPageModel.TaskOfObject))]
|
||||||
|
[InlineData(nameof(TestPageModel.ViewComponent))]
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => ExecutorFactory.CreateExecutor(actionDescriptor, methodInfo));
|
||||||
|
Assert.Equal($"Unsupported handler method return type '{methodInfo.ReturnType}'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPage : Page
|
||||||
|
{
|
||||||
|
public TestPage()
|
||||||
|
{
|
||||||
|
Binder = new MockBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SideEffects { get; private set; }
|
||||||
|
|
||||||
|
public IActionResult ActionResultReturningHandler() => new EmptyResult();
|
||||||
|
|
||||||
|
public IActionResult ActionResultReturnHandlerWithParameters(int arg1, string arg2 = "Hello")
|
||||||
|
{
|
||||||
|
return new ContentResult
|
||||||
|
{
|
||||||
|
Content = $"{arg2} {arg1}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ConcreteActionResult() => new ViewResult();
|
||||||
|
|
||||||
|
public void VoidReturningHandler()
|
||||||
|
{
|
||||||
|
SideEffects = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task VoidTaskReturningHandler()
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
SideEffects = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IActionResult> GenericTaskHandler() => Task.FromResult<IActionResult>(new EmptyResult());
|
||||||
|
|
||||||
|
public Task<ContentResult> TaskReturningConcreteSubtype(string arg = "value")
|
||||||
|
{
|
||||||
|
return Task.FromResult(new ContentResult
|
||||||
|
{
|
||||||
|
Content = arg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPageModel
|
||||||
|
{
|
||||||
|
public bool SideEffects { get; private set; }
|
||||||
|
|
||||||
|
public IActionResult ActionResultReturningHandler() => new EmptyResult();
|
||||||
|
|
||||||
|
public IActionResult ActionResultReturnHandlerWithParameters(int arg1, string arg2 = "Hello")
|
||||||
|
{
|
||||||
|
return new ContentResult
|
||||||
|
{
|
||||||
|
Content = $"{arg2} {arg1}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewResult ConcreteActionResult() => new ViewResult();
|
||||||
|
|
||||||
|
public void VoidReturningHandler()
|
||||||
|
{
|
||||||
|
SideEffects = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task VoidTaskReturningHandler()
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
SideEffects = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IActionResult> GenericTaskHandler() => Task.FromResult<IActionResult>(new EmptyResult());
|
||||||
|
|
||||||
|
public Task<ContentResult> TaskReturningConcreteSubtype(string arg = "value")
|
||||||
|
{
|
||||||
|
return Task.FromResult(new ContentResult
|
||||||
|
{
|
||||||
|
Content = arg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StringResult() => "";
|
||||||
|
|
||||||
|
public Task<object> TaskOfObject() => Task.FromResult(new object());
|
||||||
|
|
||||||
|
public IViewComponentResult ViewComponent() => new ViewViewComponentResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EmptyPage : Page
|
||||||
|
{
|
||||||
|
public EmptyPage()
|
||||||
|
{
|
||||||
|
Binder = new MockBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockBinder : PageArgumentBinder
|
||||||
|
{
|
||||||
|
protected override Task<ModelBindingResult> BindAsync(PageContext context, object value, string name, Type type)
|
||||||
|
{
|
||||||
|
var result = ModelBindingResult.Failed();
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -171,6 +171,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
Assert.Equal(new[] { factory2, factory1 }, entry.PageStartFactories);
|
Assert.Equal(new[] { factory2, factory1 }, entry.PageStartFactories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_CachesExecutor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptor = new PageActionDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = "/Home/Path1/File.cshtml",
|
||||||
|
ViewEnginePath = "/Home/Path1/File.cshtml",
|
||||||
|
FilterDescriptors = new FilterDescriptor[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
var loader = new Mock<IPageLoader>();
|
||||||
|
loader.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||||
|
.Returns(typeof(PageWithModel));
|
||||||
|
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
|
||||||
|
var actionDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||||
|
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||||
|
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var defaultRazorProject = new DefaultRazorProject(fileProvider);
|
||||||
|
|
||||||
|
var invokerProvider = CreateInvokerProvider(
|
||||||
|
loader.Object,
|
||||||
|
actionDescriptorProvider.Object,
|
||||||
|
razorPageFactoryProvider: razorPageFactoryProvider.Object,
|
||||||
|
razorProject: defaultRazorProject);
|
||||||
|
var context = new ActionInvokerProviderContext(
|
||||||
|
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
invokerProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(context.Result);
|
||||||
|
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
|
||||||
|
var actionDescriptor = actionInvoker.CacheEntry.ActionDescriptor;
|
||||||
|
Assert.Collection(actionDescriptor.HandlerMethods,
|
||||||
|
handlerDescriptor =>
|
||||||
|
{
|
||||||
|
Assert.Equal(nameof(TestPageModel.OnGet), handlerDescriptor.Method.Name);
|
||||||
|
Assert.NotNull(handlerDescriptor.Executor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnProvidersExecuting_CachesEntries()
|
public void OnProvidersExecuting_CachesEntries()
|
||||||
{
|
{
|
||||||
|
|
@ -291,7 +336,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
private class PageWithModel
|
private class PageWithModel
|
||||||
{
|
{
|
||||||
public object Model { get; set; }
|
public TestPageModel Model { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue