parent
4f709bdbfd
commit
007c47d065
|
|
@ -129,7 +129,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
// These are stateless
|
||||
services.TryAddSingleton<IControllerActionArgumentBinder, ControllerArgumentBinder>();
|
||||
services.TryAddSingleton<FilterCache>();
|
||||
services.TryAddSingleton<ControllerActionInvokerCache>();
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
||||
|
||||
|
|
|
|||
|
|
@ -26,36 +26,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
public static Task<object> ExecuteAsync(
|
||||
MethodInfo actionMethodInfo,
|
||||
ObjectMethodExecutor actionMethodExecutor,
|
||||
object instance,
|
||||
IDictionary<string, object> actionArguments)
|
||||
{
|
||||
var orderedArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
|
||||
return ExecuteAsync(actionMethodInfo, instance, orderedArguments);
|
||||
var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor.MethodInfo.GetParameters());
|
||||
return ExecuteAsync(actionMethodExecutor, instance, orderedArguments);
|
||||
}
|
||||
|
||||
public static Task<object> ExecuteAsync(
|
||||
MethodInfo actionMethodInfo,
|
||||
ObjectMethodExecutor actionMethodExecutor,
|
||||
object instance,
|
||||
object[] orderedActionArguments)
|
||||
{
|
||||
object invocationResult = null;
|
||||
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();
|
||||
}
|
||||
|
||||
object invocationResult = actionMethodExecutor.Execute(instance, orderedActionArguments);
|
||||
return CoerceResultToTaskAsync(
|
||||
invocationResult,
|
||||
actionMethodInfo.ReturnType,
|
||||
actionMethodInfo.Name,
|
||||
actionMethodInfo.DeclaringType);
|
||||
actionMethodExecutor.MethodInfo.ReturnType,
|
||||
actionMethodExecutor.MethodInfo.Name,
|
||||
actionMethodExecutor.MethodInfo.DeclaringType);
|
||||
}
|
||||
|
||||
// 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(
|
||||
ActionContext actionContext,
|
||||
FilterCache filterCache,
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
IControllerFactory controllerFactory,
|
||||
ControllerActionDescriptor descriptor,
|
||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
int maxModelValidationErrors)
|
||||
: base(
|
||||
actionContext,
|
||||
filterCache,
|
||||
controllerActionInvokerCache,
|
||||
inputFormatters,
|
||||
modelBinders,
|
||||
modelValidatorProviders,
|
||||
|
|
@ -96,6 +96,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
var actionMethodInfo = _descriptor.MethodInfo;
|
||||
|
||||
var methodExecutor = GetControllerActionMethodExecutor();
|
||||
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||
actionExecutingContext.ActionArguments,
|
||||
actionMethodInfo.GetParameters());
|
||||
|
|
@ -103,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Logger.ActionMethodExecuting(actionExecutingContext, arguments);
|
||||
|
||||
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
|
||||
actionMethodInfo,
|
||||
methodExecutor,
|
||||
actionExecutingContext.Controller,
|
||||
arguments);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class FilterCache
|
||||
public class ControllerActionInvokerCache
|
||||
{
|
||||
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
private volatile InnerCache _currentCache;
|
||||
|
||||
public FilterCache(
|
||||
public ControllerActionInvokerCache(
|
||||
IActionDescriptorCollectionProvider collectionProvider,
|
||||
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 actionDescriptor = actionContext.ActionDescriptor;
|
||||
var actionDescriptor = controllerContext.ActionDescriptor;
|
||||
|
||||
CacheEntry entry;
|
||||
Entry 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);
|
||||
for (var i = 0; i < actionDescriptor.FilterDescriptors.Count; i++)
|
||||
{
|
||||
items.Add(new FilterItem(actionDescriptor.FilterDescriptors[i]));
|
||||
}
|
||||
|
||||
ExecuteProviders(actionContext, items);
|
||||
ExecuteProviders(controllerContext, items);
|
||||
|
||||
var filters = ExtractFilters(items);
|
||||
|
||||
|
|
@ -79,30 +81,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (allFiltersCached)
|
||||
{
|
||||
entry = new CacheEntry(filters);
|
||||
entry = new Entry(filters, null, executor);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = new CacheEntry(items);
|
||||
entry = new Entry(null, items, executor);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return entry.Filters;
|
||||
}
|
||||
|
||||
var items = new List<FilterItem>(entry.Items.Count);
|
||||
for (var i = 0; i < entry.Items.Count; i++)
|
||||
var items = new List<FilterItem>(entry.FilterItems.Count);
|
||||
for (var i = 0; i < entry.FilterItems.Count; i++)
|
||||
{
|
||||
var item = entry.Items[i];
|
||||
var item = entry.FilterItems[i];
|
||||
if (item.IsReusable)
|
||||
{
|
||||
items.Add(item);
|
||||
|
|
@ -172,29 +186,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Version = version;
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<ActionDescriptor, CacheEntry> Entries { get; } =
|
||||
new ConcurrentDictionary<ActionDescriptor, CacheEntry>();
|
||||
public ConcurrentDictionary<ActionDescriptor, Entry> Entries { get; } =
|
||||
new ConcurrentDictionary<ActionDescriptor, Entry>();
|
||||
|
||||
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;
|
||||
Items = null;
|
||||
ActionMethodExecutor = executor;
|
||||
}
|
||||
|
||||
public CacheEntry(List<FilterItem> items)
|
||||
{
|
||||
Items = items;
|
||||
Filters = null;
|
||||
}
|
||||
|
||||
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 IControllerFactory _controllerFactory;
|
||||
private readonly FilterCache _filterCache;
|
||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||
|
|
@ -30,14 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
public ControllerActionInvokerProvider(
|
||||
IControllerFactory controllerFactory,
|
||||
FilterCache filterCache,
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
IControllerActionArgumentBinder argumentBinder,
|
||||
IOptions<MvcOptions> optionsAccessor,
|
||||
ILoggerFactory loggerFactory,
|
||||
DiagnosticSource diagnosticSource)
|
||||
{
|
||||
_controllerFactory = controllerFactory;
|
||||
_filterCache = filterCache;
|
||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||
_argumentBinder = argumentBinder;
|
||||
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
|
||||
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
|
||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
context.Result = new ControllerActionInvoker(
|
||||
context.ActionContext,
|
||||
_filterCache,
|
||||
_controllerActionInvokerCache,
|
||||
_controllerFactory,
|
||||
actionDescriptor,
|
||||
_inputFormatters,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public abstract class FilterActionInvoker : IActionInvoker
|
||||
{
|
||||
private readonly FilterCache _filterCache;
|
||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
|
||||
private readonly IReadOnlyList<IModelBinder> _modelBinders;
|
||||
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
|
||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private readonly int _maxModelValidationErrors;
|
||||
|
||||
private IFilterMetadata[] _filters;
|
||||
private ObjectMethodExecutor _controllerActionMethodExecutor;
|
||||
private FilterCursor _cursor;
|
||||
|
||||
private AuthorizationFilterContext _authorizationContext;
|
||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
public FilterActionInvoker(
|
||||
ActionContext actionContext,
|
||||
FilterCache filterCache,
|
||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||
IReadOnlyList<IInputFormatter> inputFormatters,
|
||||
IReadOnlyList<IModelBinder> modelBinders,
|
||||
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
|
||||
|
|
@ -58,9 +59,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
throw new ArgumentNullException(nameof(actionContext));
|
||||
}
|
||||
|
||||
if (filterCache == null)
|
||||
if (controllerActionInvokerCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filterCache));
|
||||
throw new ArgumentNullException(nameof(controllerActionInvokerCache));
|
||||
}
|
||||
|
||||
if (inputFormatters == null)
|
||||
|
|
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
Context = new ControllerContext(actionContext);
|
||||
|
||||
_filterCache = filterCache;
|
||||
_controllerActionInvokerCache = controllerActionInvokerCache;
|
||||
_inputFormatters = inputFormatters;
|
||||
_modelBinders = modelBinders;
|
||||
_modelValidatorProviders = modelValidatorProviders;
|
||||
|
|
@ -130,7 +131,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
public virtual async Task InvokeAsync()
|
||||
{
|
||||
_filters = GetFilters();
|
||||
var cacheEntry = _controllerActionInvokerCache.GetCacheEntry(Context);
|
||||
_filters = _controllerActionInvokerCache.GetFiltersFromEntry(cacheEntry, Context);
|
||||
_controllerActionMethodExecutor = cacheEntry.ActionMethodExecutor;
|
||||
_cursor = new FilterCursor(_filters);
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
DefaultViewComponentDescriptorCollectionProvider>();
|
||||
|
||||
services.TryAddSingleton<ViewComponentInvokerCache>();
|
||||
services.TryAddTransient<IViewComponentDescriptorProvider, DefaultViewComponentDescriptorProvider>();
|
||||
services.TryAddSingleton<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
|
||||
services.TryAddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
public class DefaultViewComponentInvoker : IViewComponentInvoker
|
||||
{
|
||||
private readonly IViewComponentFactory _viewComponentFactory;
|
||||
private readonly ViewComponentInvokerCache _viewComponentInvokerCache;
|
||||
private readonly DiagnosticSource _diagnosticSource;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
|
@ -27,10 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
/// Initializes a new instance of <see cref="DefaultViewComponentInvoker"/>.
|
||||
/// </summary>
|
||||
/// <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="logger">The <see cref="ILogger"/>.</param>
|
||||
public DefaultViewComponentInvoker(
|
||||
IViewComponentFactory viewComponentFactory,
|
||||
ViewComponentInvokerCache viewComponentInvokerCache,
|
||||
DiagnosticSource diagnosticSource,
|
||||
ILogger logger)
|
||||
{
|
||||
|
|
@ -39,6 +42,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
throw new ArgumentNullException(nameof(viewComponentFactory));
|
||||
}
|
||||
|
||||
if (viewComponentInvokerCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewComponentInvokerCache));
|
||||
}
|
||||
|
||||
if (diagnosticSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||
|
|
@ -50,6 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
}
|
||||
|
||||
_viewComponentFactory = viewComponentFactory;
|
||||
_viewComponentInvokerCache = viewComponentInvokerCache;
|
||||
_diagnosticSource = diagnosticSource;
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -95,11 +104,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
|
||||
|
||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
||||
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);
|
||||
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
|
||||
|
|
@ -121,6 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||
context.Arguments,
|
||||
method.GetParameters());
|
||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
|
@ -129,16 +141,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
object result;
|
||||
try
|
||||
{
|
||||
result = method.Invoke(component, arguments);
|
||||
result = methodExecutor.Execute(component, arguments);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
finally
|
||||
{
|
||||
_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);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||
|
|
@ -11,11 +11,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
public class DefaultViewComponentInvokerFactory : IViewComponentInvokerFactory
|
||||
{
|
||||
private readonly IViewComponentFactory _viewComponentFactory;
|
||||
private readonly ViewComponentInvokerCache _viewComponentInvokerCache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly DiagnosticSource _diagnosticSource;
|
||||
|
||||
public DefaultViewComponentInvokerFactory(
|
||||
IViewComponentFactory viewComponentFactory,
|
||||
ViewComponentInvokerCache viewComponentInvokerCache,
|
||||
DiagnosticSource diagnosticSource,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
|
|
@ -24,6 +26,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
throw new ArgumentNullException(nameof(viewComponentFactory));
|
||||
}
|
||||
|
||||
if (viewComponentInvokerCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewComponentInvokerCache));
|
||||
}
|
||||
|
||||
if (diagnosticSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||
|
|
@ -36,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
|
||||
_viewComponentFactory = viewComponentFactory;
|
||||
_diagnosticSource = diagnosticSource;
|
||||
_viewComponentInvokerCache = viewComponentInvokerCache;
|
||||
|
||||
_logger = loggerFactory.CreateLogger<DefaultViewComponentInvoker>();
|
||||
}
|
||||
|
|
@ -53,6 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
|
||||
return new DefaultViewComponentInvoker(
|
||||
_viewComponentFactory,
|
||||
_viewComponentInvokerCache,
|
||||
_diagnosticSource,
|
||||
_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);
|
||||
|
||||
[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]
|
||||
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 methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters);
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
// Assert
|
||||
Assert.Same(null, result);
|
||||
|
|
@ -79,11 +61,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
}
|
||||
|
|
@ -99,10 +80,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction);
|
||||
|
||||
// Act
|
||||
var result = await (Task<int>)(await ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskOfTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters));
|
||||
var result = await (Task<int>)( await ExecuteAction(
|
||||
methodWithTaskOfTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
|
|
@ -120,9 +101,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act and Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters));
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -137,9 +119,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters));
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -155,8 +138,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
|
|
@ -170,11 +153,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var syncMethod = new SyncMethod(_controller.Echo);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
// Assert
|
||||
Assert.Equal(inputString, result);
|
||||
}
|
||||
|
|
@ -188,10 +170,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } }));
|
||||
() => ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -201,8 +183,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>());
|
||||
|
||||
|
|
@ -217,8 +199,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new object[] { null, });
|
||||
|
||||
|
|
@ -233,8 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>());
|
||||
|
||||
|
|
@ -249,8 +231,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
syncMethod.GetMethodInfo(),
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", null } });
|
||||
|
||||
|
|
@ -276,8 +258,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithCutomTaskReturnType.GetMethodInfo(),
|
||||
() => ExecuteAction(
|
||||
methodWithCutomTaskReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
|
|
@ -299,8 +281,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithCutomTaskOfTReturnType.GetMethodInfo(),
|
||||
() => ExecuteAction(
|
||||
methodWithCutomTaskOfTReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
|
|
@ -325,8 +307,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithUnwrappedTask.GetMethodInfo(),
|
||||
() => ExecuteAction(
|
||||
methodWithUnwrappedTask,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
|
|
@ -348,10 +330,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
dynamicTaskMethod.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters));
|
||||
() => ExecuteAction(
|
||||
dynamicTaskMethod,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
|
|
@ -367,10 +349,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||
|
||||
// Act
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters);
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
|
|
@ -383,32 +365,48 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var inputParam2 = "Second Parameter";
|
||||
|
||||
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
||||
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));
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||
|
||||
// Act & Assert
|
||||
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => ControllerActionExecutor.ExecuteAsync(
|
||||
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||
_controller,
|
||||
actionParameters));
|
||||
var ex = await Assert.ThrowsAsync<InvalidCastException>(
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
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 static void VoidAction()
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
|
@ -12,7 +13,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class FilterCacheTest
|
||||
public class ControllerActionInvokerCacheTest
|
||||
{
|
||||
[Fact]
|
||||
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),
|
||||
},
|
||||
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
|
||||
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 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
|
||||
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 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
|
||||
var filters1 = cache.GetFilters(context);
|
||||
|
|
@ -128,6 +147,46 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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
|
||||
{
|
||||
}
|
||||
|
|
@ -147,11 +206,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return new ServiceCollection().BuildServiceProvider();
|
||||
}
|
||||
|
||||
private static FilterCache CreateCache(params IFilterProvider[] providers)
|
||||
private static ControllerActionInvokerCache CreateCache(params IFilterProvider[] providers)
|
||||
{
|
||||
var services = CreateServices();
|
||||
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.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo();
|
||||
|
||||
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 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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
services.AddSingleton<DiagnosticSource>(diagnosticSource);
|
||||
services.AddSingleton<ViewComponentInvokerCache>();
|
||||
services.AddSingleton<IOptions<MvcViewOptions>, TestOptionsManager<MvcViewOptions>>();
|
||||
services.AddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
|
||||
services.AddSingleton<IViewComponentSelector, DefaultViewComponentSelector>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue