parent
4f709bdbfd
commit
007c47d065
|
|
@ -129,7 +129,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
|
||||||
// These are stateless
|
// These are stateless
|
||||||
services.TryAddSingleton<IControllerActionArgumentBinder, ControllerArgumentBinder>();
|
services.TryAddSingleton<IControllerActionArgumentBinder, ControllerArgumentBinder>();
|
||||||
services.TryAddSingleton<FilterCache>();
|
services.TryAddSingleton<ControllerActionInvokerCache>();
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,36 +26,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<object> ExecuteAsync(
|
public static Task<object> ExecuteAsync(
|
||||||
MethodInfo actionMethodInfo,
|
ObjectMethodExecutor actionMethodExecutor,
|
||||||
object instance,
|
object instance,
|
||||||
IDictionary<string, object> actionArguments)
|
IDictionary<string, object> actionArguments)
|
||||||
{
|
{
|
||||||
var orderedArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
|
var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor.MethodInfo.GetParameters());
|
||||||
return ExecuteAsync(actionMethodInfo, instance, orderedArguments);
|
return ExecuteAsync(actionMethodExecutor, instance, orderedArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<object> ExecuteAsync(
|
public static Task<object> ExecuteAsync(
|
||||||
MethodInfo actionMethodInfo,
|
ObjectMethodExecutor actionMethodExecutor,
|
||||||
object instance,
|
object instance,
|
||||||
object[] orderedActionArguments)
|
object[] orderedActionArguments)
|
||||||
{
|
{
|
||||||
object invocationResult = null;
|
object invocationResult = actionMethodExecutor.Execute(instance, orderedActionArguments);
|
||||||
try
|
|
||||||
{
|
|
||||||
invocationResult = actionMethodInfo.Invoke(instance, orderedActionArguments);
|
|
||||||
}
|
|
||||||
catch (TargetInvocationException targetInvocationException)
|
|
||||||
{
|
|
||||||
// Capturing the exception and the original callstack and rethrow for external exception handlers.
|
|
||||||
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(targetInvocationException.InnerException);
|
|
||||||
exceptionDispatchInfo.Throw();
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoerceResultToTaskAsync(
|
return CoerceResultToTaskAsync(
|
||||||
invocationResult,
|
invocationResult,
|
||||||
actionMethodInfo.ReturnType,
|
actionMethodExecutor.MethodInfo.ReturnType,
|
||||||
actionMethodInfo.Name,
|
actionMethodExecutor.MethodInfo.Name,
|
||||||
actionMethodInfo.DeclaringType);
|
actionMethodExecutor.MethodInfo.DeclaringType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>.
|
// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public ControllerActionInvoker(
|
public ControllerActionInvoker(
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
FilterCache filterCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IControllerFactory controllerFactory,
|
IControllerFactory controllerFactory,
|
||||||
ControllerActionDescriptor descriptor,
|
ControllerActionDescriptor descriptor,
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
int maxModelValidationErrors)
|
int maxModelValidationErrors)
|
||||||
: base(
|
: base(
|
||||||
actionContext,
|
actionContext,
|
||||||
filterCache,
|
controllerActionInvokerCache,
|
||||||
inputFormatters,
|
inputFormatters,
|
||||||
modelBinders,
|
modelBinders,
|
||||||
modelValidatorProviders,
|
modelValidatorProviders,
|
||||||
|
|
@ -96,6 +96,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionMethodInfo = _descriptor.MethodInfo;
|
var actionMethodInfo = _descriptor.MethodInfo;
|
||||||
|
|
||||||
|
var methodExecutor = GetControllerActionMethodExecutor();
|
||||||
|
|
||||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||||
actionExecutingContext.ActionArguments,
|
actionExecutingContext.ActionArguments,
|
||||||
actionMethodInfo.GetParameters());
|
actionMethodInfo.GetParameters());
|
||||||
|
|
@ -103,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Logger.ActionMethodExecuting(actionExecutingContext, arguments);
|
Logger.ActionMethodExecuting(actionExecutingContext, arguments);
|
||||||
|
|
||||||
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
|
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
|
||||||
actionMethodInfo,
|
methodExecutor,
|
||||||
actionExecutingContext.Controller,
|
actionExecutingContext.Controller,
|
||||||
arguments);
|
arguments);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class FilterCache
|
public class ControllerActionInvokerCache
|
||||||
{
|
{
|
||||||
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
|
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private volatile InnerCache _currentCache;
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
public FilterCache(
|
public ControllerActionInvokerCache(
|
||||||
IActionDescriptorCollectionProvider collectionProvider,
|
IActionDescriptorCollectionProvider collectionProvider,
|
||||||
IEnumerable<IFilterProvider> filterProviders)
|
IEnumerable<IFilterProvider> filterProviders)
|
||||||
{
|
{
|
||||||
|
|
@ -45,24 +45,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFilterMetadata[] GetFilters(ActionContext actionContext)
|
public Entry GetCacheEntry(ControllerContext controllerContext)
|
||||||
{
|
{
|
||||||
var cache = CurrentCache;
|
var cache = CurrentCache;
|
||||||
var actionDescriptor = actionContext.ActionDescriptor;
|
var actionDescriptor = controllerContext.ActionDescriptor;
|
||||||
|
|
||||||
CacheEntry entry;
|
Entry entry;
|
||||||
if (cache.Entries.TryGetValue(actionDescriptor, out entry))
|
if (cache.Entries.TryGetValue(actionDescriptor, out entry))
|
||||||
{
|
{
|
||||||
return GetFiltersFromEntry(entry, actionContext);
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var executor = ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo);
|
||||||
|
|
||||||
var items = new List<FilterItem>(actionDescriptor.FilterDescriptors.Count);
|
var items = new List<FilterItem>(actionDescriptor.FilterDescriptors.Count);
|
||||||
for (var i = 0; i < actionDescriptor.FilterDescriptors.Count; i++)
|
for (var i = 0; i < actionDescriptor.FilterDescriptors.Count; i++)
|
||||||
{
|
{
|
||||||
items.Add(new FilterItem(actionDescriptor.FilterDescriptors[i]));
|
items.Add(new FilterItem(actionDescriptor.FilterDescriptors[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
ExecuteProviders(actionContext, items);
|
ExecuteProviders(controllerContext, items);
|
||||||
|
|
||||||
var filters = ExtractFilters(items);
|
var filters = ExtractFilters(items);
|
||||||
|
|
||||||
|
|
@ -79,30 +81,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (allFiltersCached)
|
if (allFiltersCached)
|
||||||
{
|
{
|
||||||
entry = new CacheEntry(filters);
|
entry = new Entry(filters, null, executor);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entry = new CacheEntry(items);
|
entry = new Entry(null, items, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Entries.TryAdd(actionDescriptor, entry);
|
cache.Entries.TryAdd(actionDescriptor, entry);
|
||||||
return filters;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IFilterMetadata[] GetFiltersFromEntry(CacheEntry entry, ActionContext actionContext)
|
public IFilterMetadata[] GetFilters(ControllerContext controllerContext)
|
||||||
{
|
{
|
||||||
Debug.Assert(entry.Filters != null || entry.Items != null);
|
var entry = GetCacheEntry(controllerContext);
|
||||||
|
return GetFiltersFromEntry(entry, controllerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectMethodExecutor GetControllerActionMethodExecutor(ControllerContext controllerContext)
|
||||||
|
{
|
||||||
|
var entry = GetCacheEntry(controllerContext);
|
||||||
|
return entry.ActionMethodExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFilterMetadata[] GetFiltersFromEntry(Entry entry, ActionContext actionContext)
|
||||||
|
{
|
||||||
|
Debug.Assert(entry.Filters != null || entry.FilterItems != null);
|
||||||
|
|
||||||
if (entry.Filters != null)
|
if (entry.Filters != null)
|
||||||
{
|
{
|
||||||
return entry.Filters;
|
return entry.Filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = new List<FilterItem>(entry.Items.Count);
|
var items = new List<FilterItem>(entry.FilterItems.Count);
|
||||||
for (var i = 0; i < entry.Items.Count; i++)
|
for (var i = 0; i < entry.FilterItems.Count; i++)
|
||||||
{
|
{
|
||||||
var item = entry.Items[i];
|
var item = entry.FilterItems[i];
|
||||||
if (item.IsReusable)
|
if (item.IsReusable)
|
||||||
{
|
{
|
||||||
items.Add(item);
|
items.Add(item);
|
||||||
|
|
@ -172,29 +186,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Version = version;
|
Version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConcurrentDictionary<ActionDescriptor, CacheEntry> Entries { get; } =
|
public ConcurrentDictionary<ActionDescriptor, Entry> Entries { get; } =
|
||||||
new ConcurrentDictionary<ActionDescriptor, CacheEntry>();
|
new ConcurrentDictionary<ActionDescriptor, Entry>();
|
||||||
|
|
||||||
public int Version { get; }
|
public int Version { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CacheEntry
|
public struct Entry
|
||||||
{
|
{
|
||||||
public CacheEntry(IFilterMetadata[] filters)
|
public Entry(IFilterMetadata[] filters, List<FilterItem> items, ObjectMethodExecutor executor)
|
||||||
{
|
{
|
||||||
|
FilterItems = items;
|
||||||
Filters = filters;
|
Filters = filters;
|
||||||
Items = null;
|
ActionMethodExecutor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CacheEntry(List<FilterItem> items)
|
|
||||||
{
|
|
||||||
Items = items;
|
|
||||||
Filters = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFilterMetadata[] Filters { get; }
|
public IFilterMetadata[] Filters { get; }
|
||||||
|
|
||||||
public List<FilterItem> Items { get; }
|
public List<FilterItem> FilterItems { get; }
|
||||||
|
|
||||||
|
public ObjectMethodExecutor ActionMethodExecutor { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
private readonly IControllerActionArgumentBinder _argumentBinder;
|
private readonly IControllerActionArgumentBinder _argumentBinder;
|
||||||
private readonly IControllerFactory _controllerFactory;
|
private readonly IControllerFactory _controllerFactory;
|
||||||
private readonly FilterCache _filterCache;
|
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||||
|
|
@ -30,14 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public ControllerActionInvokerProvider(
|
public ControllerActionInvokerProvider(
|
||||||
IControllerFactory controllerFactory,
|
IControllerFactory controllerFactory,
|
||||||
FilterCache filterCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IControllerActionArgumentBinder argumentBinder,
|
IControllerActionArgumentBinder argumentBinder,
|
||||||
IOptions<MvcOptions> optionsAccessor,
|
IOptions<MvcOptions> optionsAccessor,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
DiagnosticSource diagnosticSource)
|
DiagnosticSource diagnosticSource)
|
||||||
{
|
{
|
||||||
_controllerFactory = controllerFactory;
|
_controllerFactory = controllerFactory;
|
||||||
_filterCache = filterCache;
|
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||||
_argumentBinder = argumentBinder;
|
_argumentBinder = argumentBinder;
|
||||||
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
||||||
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
|
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
context.Result = new ControllerActionInvoker(
|
context.Result = new ControllerActionInvoker(
|
||||||
context.ActionContext,
|
context.ActionContext,
|
||||||
_filterCache,
|
_controllerActionInvokerCache,
|
||||||
_controllerFactory,
|
_controllerFactory,
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
_inputFormatters,
|
_inputFormatters,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public abstract class FilterActionInvoker : IActionInvoker
|
public abstract class FilterActionInvoker : IActionInvoker
|
||||||
{
|
{
|
||||||
private readonly FilterCache _filterCache;
|
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private readonly int _maxModelValidationErrors;
|
private readonly int _maxModelValidationErrors;
|
||||||
|
|
||||||
private IFilterMetadata[] _filters;
|
private IFilterMetadata[] _filters;
|
||||||
|
private ObjectMethodExecutor _controllerActionMethodExecutor;
|
||||||
private FilterCursor _cursor;
|
private FilterCursor _cursor;
|
||||||
|
|
||||||
private AuthorizationFilterContext _authorizationContext;
|
private AuthorizationFilterContext _authorizationContext;
|
||||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public FilterActionInvoker(
|
public FilterActionInvoker(
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
FilterCache filterCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||||
IReadOnlyList<IModelBinder> modelBinders,
|
IReadOnlyList<IModelBinder> modelBinders,
|
||||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||||
|
|
@ -58,9 +59,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
throw new ArgumentNullException(nameof(actionContext));
|
throw new ArgumentNullException(nameof(actionContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterCache == null)
|
if (controllerActionInvokerCache == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(filterCache));
|
throw new ArgumentNullException(nameof(controllerActionInvokerCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputFormatters == null)
|
if (inputFormatters == null)
|
||||||
|
|
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
Context = new ControllerContext(actionContext);
|
Context = new ControllerContext(actionContext);
|
||||||
|
|
||||||
_filterCache = filterCache;
|
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||||
_inputFormatters = inputFormatters;
|
_inputFormatters = inputFormatters;
|
||||||
_modelBinders = modelBinders;
|
_modelBinders = modelBinders;
|
||||||
_modelValidatorProviders = modelValidatorProviders;
|
_modelValidatorProviders = modelValidatorProviders;
|
||||||
|
|
@ -130,7 +131,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public virtual async Task InvokeAsync()
|
public virtual async Task InvokeAsync()
|
||||||
{
|
{
|
||||||
_filters = GetFilters();
|
var cacheEntry = _controllerActionInvokerCache.GetCacheEntry(Context);
|
||||||
|
_filters = _controllerActionInvokerCache.GetFiltersFromEntry(cacheEntry, Context);
|
||||||
|
_controllerActionMethodExecutor = cacheEntry.ActionMethodExecutor;
|
||||||
_cursor = new FilterCursor(_filters);
|
_cursor = new FilterCursor(_filters);
|
||||||
|
|
||||||
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
|
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
|
||||||
|
|
@ -177,9 +180,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IFilterMetadata[] GetFilters()
|
protected ObjectMethodExecutor GetControllerActionMethodExecutor()
|
||||||
{
|
{
|
||||||
return _filterCache.GetFilters(Context);
|
return _controllerActionMethodExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task InvokeAllAuthorizationFiltersAsync()
|
private Task InvokeAllAuthorizationFiltersAsync()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
// 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.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
public class ObjectMethodExecutor
|
||||||
|
{
|
||||||
|
private ActionExecutor _executor;
|
||||||
|
|
||||||
|
private ObjectMethodExecutor(MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
if (methodInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(methodInfo));
|
||||||
|
}
|
||||||
|
MethodInfo = methodInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate object ActionExecutor(object target, object[] parameters);
|
||||||
|
|
||||||
|
private delegate void VoidActionExecutor(object target, object[] parameters);
|
||||||
|
|
||||||
|
public MethodInfo MethodInfo { get; }
|
||||||
|
|
||||||
|
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||||
|
{
|
||||||
|
var executor = new ObjectMethodExecutor(methodInfo);
|
||||||
|
executor._executor = GetExecutor(methodInfo, targetTypeInfo);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Execute(object target, object[] parameters)
|
||||||
|
{
|
||||||
|
return _executor(target, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||||
|
{
|
||||||
|
// Parameters to executor
|
||||||
|
var targetParameter = Expression.Parameter(typeof(object), "target");
|
||||||
|
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
|
||||||
|
|
||||||
|
// Build parameter list
|
||||||
|
var parameters = new List<Expression>();
|
||||||
|
var paramInfos = methodInfo.GetParameters();
|
||||||
|
for (int i = 0; i < paramInfos.Length; i++)
|
||||||
|
{
|
||||||
|
var paramInfo = paramInfos[i];
|
||||||
|
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
|
||||||
|
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
|
||||||
|
|
||||||
|
// valueCast is "(Ti) parameters[i]"
|
||||||
|
parameters.Add(valueCast);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call method
|
||||||
|
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
|
||||||
|
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
|
||||||
|
|
||||||
|
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
|
||||||
|
// Create function
|
||||||
|
if (methodCall.Type == typeof(void))
|
||||||
|
{
|
||||||
|
var lambda = Expression.Lambda<VoidActionExecutor>(methodCall, targetParameter, parametersParameter);
|
||||||
|
var voidExecutor = lambda.Compile();
|
||||||
|
return WrapVoidAction(voidExecutor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// must coerce methodCall to match ActionExecutor signature
|
||||||
|
var castMethodCall = Expression.Convert(methodCall, typeof(object));
|
||||||
|
var lambda = Expression.Lambda<ActionExecutor>(castMethodCall, targetParameter, parametersParameter);
|
||||||
|
return lambda.Compile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionExecutor WrapVoidAction(VoidActionExecutor executor)
|
||||||
|
{
|
||||||
|
return delegate (object target, object[] parameters)
|
||||||
|
{
|
||||||
|
executor(target, parameters);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -127,6 +127,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
IViewComponentDescriptorCollectionProvider,
|
IViewComponentDescriptorCollectionProvider,
|
||||||
DefaultViewComponentDescriptorCollectionProvider>();
|
DefaultViewComponentDescriptorCollectionProvider>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<ViewComponentInvokerCache>();
|
||||||
services.TryAddTransient<IViewComponentDescriptorProvider, DefaultViewComponentDescriptorProvider>();
|
services.TryAddTransient<IViewComponentDescriptorProvider, DefaultViewComponentDescriptorProvider>();
|
||||||
services.TryAddSingleton<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
|
services.TryAddSingleton<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
|
||||||
services.TryAddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
services.TryAddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
||||||
{
|
{
|
||||||
private readonly IViewComponentFactory _viewComponentFactory;
|
private readonly IViewComponentFactory _viewComponentFactory;
|
||||||
|
private readonly ViewComponentInvokerCache _viewComponentInvokerCache;
|
||||||
private readonly DiagnosticSource _diagnosticSource;
|
private readonly DiagnosticSource _diagnosticSource;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
|
@ -27,10 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
|
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="viewComponentFactory">The <see cref="IViewComponentFactory"/>.</param>
|
/// <param name="viewComponentFactory">The <see cref="IViewComponentFactory"/>.</param>
|
||||||
|
/// <param name="viewComponentInvokerCache">The <see cref="ViewComponentInvokerCache"/>.</param>
|
||||||
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
|
/// <param name="diagnosticSource">The <see cref="DiagnosticSource"/>.</param>
|
||||||
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
/// <param name="logger">The <see cref="ILogger"/>.</param>
|
||||||
public DefaultViewComponentInvoker(
|
public DefaultViewComponentInvoker(
|
||||||
IViewComponentFactory viewComponentFactory,
|
IViewComponentFactory viewComponentFactory,
|
||||||
|
ViewComponentInvokerCache viewComponentInvokerCache,
|
||||||
DiagnosticSource diagnosticSource,
|
DiagnosticSource diagnosticSource,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
|
|
@ -39,6 +42,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
throw new ArgumentNullException(nameof(viewComponentFactory));
|
throw new ArgumentNullException(nameof(viewComponentFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewComponentInvokerCache == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(viewComponentInvokerCache));
|
||||||
|
}
|
||||||
|
|
||||||
if (diagnosticSource == null)
|
if (diagnosticSource == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||||
|
|
@ -50,6 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewComponentFactory = viewComponentFactory;
|
_viewComponentFactory = viewComponentFactory;
|
||||||
|
_viewComponentInvokerCache = viewComponentInvokerCache;
|
||||||
_diagnosticSource = diagnosticSource;
|
_diagnosticSource = diagnosticSource;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
@ -95,11 +104,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
|
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
|
||||||
|
|
||||||
|
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||||
|
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
_logger.ViewComponentExecuting(context, arguments);
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
||||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(method, component, arguments);
|
var result = await ControllerActionExecutor.ExecuteAsync(methodExecutor, component, arguments);
|
||||||
|
|
||||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||||
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
|
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
|
||||||
|
|
@ -121,6 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||||
context.Arguments,
|
context.Arguments,
|
||||||
method.GetParameters());
|
method.GetParameters());
|
||||||
|
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||||
|
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
_logger.ViewComponentExecuting(context, arguments);
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
@ -129,16 +141,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
object result;
|
object result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = method.Invoke(component, arguments);
|
result = methodExecutor.Execute(component, arguments);
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException ex)
|
finally
|
||||||
{
|
{
|
||||||
_viewComponentFactory.ReleaseViewComponent(context, component);
|
_viewComponentFactory.ReleaseViewComponent(context, component);
|
||||||
|
|
||||||
// Preserve callstack of any user-thrown exceptions.
|
|
||||||
var exceptionInfo = ExceptionDispatchInfo.Capture(ex.InnerException);
|
|
||||||
exceptionInfo.Throw();
|
|
||||||
return null; // Unreachable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
|
|
@ -11,11 +11,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
public class DefaultViewComponentInvokerFactory : IViewComponentInvokerFactory
|
public class DefaultViewComponentInvokerFactory : IViewComponentInvokerFactory
|
||||||
{
|
{
|
||||||
private readonly IViewComponentFactory _viewComponentFactory;
|
private readonly IViewComponentFactory _viewComponentFactory;
|
||||||
|
private readonly ViewComponentInvokerCache _viewComponentInvokerCache;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly DiagnosticSource _diagnosticSource;
|
private readonly DiagnosticSource _diagnosticSource;
|
||||||
|
|
||||||
public DefaultViewComponentInvokerFactory(
|
public DefaultViewComponentInvokerFactory(
|
||||||
IViewComponentFactory viewComponentFactory,
|
IViewComponentFactory viewComponentFactory,
|
||||||
|
ViewComponentInvokerCache viewComponentInvokerCache,
|
||||||
DiagnosticSource diagnosticSource,
|
DiagnosticSource diagnosticSource,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
|
|
@ -24,6 +26,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
throw new ArgumentNullException(nameof(viewComponentFactory));
|
throw new ArgumentNullException(nameof(viewComponentFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewComponentInvokerCache == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(viewComponentInvokerCache));
|
||||||
|
}
|
||||||
|
|
||||||
if (diagnosticSource == null)
|
if (diagnosticSource == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||||
|
|
@ -36,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
|
|
||||||
_viewComponentFactory = viewComponentFactory;
|
_viewComponentFactory = viewComponentFactory;
|
||||||
_diagnosticSource = diagnosticSource;
|
_diagnosticSource = diagnosticSource;
|
||||||
|
_viewComponentInvokerCache = viewComponentInvokerCache;
|
||||||
|
|
||||||
_logger = loggerFactory.CreateLogger<DefaultViewComponentInvoker>();
|
_logger = loggerFactory.CreateLogger<DefaultViewComponentInvoker>();
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
|
|
||||||
return new DefaultViewComponentInvoker(
|
return new DefaultViewComponentInvoker(
|
||||||
_viewComponentFactory,
|
_viewComponentFactory,
|
||||||
|
_viewComponentInvokerCache,
|
||||||
_diagnosticSource,
|
_diagnosticSource,
|
||||||
_logger);
|
_logger);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class ViewComponentInvokerCache
|
||||||
|
{
|
||||||
|
private readonly IViewComponentDescriptorCollectionProvider _collectionProvider;
|
||||||
|
|
||||||
|
private volatile InnerCache _currentCache;
|
||||||
|
|
||||||
|
public ViewComponentInvokerCache(IViewComponentDescriptorCollectionProvider collectionProvider)
|
||||||
|
{
|
||||||
|
_collectionProvider = collectionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InnerCache CurrentCache
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var current = _currentCache;
|
||||||
|
var actionDescriptors = _collectionProvider.ViewComponents;
|
||||||
|
|
||||||
|
if (current == null || current.Version != actionDescriptors.Version)
|
||||||
|
{
|
||||||
|
current = new InnerCache(actionDescriptors.Version);
|
||||||
|
_currentCache = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectMethodExecutor GetViewComponentMethodExecutor(ViewComponentContext viewComponentContext)
|
||||||
|
{
|
||||||
|
var cache = CurrentCache;
|
||||||
|
var viewComponentDescriptor = viewComponentContext.ViewComponentDescriptor;
|
||||||
|
|
||||||
|
ObjectMethodExecutor executor;
|
||||||
|
if (cache.Entries.TryGetValue(viewComponentDescriptor, out executor))
|
||||||
|
{
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
executor = ObjectMethodExecutor.Create(viewComponentDescriptor.MethodInfo, viewComponentDescriptor.TypeInfo);
|
||||||
|
|
||||||
|
cache.Entries.TryAdd(viewComponentDescriptor, executor);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InnerCache
|
||||||
|
{
|
||||||
|
public InnerCache(int version)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentDictionary<ViewComponentDescriptor, ObjectMethodExecutor> Entries { get; } =
|
||||||
|
new ConcurrentDictionary<ViewComponentDescriptor, ObjectMethodExecutor>();
|
||||||
|
|
||||||
|
public int Version { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,22 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private delegate dynamic ReturnTaskAsDynamicValue(int i, string s);
|
private delegate dynamic ReturnTaskAsDynamicValue(int i, string s);
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AsyncAction_WithVoidReturnType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var methodWithVoidReturnType = new MethodWithVoidReturnType(TestController.VoidAction);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
|
||||||
methodWithVoidReturnType.GetMethodInfo(),
|
|
||||||
null,
|
|
||||||
(IDictionary<string, object>)null);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Same(null, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AsyncAction_TaskReturnType()
|
public async Task AsyncAction_TaskReturnType()
|
||||||
{
|
{
|
||||||
|
|
@ -57,12 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
var methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction);
|
var methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction);
|
||||||
|
var result = await ExecuteAction(
|
||||||
// Act
|
methodWithTaskReturnType,
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
_controller,
|
||||||
methodWithTaskReturnType.GetMethodInfo(),
|
actionParameters);
|
||||||
_controller,
|
|
||||||
actionParameters);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(null, result);
|
Assert.Same(null, result);
|
||||||
|
|
@ -79,11 +61,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
methodWithTaskOfIntReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters);
|
actionParameters);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(inputParam1, result);
|
Assert.Equal(inputParam1, result);
|
||||||
}
|
}
|
||||||
|
|
@ -99,10 +80,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction);
|
var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await (Task<int>)(await ControllerActionExecutor.ExecuteAsync(
|
var result = await (Task<int>)( await ExecuteAction(
|
||||||
methodWithTaskOfTaskOfIntReturnType.GetMethodInfo(),
|
methodWithTaskOfTaskOfIntReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(inputParam1, result);
|
Assert.Equal(inputParam1, result);
|
||||||
|
|
@ -120,9 +101,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
await Assert.ThrowsAsync<NotImplementedException>(
|
await Assert.ThrowsAsync<NotImplementedException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(methodWithTaskOfIntReturnType.GetMethodInfo(),
|
() => ExecuteAction(
|
||||||
_controller,
|
methodWithTaskOfIntReturnType,
|
||||||
actionParameters));
|
_controller,
|
||||||
|
actionParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -137,9 +119,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await Assert.ThrowsAsync<NotImplementedException>(
|
await Assert.ThrowsAsync<NotImplementedException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(methodWithTaskOfIntReturnType.GetMethodInfo(),
|
() => ExecuteAction(
|
||||||
_controller,
|
methodWithTaskOfIntReturnType,
|
||||||
actionParameters));
|
_controller,
|
||||||
|
actionParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -155,8 +138,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
methodWithTaskOfIntReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
Assert.Equal(expectedException, ex.Message);
|
Assert.Equal(expectedException, ex.Message);
|
||||||
|
|
@ -170,11 +153,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var syncMethod = new SyncMethod(_controller.Echo);
|
var syncMethod = new SyncMethod(_controller.Echo);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new Dictionary<string, object>() { { "input", inputString } });
|
new Dictionary<string, object>() { { "input", inputString } });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(inputString, result);
|
Assert.Equal(inputString, result);
|
||||||
}
|
}
|
||||||
|
|
@ -188,10 +170,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await Assert.ThrowsAsync<NotImplementedException>(
|
await Assert.ThrowsAsync<NotImplementedException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new Dictionary<string, object>() { { "input", inputString } }));
|
new Dictionary<string, object>() { { "input", inputString } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -201,8 +183,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new Dictionary<string, object>());
|
new Dictionary<string, object>());
|
||||||
|
|
||||||
|
|
@ -217,8 +199,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new object[] { null, });
|
new object[] { null, });
|
||||||
|
|
||||||
|
|
@ -233,8 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new Dictionary<string, object>());
|
new Dictionary<string, object>());
|
||||||
|
|
||||||
|
|
@ -249,8 +231,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
syncMethod.GetMethodInfo(),
|
syncMethod,
|
||||||
_controller,
|
_controller,
|
||||||
new Dictionary<string, object>() { { "input", null } });
|
new Dictionary<string, object>() { { "input", null } });
|
||||||
|
|
||||||
|
|
@ -276,8 +258,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
methodWithCutomTaskReturnType.GetMethodInfo(),
|
methodWithCutomTaskReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
Assert.Equal(expectedException, ex.Message);
|
Assert.Equal(expectedException, ex.Message);
|
||||||
|
|
@ -299,8 +281,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
methodWithCutomTaskOfTReturnType.GetMethodInfo(),
|
methodWithCutomTaskOfTReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
Assert.Equal(expectedException, ex.Message);
|
Assert.Equal(expectedException, ex.Message);
|
||||||
|
|
@ -325,8 +307,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
methodWithUnwrappedTask.GetMethodInfo(),
|
methodWithUnwrappedTask,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
Assert.Equal(expectedException, ex.Message);
|
Assert.Equal(expectedException, ex.Message);
|
||||||
|
|
@ -348,10 +330,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
dynamicTaskMethod.GetMethodInfo(),
|
dynamicTaskMethod,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
Assert.Equal(expectedException, ex.Message);
|
Assert.Equal(expectedException, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,10 +349,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
var result = await ExecuteAction(
|
||||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
methodWithTaskOfIntReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters);
|
actionParameters);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(inputParam1, result);
|
Assert.Equal(inputParam1, result);
|
||||||
|
|
@ -383,32 +365,48 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var inputParam2 = "Second Parameter";
|
var inputParam2 = "Second Parameter";
|
||||||
|
|
||||||
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
||||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
var message = TestPlatformHelper.IsMono ? "Object type {0} cannot be converted to target type: {1}" :
|
|
||||||
"Object of type '{0}' cannot be converted to type '{1}'.";
|
|
||||||
var expectedException = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
message,
|
|
||||||
typeof(string),
|
|
||||||
typeof(int));
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
||||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
var ex = await Assert.ThrowsAsync<InvalidCastException>(
|
||||||
() => ControllerActionExecutor.ExecuteAsync(
|
() => ExecuteAction(
|
||||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
methodWithTaskOfIntReturnType,
|
||||||
_controller,
|
_controller,
|
||||||
actionParameters));
|
actionParameters));
|
||||||
|
}
|
||||||
|
|
||||||
Assert.Equal(expectedException, ex.Message);
|
private async Task<object> ExecuteAction(
|
||||||
|
Delegate methodDelegate,
|
||||||
|
TestController controller,
|
||||||
|
IDictionary<string, object> actionParameters)
|
||||||
|
{
|
||||||
|
var executor = ObjectMethodExecutor.Create(methodDelegate.GetMethodInfo(), _controller.GetType().GetTypeInfo());
|
||||||
|
|
||||||
|
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||||
|
executor,
|
||||||
|
controller,
|
||||||
|
actionParameters);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<object> ExecuteAction(
|
||||||
|
Delegate methodDelegate,
|
||||||
|
TestController controller,
|
||||||
|
object[] actionParameters)
|
||||||
|
{
|
||||||
|
var executor = ObjectMethodExecutor.Create(methodDelegate.GetMethodInfo(), _controller.GetType().GetTypeInfo());
|
||||||
|
|
||||||
|
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||||
|
executor,
|
||||||
|
controller,
|
||||||
|
actionParameters);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestController
|
public class TestController
|
||||||
{
|
{
|
||||||
public static void VoidAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning disable 1998
|
#pragma warning disable 1998
|
||||||
public async Task TaskAction(int i, string s)
|
public async Task TaskAction(int i, string s)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
@ -12,7 +13,7 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class FilterCacheTest
|
public class ControllerActionInvokerCacheTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetFilters_CachesAllFilters()
|
public void GetFilters_CachesAllFilters()
|
||||||
|
|
@ -28,9 +29,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
||||||
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
||||||
},
|
},
|
||||||
|
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod(
|
||||||
|
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)),
|
||||||
|
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo()
|
||||||
};
|
};
|
||||||
|
|
||||||
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
|
var context = new ControllerContext(new ActionContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
action));
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var filters1 = cache.GetFilters(context);
|
var filters1 = cache.GetFilters(context);
|
||||||
|
|
@ -66,9 +73,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action),
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action),
|
||||||
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
||||||
},
|
},
|
||||||
|
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod(
|
||||||
|
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)),
|
||||||
|
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo()
|
||||||
};
|
};
|
||||||
|
|
||||||
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
|
var context = new ControllerContext(new ActionContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
action));
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var filters1 = cache.GetFilters(context);
|
var filters1 = cache.GetFilters(context);
|
||||||
|
|
@ -104,9 +117,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
|
||||||
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
||||||
},
|
},
|
||||||
|
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod(
|
||||||
|
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)),
|
||||||
|
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo()
|
||||||
};
|
};
|
||||||
|
|
||||||
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
|
var context = new ControllerContext(new ActionContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
action));
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var filters1 = cache.GetFilters(context);
|
var filters1 = cache.GetFilters(context);
|
||||||
|
|
@ -128,6 +147,46 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
f => Assert.Same(filters1[1], f)); // Cached
|
f => Assert.Same(filters1[1], f)); // Cached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetControllerActionMethodExecutor_CachesExecutor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = CreateServices();
|
||||||
|
var cache = CreateCache(new DefaultFilterProvider());
|
||||||
|
|
||||||
|
var action = new ControllerActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors = new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
|
||||||
|
new FilterDescriptor(new TestFilter(), FilterScope.Action),
|
||||||
|
},
|
||||||
|
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod(
|
||||||
|
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)),
|
||||||
|
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo()
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new ControllerContext(
|
||||||
|
new ActionContext(new DefaultHttpContext(),
|
||||||
|
new RouteData(),
|
||||||
|
action));
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var executor1 = cache.GetControllerActionMethodExecutor(context);
|
||||||
|
|
||||||
|
Assert.NotNull(executor1);
|
||||||
|
|
||||||
|
var filters1 = cache.GetFilters(context);
|
||||||
|
|
||||||
|
Assert.NotNull(filters1);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
var executor2 = cache.GetControllerActionMethodExecutor(context);
|
||||||
|
|
||||||
|
Assert.Same(executor1, executor2);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestFilter : IFilterMetadata
|
private class TestFilter : IFilterMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -147,11 +206,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return new ServiceCollection().BuildServiceProvider();
|
return new ServiceCollection().BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FilterCache CreateCache(params IFilterProvider[] providers)
|
private static ControllerActionInvokerCache CreateCache(params IFilterProvider[] providers)
|
||||||
{
|
{
|
||||||
var services = CreateServices();
|
var services = CreateServices();
|
||||||
var descriptorProvider = new ActionDescriptorCollectionProvider(services);
|
var descriptorProvider = new ActionDescriptorCollectionProvider(services);
|
||||||
return new FilterCache(descriptorProvider, providers);
|
return new ControllerActionInvokerCache(descriptorProvider, providers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1977,6 +1977,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod");
|
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod");
|
||||||
}
|
}
|
||||||
|
actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo();
|
||||||
|
|
||||||
var httpContext = new Mock<HttpContext>(MockBehavior.Loose);
|
var httpContext = new Mock<HttpContext>(MockBehavior.Loose);
|
||||||
|
|
||||||
|
|
@ -2224,11 +2225,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FilterCache CreateFilterCache(IFilterProvider[] filterProviders = null)
|
private static ControllerActionInvokerCache CreateFilterCache(IFilterProvider[] filterProviders = null)
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection().BuildServiceProvider();
|
var services = new ServiceCollection().BuildServiceProvider();
|
||||||
var descriptorProvider = new ActionDescriptorCollectionProvider(services);
|
var descriptorProvider = new ActionDescriptorCollectionProvider(services);
|
||||||
return new FilterCache(descriptorProvider, filterProviders.AsEnumerable() ?? new List<IFilterProvider>());
|
return new ControllerActionInvokerCache(descriptorProvider, filterProviders.AsEnumerable() ?? new List<IFilterProvider>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestControllerActionInvoker : ControllerActionInvoker
|
private class TestControllerActionInvoker : ControllerActionInvoker
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
public class ObjectMethodExecutorTest
|
||||||
|
{
|
||||||
|
private TestObject _targetObject = new TestObject();
|
||||||
|
private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteValueMethod()
|
||||||
|
{
|
||||||
|
var executor = GetExecutorForMethod("ValueMethod");
|
||||||
|
var result = executor.Execute(
|
||||||
|
_targetObject,
|
||||||
|
new object[] { 10 , 20 });
|
||||||
|
Assert.Equal(30, (int)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteVoidValueMethod()
|
||||||
|
{
|
||||||
|
var executor = GetExecutorForMethod("VoidValueMethod");
|
||||||
|
var result = executor.Execute(
|
||||||
|
_targetObject,
|
||||||
|
new object[] { 10 });
|
||||||
|
Assert.Same(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteValueMethodWithReturnType()
|
||||||
|
{
|
||||||
|
var executor = GetExecutorForMethod("ValueMethodWithReturnType");
|
||||||
|
var result = executor.Execute(
|
||||||
|
_targetObject,
|
||||||
|
new object[] { 10 });
|
||||||
|
var resultObject = Assert.IsType<TestObject>(result);
|
||||||
|
Assert.Equal("Hello", resultObject.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteValueMethodUpdateValue()
|
||||||
|
{
|
||||||
|
var executor = GetExecutorForMethod("ValueMethodUpdateValue");
|
||||||
|
var parameter = new TestObject();
|
||||||
|
var result = executor.Execute(
|
||||||
|
_targetObject,
|
||||||
|
new object[] { parameter });
|
||||||
|
var resultObject = Assert.IsType<TestObject>(result);
|
||||||
|
Assert.Equal("HelloWorld", resultObject.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecuteValueMethodWithReturnTypeThrowsException()
|
||||||
|
{
|
||||||
|
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException");
|
||||||
|
var parameter = new TestObject();
|
||||||
|
Assert.Throws<NotImplementedException>(
|
||||||
|
() => executor.Execute(
|
||||||
|
_targetObject,
|
||||||
|
new object[] { parameter }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
|
||||||
|
{
|
||||||
|
var method = typeof(TestObject).GetMethod(methodName);
|
||||||
|
var executor = ObjectMethodExecutor.Create(method, targetTypeInfo);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestObject
|
||||||
|
{
|
||||||
|
public string value;
|
||||||
|
public int ValueMethod(int i, int j)
|
||||||
|
{
|
||||||
|
return i+j;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void VoidValueMethod(int i)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public TestObject ValueMethodWithReturnType(int i)
|
||||||
|
{
|
||||||
|
return new TestObject() { value = "Hello" }; ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Not Implemented Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestObject ValueMethodUpdateValue(TestObject parameter)
|
||||||
|
{
|
||||||
|
parameter.value = "HelloWorld";
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -518,6 +518,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<DiagnosticSource>(diagnosticSource);
|
services.AddSingleton<DiagnosticSource>(diagnosticSource);
|
||||||
|
services.AddSingleton<ViewComponentInvokerCache>();
|
||||||
services.AddSingleton<IOptions<MvcViewOptions>, TestOptionsManager<MvcViewOptions>>();
|
services.AddSingleton<IOptions<MvcViewOptions>, TestOptionsManager<MvcViewOptions>>();
|
||||||
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||||
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
|
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue