Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit d6a9068

Browse files
committed
[Fixes #4616] Filter caching is too aggressive
1 parent f4679fe commit d6a9068

File tree

2 files changed

+39
-32
lines changed

2 files changed

+39
-32
lines changed

src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,20 @@ public ControllerActionInvokerState GetState(ControllerContext controllerContext
5656
Entry cacheEntry;
5757
if (cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
5858
{
59-
filters = GetFilters(controllerContext, cacheEntry.FilterItems);
59+
// Deep copy the cached filter items as filter providers could modify them
60+
var filterItems = new List<FilterItem>(cacheEntry.FilterItems.Count);
61+
for (var i = 0; i < cacheEntry.FilterItems.Count; i++)
62+
{
63+
var filterItem = cacheEntry.FilterItems[i];
64+
filterItems.Add(
65+
new FilterItem(filterItem.Descriptor)
66+
{
67+
Filter = filterItem.Filter,
68+
IsReusable = filterItem.IsReusable
69+
});
70+
}
71+
72+
filters = GetFilters(controllerContext, filterItems);
6073

6174
return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor);
6275
}
@@ -71,7 +84,11 @@ public ControllerActionInvokerState GetState(ControllerContext controllerContext
7184
staticFilterItems.Add(new FilterItem(actionDescriptor.FilterDescriptors[i]));
7285
}
7386

74-
filters = GetFilters(controllerContext, staticFilterItems);
87+
// Create a separate collection as we want to hold onto the statically defined filter items
88+
// in order to cache them
89+
var allFilterItems = new List<FilterItem>(staticFilterItems);
90+
91+
filters = GetFilters(controllerContext, allFilterItems);
7592

7693
// Cache the filter items based on the following criteria
7794
// 1. Are created statically (ex: via filter attributes, added to global filter list etc.)
@@ -90,12 +107,8 @@ public ControllerActionInvokerState GetState(ControllerContext controllerContext
90107
return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor);
91108
}
92109

93-
private IFilterMetadata[] GetFilters(ActionContext actionContext, List<FilterItem> staticFilterItems)
110+
private IFilterMetadata[] GetFilters(ActionContext actionContext, List<FilterItem> filterItems)
94111
{
95-
// Create a separate collection as we want to hold onto the statically defined filter items
96-
// in order to cache them
97-
var filterItems = new List<FilterItem>(staticFilterItems);
98-
99112
// Execute providers
100113
var context = new FilterProviderContext(actionContext, filterItems);
101114

test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,20 @@ public void GetFilters_CachesFilterFromFactory()
6363
new[] { new DefaultFilterProvider() });
6464
var filterDescriptors = controllerContext.ActionDescriptor.FilterDescriptors;
6565

66-
// Act - 1
66+
// Act & Assert
6767
var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
68-
69-
// Assert - 1
7068
Assert.Equal(2, filters.Length);
71-
var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
69+
var cachedFactoryCreatedFilter = Assert.IsType<TestFilter>(filters[0]); // Created by factory
7270
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
7371

74-
// Act - 2
75-
filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
72+
for (var i = 0; i < 5; i++)
73+
{
74+
filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
7675

77-
// Assert - 2
78-
Assert.Collection(
79-
filters,
80-
f => Assert.Same(request1Filter1, f), // Cached
81-
f => Assert.Same(staticFilter, f)); // Cached and the same statically created filter instance
76+
var currentFactoryCreatedFilter = filters[0];
77+
Assert.Same(currentFactoryCreatedFilter, cachedFactoryCreatedFilter); // Cached
78+
Assert.Same(staticFilter, filters[1]); // Cached
79+
}
8280
}
8381

8482
[Fact]
@@ -96,22 +94,18 @@ public void GetFilters_DoesNotCacheFiltersWithIsReusableFalse()
9694
new[] { new DefaultFilterProvider() });
9795
var filterDescriptors = controllerContext.ActionDescriptor.FilterDescriptors;
9896

99-
// Act - 1
100-
var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
97+
// Act & Assert
98+
IFilterMetadata previousFactoryCreatedFilter = null;
99+
for (var i = 0; i < 5; i++)
100+
{
101+
var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
101102

102-
// Assert - 1
103-
Assert.Equal(2, filters.Length);
104-
var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
105-
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
103+
var currentFactoryCreatedFilter = filters[0];
104+
Assert.NotSame(currentFactoryCreatedFilter, previousFactoryCreatedFilter); // Never Cached
105+
Assert.Same(staticFilter, filters[1]); // Cached
106106

107-
// Act - 2
108-
filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
109-
110-
// Assert - 2
111-
Assert.Collection(
112-
filters,
113-
f => Assert.NotSame(request1Filter1, f), // Created by factory again
114-
f => Assert.Same(staticFilter, f)); // Cached and the same statically created filter instance
107+
previousFactoryCreatedFilter = currentFactoryCreatedFilter;
108+
}
115109
}
116110

117111
[Fact]

0 commit comments

Comments
 (0)