Merge branch 'release' into dev

This commit is contained in:
Kiran Challa 2016-04-23 06:57:08 -07:00
commit 4e8e30437f
4 changed files with 253 additions and 233 deletions

View File

@ -45,96 +45,59 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
public Entry GetCacheEntry(ControllerContext controllerContext) public ControllerActionInvokerState GetState(ControllerContext controllerContext)
{ {
// Filter instances from statically defined filter descriptors + from filter providers
IFilterMetadata[] filters;
var cache = CurrentCache; var cache = CurrentCache;
var actionDescriptor = controllerContext.ActionDescriptor; var actionDescriptor = controllerContext.ActionDescriptor;
Entry entry; Entry cacheEntry;
if (cache.Entries.TryGetValue(actionDescriptor, out entry)) if (cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
{ {
return entry; filters = GetFilters(controllerContext, cacheEntry.FilterItems);
return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor);
} }
var executor = ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo); var executor = ObjectMethodExecutor.Create(
actionDescriptor.MethodInfo,
actionDescriptor.ControllerTypeInfo);
var items = new List<FilterItem>(actionDescriptor.FilterDescriptors.Count); var staticFilterItems = 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])); staticFilterItems.Add(new FilterItem(actionDescriptor.FilterDescriptors[i]));
} }
ExecuteProviders(controllerContext, items); filters = GetFilters(controllerContext, staticFilterItems);
var filters = ExtractFilters(items); // Cache the filter items based on the following criteria
// 1. Are created statically (ex: via filter attributes, added to global filter list etc.)
var allFiltersCached = true; // 2. Are re-usable
for (var i = 0; i < items.Count; i++) for (var i = 0; i < staticFilterItems.Count; i++)
{ {
var item = items[i]; var item = staticFilterItems[i];
if (!item.IsReusable) if (!item.IsReusable)
{ {
item.Filter = null; item.Filter = null;
allFiltersCached = false;
} }
} }
cacheEntry = new Entry(staticFilterItems, executor);
cache.Entries.TryAdd(actionDescriptor, cacheEntry);
if (allFiltersCached) return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor);
{
entry = new Entry(filters, null, executor);
}
else
{
entry = new Entry(null, items, executor);
}
cache.Entries.TryAdd(actionDescriptor, entry);
return entry;
} }
public IFilterMetadata[] GetFilters(ControllerContext controllerContext) private IFilterMetadata[] GetFilters(ActionContext actionContext, List<FilterItem> staticFilterItems)
{ {
var entry = GetCacheEntry(controllerContext); // Create a separate collection as we want to hold onto the statically defined filter items
return GetFiltersFromEntry(entry, controllerContext); // in order to cache them
} var filterItems = new List<FilterItem>(staticFilterItems);
public ObjectMethodExecutor GetControllerActionMethodExecutor(ControllerContext controllerContext) // Execute providers
{ var context = new FilterProviderContext(actionContext, filterItems);
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.FilterItems.Count);
for (var i = 0; i < entry.FilterItems.Count; i++)
{
var item = entry.FilterItems[i];
if (item.IsReusable)
{
items.Add(item);
}
else
{
items.Add(new FilterItem(item.Descriptor));
}
}
ExecuteProviders(actionContext, items);
return ExtractFilters(items);
}
private void ExecuteProviders(ActionContext actionContext, List<FilterItem> items)
{
var context = new FilterProviderContext(actionContext, items);
for (var i = 0; i < _filterProviders.Length; i++) for (var i = 0; i < _filterProviders.Length; i++)
{ {
@ -145,14 +108,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
_filterProviders[i].OnProvidersExecuted(context); _filterProviders[i].OnProvidersExecuted(context);
} }
}
private IFilterMetadata[] ExtractFilters(List<FilterItem> items) // Extract filter instances from statically defined filters and filter providers
{
var count = 0; var count = 0;
for (var i = 0; i < items.Count; i++) for (var i = 0; i < filterItems.Count; i++)
{ {
if (items[i].Filter != null) if (filterItems[i].Filter != null)
{ {
count++; count++;
} }
@ -166,9 +127,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
var filters = new IFilterMetadata[count]; var filters = new IFilterMetadata[count];
var filterIndex = 0; var filterIndex = 0;
for (int i = 0; i < items.Count; i++) for (int i = 0; i < filterItems.Count; i++)
{ {
var filter = items[i].Filter; var filter = filterItems[i].Filter;
if (filter != null) if (filter != null)
{ {
filters[filterIndex++] = filter; filters[filterIndex++] = filter;
@ -186,25 +147,38 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Version = version; Version = version;
} }
public ConcurrentDictionary<ActionDescriptor, Entry> Entries { get; } = public ConcurrentDictionary<ActionDescriptor, Entry> Entries { get; } =
new ConcurrentDictionary<ActionDescriptor, Entry>(); new ConcurrentDictionary<ActionDescriptor, Entry>();
public int Version { get; } public int Version { get; }
} }
public struct Entry private struct Entry
{ {
public Entry(IFilterMetadata[] filters, List<FilterItem> items, ObjectMethodExecutor executor) public Entry(List<FilterItem> items, ObjectMethodExecutor executor)
{ {
FilterItems = items; FilterItems = items;
Filters = filters;
ActionMethodExecutor = executor; ActionMethodExecutor = executor;
} }
public IFilterMetadata[] Filters { get; }
public List<FilterItem> FilterItems { get; } public List<FilterItem> FilterItems { get; }
public ObjectMethodExecutor ActionMethodExecutor { 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; }
}
} }
} }

View File

@ -123,9 +123,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public virtual async Task InvokeAsync() public virtual async Task InvokeAsync()
{ {
var cacheEntry = _controllerActionInvokerCache.GetCacheEntry(Context); var controllerActionInvokerState = _controllerActionInvokerCache.GetState(Context);
_filters = _controllerActionInvokerCache.GetFiltersFromEntry(cacheEntry, Context); _filters = controllerActionInvokerState.Filters;
_controllerActionMethodExecutor = cacheEntry.ActionMethodExecutor; _controllerActionMethodExecutor = controllerActionInvokerState.ActionMethodExecutor;
_cursor = new FilterCursor(_filters); _cursor = new FilterCursor(_filters);
Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors; Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors;

View File

@ -1,34 +0,0 @@
// 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 Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class FilterItemOrderComparer : IComparer<FilterItem>
{
private static readonly FilterItemOrderComparer _comparer = new FilterItemOrderComparer();
public static FilterItemOrderComparer Comparer
{
get { return _comparer; }
}
public int Compare(FilterItem x, FilterItem y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
return FilterDescriptorOrderComparer.Comparer.Compare(x.Descriptor, y.Descriptor);
}
}
}

View File

@ -6,8 +6,8 @@ using System.Reflection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal namespace Microsoft.AspNetCore.Mvc.Internal
@ -18,198 +18,278 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public void GetFilters_CachesAllFilters() public void GetFilters_CachesAllFilters()
{ {
// Arrange // Arrange
var services = CreateServices(); var staticFilter1 = new TestFilter();
var cache = CreateCache(new DefaultFilterProvider()); var staticFilter2 = new TestFilter();
var controllerContext = CreateControllerContext(new[]
var action = new ControllerActionDescriptor()
{
FilterDescriptors = new[]
{ {
new FilterDescriptor(new TestFilter(), FilterScope.Action), new FilterDescriptor(staticFilter1, FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action), new FilterDescriptor(staticFilter2, FilterScope.Action),
}, });
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod( var controllerActionInvokerCache = CreateControllerActionInvokerCache(
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)), controllerContext,
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo() new[] { new DefaultFilterProvider() });
};
var context = new ControllerContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
action));
// Act - 1 // Act - 1
var filters1 = cache.GetFilters(context); var request1Filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
// Assert - 1 // Assert - 1
Assert.Collection( Assert.Collection(
filters1, request1Filters,
f => Assert.Same(action.FilterDescriptors[0].Filter, f), // Copied by provider f => Assert.Same(staticFilter1, f),
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider f => Assert.Same(staticFilter2, f));
// Act - 2 // Act - 2
var filters2 = cache.GetFilters(context); var request2Filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
Assert.Same(filters1, filters2);
// Assert - 2
Assert.Collection( Assert.Collection(
filters2, request2Filters,
f => Assert.Same(action.FilterDescriptors[0].Filter, f), // Cached f => Assert.Same(staticFilter1, f),
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Cached f => Assert.Same(staticFilter2, f));
} }
[Fact] [Fact]
public void GetFilters_CachesFilterFromFactory() public void GetFilters_CachesFilterFromFactory()
{ {
// Arrange // Arrange
var services = CreateServices(); var staticFilter = new TestFilter();
var cache = CreateCache(new DefaultFilterProvider()); var controllerContext = CreateControllerContext(new[]
var action = new ControllerActionDescriptor()
{
FilterDescriptors = new[]
{ {
new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action), new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action), new FilterDescriptor(staticFilter, FilterScope.Action),
}, });
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod( var controllerActionInvokerCache = CreateControllerActionInvokerCache(
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)), controllerContext,
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo() new[] { new DefaultFilterProvider() });
}; var filterDescriptors = controllerContext.ActionDescriptor.FilterDescriptors;
var context = new ControllerContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
action));
// Act - 1 // Act - 1
var filters1 = cache.GetFilters(context); var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
// Assert - 1 // Assert - 1
Assert.Collection( Assert.Equal(2, filters.Length);
filters1, var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
f => Assert.NotSame(action.FilterDescriptors[0].Filter, f), // Created by factory Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider
// Act - 2 // Act - 2
var filters2 = cache.GetFilters(context); filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
Assert.Same(filters1, filters2);
// Assert - 2
Assert.Collection( Assert.Collection(
filters2, filters,
f => Assert.Same(filters1[0], f), // Cached f => Assert.Same(request1Filter1, f), // Cached
f => Assert.Same(filters1[1], f)); // Cached f => Assert.Same(staticFilter, f)); // Cached and the same statically created filter instance
} }
[Fact] [Fact]
public void GetFilters_DoesNotCacheFiltersWithIsReusableFalse() public void GetFilters_DoesNotCacheFiltersWithIsReusableFalse()
{ {
// Arrange // Arrange
var services = CreateServices(); var staticFilter = new TestFilter();
var cache = CreateCache(new DefaultFilterProvider()); var controllerContext = CreateControllerContext(new[]
var action = new ControllerActionDescriptor()
{
FilterDescriptors = new[]
{ {
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action), new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action), new FilterDescriptor(staticFilter, FilterScope.Action),
}, });
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod( var controllerActionInvokerCache = CreateControllerActionInvokerCache(
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)), controllerContext,
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo() new[] { new DefaultFilterProvider() });
}; var filterDescriptors = controllerContext.ActionDescriptor.FilterDescriptors;
var context = new ControllerContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
action));
// Act - 1 // Act - 1
var filters1 = cache.GetFilters(context); var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
// Assert - 1 // Assert - 1
Assert.Collection( Assert.Equal(2, filters.Length);
filters1, var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
f => Assert.NotSame(action.FilterDescriptors[0].Filter, f), // Created by factory Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider
// Act - 2 // Act - 2
var filters2 = cache.GetFilters(context); filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
Assert.NotSame(filters1, filters2);
// Assert - 2
Assert.Collection( Assert.Collection(
filters2, filters,
f => Assert.NotSame(filters1[0], f), // Created by factory (again) f => Assert.NotSame(request1Filter1, f), // Created by factory again
f => Assert.Same(filters1[1], f)); // Cached f => Assert.Same(staticFilter, f)); // Cached and the same statically created filter instance
} }
[Fact] [Fact]
public void GetControllerActionMethodExecutor_CachesExecutor() public void GetControllerActionMethodExecutor_CachesActionMethodExecutor()
{ {
// Arrange // Arrange
var services = CreateServices(); var filter = new TestFilter();
var cache = CreateCache(new DefaultFilterProvider()); var controllerContext = CreateControllerContext(new[]
{
new FilterDescriptor(filter, FilterScope.Action)
});
var controllerActionInvokerCache = CreateControllerActionInvokerCache(
controllerContext,
new[] { new DefaultFilterProvider() });
var action = new ControllerActionDescriptor() // Act
{ var cacheEntry1 = controllerActionInvokerCache.GetState(controllerContext);
FilterDescriptors = new[] var cacheEntry2 = controllerActionInvokerCache.GetState(controllerContext);
// Assert
Assert.Same(cacheEntry1.ActionMethodExecutor, cacheEntry2.ActionMethodExecutor);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void GetFilters_FiltersAddedByFilterProviders_AreNeverCached(bool reusable)
{
// Arrange
var customFilterProvider = new TestFilterProvider(
providerExecuting: (providerContext) =>
{
var filter = new TestFilter(providerContext.ActionContext.HttpContext.Items["name"] as string);
providerContext.Results.Add(
new FilterItem(new FilterDescriptor(filter, FilterScope.Global), filter)
{
IsReusable = reusable
});
},
providerExecuted: null);
var staticFilter = new TestFilter();
var controllerContext = CreateControllerContext(new[]
{ {
new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action), new FilterDescriptor(new TestFilterFactory() { IsReusable = false }, FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action), new FilterDescriptor(staticFilter, FilterScope.Action),
}, });
MethodInfo = typeof(ControllerActionInvokerCache).GetMethod( var controllerActionInvokerCache = CreateControllerActionInvokerCache(
nameof(ControllerActionInvokerCache.GetControllerActionMethodExecutor)), controllerContext,
ControllerTypeInfo = typeof(ControllerActionInvokerCache).GetTypeInfo() new IFilterProvider[] { new DefaultFilterProvider(), customFilterProvider });
var filterDescriptors = controllerContext.ActionDescriptor.FilterDescriptors;
};
var context = new ControllerContext(
new ActionContext(new DefaultHttpContext(),
new RouteData(),
action));
// Act - 1 // Act - 1
var executor1 = cache.GetControllerActionMethodExecutor(context); controllerContext.HttpContext.Items["name"] = "foo";
var filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
Assert.NotNull(executor1); // Assert - 1
Assert.Equal(3, filters.Length);
var filters1 = cache.GetFilters(context); var request1Filter1 = Assert.IsType<TestFilter>(filters[0]); // Created by factory
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
Assert.NotNull(filters1); var request1Filter3 = Assert.IsType<TestFilter>(filters[2]); // Created by custom filter provider
Assert.Equal("foo", request1Filter3.Data);
// Act - 2 // Act - 2
var executor2 = cache.GetControllerActionMethodExecutor(context); controllerContext.HttpContext.Items["name"] = "bar";
filters = controllerActionInvokerCache.GetState(controllerContext).Filters;
Assert.Same(executor1, executor2); // Assert -2
Assert.Equal(3, filters.Length);
var request2Filter1 = Assert.IsType<TestFilter>(filters[0]);
Assert.NotSame(request1Filter1, request2Filter1); // Created by factory
Assert.Same(staticFilter, filters[1]); // Cached and the same statically created filter instance
var request2Filter3 = Assert.IsType<TestFilter>(filters[2]);
Assert.NotSame(request1Filter3, request2Filter3); // Created by custom filter provider again
Assert.Equal("bar", request2Filter3.Data);
} }
private class TestFilter : IFilterMetadata private class TestFilter : IFilterMetadata
{ {
public TestFilter()
{
}
public TestFilter(string data)
{
Data = data;
}
public string Data { get; }
} }
private class TestFilterFactory : IFilterFactory private class TestFilterFactory : IFilterFactory
{ {
private TestFilter testFilter;
public bool IsReusable { get; set; } public bool IsReusable { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{ {
return new TestFilter(); if (IsReusable)
{
if (testFilter == null)
{
testFilter = new TestFilter();
}
return testFilter;
}
else
{
return new TestFilter();
}
} }
} }
private static IServiceProvider CreateServices() private class TestFilterProvider : IFilterProvider
{ {
return new ServiceCollection().BuildServiceProvider(); private readonly Action<FilterProviderContext> _providerExecuting;
private readonly Action<FilterProviderContext> _providerExecuted;
public TestFilterProvider(
Action<FilterProviderContext> providerExecuting,
Action<FilterProviderContext> providerExecuted,
int order = 0)
{
_providerExecuting = providerExecuting;
_providerExecuted = providerExecuted;
Order = order;
}
public int Order { get; }
public void OnProvidersExecuting(FilterProviderContext context)
{
_providerExecuting?.Invoke(context);
}
public void OnProvidersExecuted(FilterProviderContext context)
{
_providerExecuted?.Invoke(context);
}
} }
private static ControllerActionInvokerCache CreateCache(params IFilterProvider[] providers) private class TestController
{ {
var services = CreateServices(); public void Index()
var descriptorProvider = new ActionDescriptorCollectionProvider(services); {
return new ControllerActionInvokerCache(descriptorProvider, providers); }
}
private class CustomActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
{
public CustomActionDescriptorCollectionProvider(ControllerActionDescriptor[] actionDescriptors)
{
ActionDescriptors = new ActionDescriptorCollection(actionDescriptors, version: 1);
}
public ActionDescriptorCollection ActionDescriptors { get; }
}
private static ControllerActionInvokerCache CreateControllerActionInvokerCache(
ControllerContext controllerContext,
IFilterProvider[] filterProviders)
{
var descriptorProvider = new CustomActionDescriptorCollectionProvider(
new[] { controllerContext.ActionDescriptor });
return new ControllerActionInvokerCache(descriptorProvider, filterProviders);
}
private static ControllerContext CreateControllerContext(FilterDescriptor[] filterDescriptors)
{
var actionDescriptor = new ControllerActionDescriptor()
{
FilterDescriptors = filterDescriptors,
MethodInfo = typeof(TestController).GetMethod(nameof(TestController.Index)),
ControllerTypeInfo = typeof(TestController).GetTypeInfo()
};
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), actionDescriptor);
return new ControllerContext(actionContext);
} }
} }
} }