// 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 System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.Internal { public class ControllerActionInvokerCache { private readonly IActionDescriptorCollectionProvider _collectionProvider; private readonly IFilterProvider[] _filterProviders; private volatile InnerCache _currentCache; public ControllerActionInvokerCache( IActionDescriptorCollectionProvider collectionProvider, IEnumerable filterProviders) { _collectionProvider = collectionProvider; _filterProviders = filterProviders.OrderBy(item => item.Order).ToArray(); } private InnerCache CurrentCache { get { var current = _currentCache; var actionDescriptors = _collectionProvider.ActionDescriptors; if (current == null || current.Version != actionDescriptors.Version) { current = new InnerCache(actionDescriptors.Version); _currentCache = current; } return current; } } public ControllerActionInvokerState GetState(ControllerContext controllerContext) { // Filter instances from statically defined filter descriptors + from filter providers IFilterMetadata[] filters; var cache = CurrentCache; var actionDescriptor = controllerContext.ActionDescriptor; Entry cacheEntry; if (cache.Entries.TryGetValue(actionDescriptor, out cacheEntry)) { // Deep copy the cached filter items as filter providers could modify them var filterItems = new List(cacheEntry.FilterItems.Count); for (var i = 0; i < cacheEntry.FilterItems.Count; i++) { var filterItem = cacheEntry.FilterItems[i]; filterItems.Add( new FilterItem(filterItem.Descriptor) { Filter = filterItem.Filter, IsReusable = filterItem.IsReusable }); } filters = GetFilters(controllerContext, filterItems); return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor); } var executor = ObjectMethodExecutor.Create( actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo); var staticFilterItems = new List(actionDescriptor.FilterDescriptors.Count); for (var i = 0; i < actionDescriptor.FilterDescriptors.Count; i++) { staticFilterItems.Add(new FilterItem(actionDescriptor.FilterDescriptors[i])); } // Create a separate collection as we want to hold onto the statically defined filter items // in order to cache them var allFilterItems = new List(staticFilterItems); filters = GetFilters(controllerContext, allFilterItems); // Cache the filter items based on the following criteria // 1. Are created statically (ex: via filter attributes, added to global filter list etc.) // 2. Are re-usable for (var i = 0; i < staticFilterItems.Count; i++) { var item = staticFilterItems[i]; if (!item.IsReusable) { item.Filter = null; } } cacheEntry = new Entry(staticFilterItems, executor); cache.Entries.TryAdd(actionDescriptor, cacheEntry); return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor); } private IFilterMetadata[] GetFilters(ActionContext actionContext, List filterItems) { // Execute providers var context = new FilterProviderContext(actionContext, filterItems); for (var i = 0; i < _filterProviders.Length; i++) { _filterProviders[i].OnProvidersExecuting(context); } for (var i = _filterProviders.Length - 1; i >= 0; i--) { _filterProviders[i].OnProvidersExecuted(context); } // Extract filter instances from statically defined filters and filter providers var count = 0; for (var i = 0; i < filterItems.Count; i++) { if (filterItems[i].Filter != null) { count++; } } if (count == 0) { return EmptyArray.Instance; } else { var filters = new IFilterMetadata[count]; var filterIndex = 0; for (int i = 0; i < filterItems.Count; i++) { var filter = filterItems[i].Filter; if (filter != null) { filters[filterIndex++] = filter; } } return filters; } } private class InnerCache { public InnerCache(int version) { Version = version; } public ConcurrentDictionary Entries { get; } = new ConcurrentDictionary(); public int Version { get; } } private struct Entry { public Entry(List items, ObjectMethodExecutor executor) { FilterItems = items; ActionMethodExecutor = executor; } public List FilterItems { get; } public ObjectMethodExecutor ActionMethodExecutor { get; } } public struct ControllerActionInvokerState { public ControllerActionInvokerState( IFilterMetadata[] filters, ObjectMethodExecutor actionMethodExecutor) { Filters = filters; ActionMethodExecutor = actionMethodExecutor; } public IFilterMetadata[] Filters { get; } public ObjectMethodExecutor ActionMethodExecutor { get; set; } } } }