Add optional filter caching

This commit is contained in:
Ryan Nowak 2016-01-07 08:06:11 -08:00
parent c5cab7a786
commit 70cee90186
19 changed files with 499 additions and 122 deletions

View File

@ -1,16 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<<<<<<< HEAD
<Project ToolsVersion="14.0.24720" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.24720</VisualStudioVersion>
=======
<Project ToolsVersion="14.0.24711" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.24711</VisualStudioVersion>
>>>>>>> CR feedback
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<<<<<<< HEAD
<ProjectGuid>45F6B3B6-D114-4D77-84D6-561B3957F341</ProjectGuid>
=======
<ProjectGuid>e26979a8-56fb-41f7-8da5-a49a570acd39</ProjectGuid>
>>>>>>> CR feedback
<RootNamespace>MvcSubAreaSample.Web</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<<<<<<< HEAD
=======
>>>>>>> CR feedback
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>

View File

@ -6,10 +6,19 @@ using System.Diagnostics;
namespace Microsoft.AspNet.Mvc.Filters
{
// Used to flow filters back from the FilterProviderContext
/// <summary>
/// Used to associate executable filters with <see cref="IFilterMetadata"/> instances
/// as part of <see cref="FilterProviderContext"/>. An <see cref="IFilterProvider"/> should
/// inspect <see cref="FilterProviderContext.Results"/> and set <see cref="Filter"/> and
/// <see cref="IsReusable"/> as appropriate.
/// </summary>
[DebuggerDisplay("FilterItem: {Filter}")]
public class FilterItem
{
/// <summary>
/// Creates a new <see cref="FilterItem"/>.
/// </summary>
/// <param name="descriptor">The <see cref="FilterDescriptor"/>.</param>
public FilterItem(FilterDescriptor descriptor)
{
if (descriptor == null)
@ -20,6 +29,11 @@ namespace Microsoft.AspNet.Mvc.Filters
Descriptor = descriptor;
}
/// <summary>
/// Creates a new <see cref="FilterItem"/>.
/// </summary>
/// <param name="descriptor">The <see cref="FilterDescriptor"/>.</param>
/// <param name="filter"></param>
public FilterItem(FilterDescriptor descriptor, IFilterMetadata filter)
: this(descriptor)
{
@ -31,8 +45,19 @@ namespace Microsoft.AspNet.Mvc.Filters
Filter = filter;
}
public FilterDescriptor Descriptor { get; set; }
/// <summary>
/// Gets the <see cref="FilterDescriptor"/> containing the filter metadata.
/// </summary>
public FilterDescriptor Descriptor { get; }
/// <summary>
/// Gets or sets the executable <see cref="IFilterMetadata"/> associated with <see cref="Descriptor"/>.
/// </summary>
public IFilterMetadata Filter { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not <see cref="Filter"/> can be reused across requests.
/// </summary>
public bool IsReusable { get; set; }
}
}

View File

@ -5,8 +5,22 @@ using System;
namespace Microsoft.AspNet.Mvc.Filters
{
/// <summary>
/// An interface for for filter metadata which can create an instance of an executable filter.
/// </summary>
public interface IFilterFactory : IFilterMetadata
{
/// <summary>
/// Gets a value that indicates if the result of <see cref="CreateInstance(IServiceProvider)"/>
/// can be reused across requests.
/// </summary>
bool IsReusable { get; }
/// <summary>
/// Creates an instance of the executable filter.
/// </summary>
/// <param name="serviceProvider">The request <see cref="IServiceProvider"/>.</param>
/// <returns>An instance of the executable filter.</returns>
IFilterMetadata CreateInstance(IServiceProvider serviceProvider);
}
}

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.Logging;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
@ -25,11 +26,11 @@ namespace Microsoft.AspNet.Mvc.Controllers
public ControllerActionInvoker(
ActionContext actionContext,
IReadOnlyList<IFilterProvider> filterProviders,
FilterCache filterCache,
IControllerFactory controllerFactory,
ControllerActionDescriptor descriptor,
IReadOnlyList<IInputFormatter> inputFormatters,
IControllerActionArgumentBinder controllerActionArgumentBinder,
IControllerActionArgumentBinder argumentBinder,
IReadOnlyList<IModelBinder> modelBinders,
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
@ -38,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
int maxModelValidationErrors)
: base(
actionContext,
filterProviders,
filterCache,
inputFormatters,
modelBinders,
modelValidatorProviders,
@ -47,16 +48,6 @@ namespace Microsoft.AspNet.Mvc.Controllers
diagnosticSource,
maxModelValidationErrors)
{
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
if (filterProviders == null)
{
throw new ArgumentNullException(nameof(filterProviders));
}
if (controllerFactory == null)
{
throw new ArgumentNullException(nameof(controllerFactory));
@ -67,51 +58,22 @@ namespace Microsoft.AspNet.Mvc.Controllers
throw new ArgumentNullException(nameof(descriptor));
}
if (inputFormatters == null)
if (argumentBinder == null)
{
throw new ArgumentNullException(nameof(inputFormatters));
throw new ArgumentNullException(nameof(argumentBinder));
}
if (controllerActionArgumentBinder == null)
{
throw new ArgumentNullException(nameof(controllerActionArgumentBinder));
}
if (modelBinders == null)
{
throw new ArgumentNullException(nameof(modelBinders));
}
if (modelValidatorProviders == null)
{
throw new ArgumentNullException(nameof(modelValidatorProviders));
}
if (valueProviderFactories == null)
{
throw new ArgumentNullException(nameof(valueProviderFactories));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (diagnosticSource == null)
{
throw new ArgumentNullException(nameof(diagnosticSource));
}
_descriptor = descriptor;
_controllerFactory = controllerFactory;
_argumentBinder = controllerActionArgumentBinder;
_descriptor = descriptor;
_argumentBinder = argumentBinder;
if (descriptor.MethodInfo == null)
{
throw new ArgumentException(
Resources.FormatPropertyOfTypeCannotBeNull("MethodInfo",
typeof(ControllerActionDescriptor)),
"descriptor");
Resources.FormatPropertyOfTypeCannotBeNull(
nameof(descriptor.MethodInfo),
typeof(ControllerActionDescriptor)),
nameof(descriptor));
}
}

View File

@ -6,8 +6,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Logging;
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
private readonly IControllerActionArgumentBinder _argumentBinder;
private readonly IControllerFactory _controllerFactory;
private readonly IFilterProvider[] _filterProviders;
private readonly FilterCache _filterCache;
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
private readonly IReadOnlyList<IModelBinder> _modelBinders;
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
@ -30,14 +30,14 @@ namespace Microsoft.AspNet.Mvc.Controllers
public ControllerActionInvokerProvider(
IControllerFactory controllerFactory,
IEnumerable<IFilterProvider> filterProviders,
FilterCache filterCache,
IControllerActionArgumentBinder argumentBinder,
IOptions<MvcOptions> optionsAccessor,
ILoggerFactory loggerFactory,
DiagnosticSource diagnosticSource)
{
_controllerFactory = controllerFactory;
_filterProviders = filterProviders.OrderBy(item => item.Order).ToArray();
_filterCache = filterCache;
_argumentBinder = argumentBinder;
_inputFormatters = optionsAccessor.Value.InputFormatters.ToArray();
_modelBinders = optionsAccessor.Value.ModelBinders.ToArray();
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
context.Result = new ControllerActionInvoker(
context.ActionContext,
_filterProviders,
_filterCache,
_controllerFactory,
actionDescriptor,
_inputFormatters,

View File

@ -21,9 +21,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
public abstract class FilterActionInvoker : IActionInvoker
{
private static readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
private readonly IReadOnlyList<IFilterProvider> _filterProviders;
private readonly FilterCache _filterCache;
private readonly IReadOnlyList<IInputFormatter> _inputFormatters;
private readonly IReadOnlyList<IModelBinder> _modelBinders;
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
@ -49,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
public FilterActionInvoker(
ActionContext actionContext,
IReadOnlyList<IFilterProvider> filterProviders,
FilterCache filterCache,
IReadOnlyList<IInputFormatter> inputFormatters,
IReadOnlyList<IModelBinder> modelBinders,
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
@ -63,9 +61,9 @@ namespace Microsoft.AspNet.Mvc.Controllers
throw new ArgumentNullException(nameof(actionContext));
}
if (filterProviders == null)
if (filterCache == null)
{
throw new ArgumentNullException(nameof(filterProviders));
throw new ArgumentNullException(nameof(filterCache));
}
if (inputFormatters == null)
@ -100,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
Context = new ControllerContext(actionContext);
_filterProviders = filterProviders;
_filterCache = filterCache;
_inputFormatters = inputFormatters;
_modelBinders = modelBinders;
_modelValidatorProviders = modelValidatorProviders;
@ -184,51 +182,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
private IFilterMetadata[] GetFilters()
{
var filterDescriptors = Context.ActionDescriptor.FilterDescriptors;
var items = new List<FilterItem>(filterDescriptors.Count);
for (var i = 0; i < filterDescriptors.Count; i++)
{
items.Add(new FilterItem(filterDescriptors[i]));
}
var context = new FilterProviderContext(Context, items);
for (var i = 0; i < _filterProviders.Count; i++)
{
_filterProviders[i].OnProvidersExecuting(context);
}
for (var i = _filterProviders.Count - 1; i >= 0; i--)
{
_filterProviders[i].OnProvidersExecuted(context);
}
var count = 0;
for (var i = 0; i < items.Count; i++)
{
if (items[i].Filter != null)
{
count++;
}
}
if (count == 0)
{
return EmptyFilterArray;
}
else
{
var filters = new IFilterMetadata[count];
for (int i = 0, j = 0; i < items.Count; i++)
{
var filter = items[i].Filter;
if (filter != null)
{
filters[j++] = filter;
}
}
return filters;
}
return _filterCache.GetFilters(Context);
}
private Task InvokeAllAuthorizationFiltersAsync()

View File

@ -119,6 +119,7 @@ namespace Microsoft.Extensions.DependencyInjection
// These are stateless
services.TryAddSingleton<IControllerActionArgumentBinder, DefaultControllerActionArgumentBinder>();
services.TryAddSingleton<FilterCache>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());

View File

@ -50,11 +50,13 @@ namespace Microsoft.AspNet.Mvc.Filters
if (filterFactory == null)
{
filterItem.Filter = filter;
filterItem.IsReusable = true;
}
else
{
var services = context.ActionContext.HttpContext.RequestServices;
filterItem.Filter = filterFactory.CreateInstance(services);
filterItem.IsReusable = filterFactory.IsReusable;
if (filterItem.Filter == null)
{

View File

@ -15,6 +15,9 @@ namespace Microsoft.AspNet.Mvc
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class FormatFilterAttribute : Attribute, IFilterFactory
{
/// <inheritdoc />
public bool IsReusable => true;
/// <summary>
/// Creates an instance of <see cref="FormatFilter"/>.
/// </summary>

View File

@ -0,0 +1,213 @@
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Infrastructure;
namespace Microsoft.AspNet.Mvc.Internal
{
public class FilterCache
{
private readonly IFilterMetadata[] EmptyFilterArray = new IFilterMetadata[0];
private readonly IActionDescriptorCollectionProvider _collectionProvider;
private readonly IFilterProvider[] _filterProviders;
private volatile InnerCache _currentCache;
public FilterCache(
IActionDescriptorCollectionProvider collectionProvider,
IEnumerable<IFilterProvider> 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 IFilterMetadata[] GetFilters(ActionContext actionContext)
{
var cache = CurrentCache;
var actionDescriptor = actionContext.ActionDescriptor;
CacheEntry entry;
if (cache.Entries.TryGetValue(actionDescriptor, out entry))
{
return GetFiltersFromEntry(entry, actionContext);
}
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);
var filters = ExtractFilters(items);
var allFiltersCached = true;
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
if (!item.IsReusable)
{
item.Filter = null;
allFiltersCached = false;
}
}
if (allFiltersCached)
{
entry = new CacheEntry(filters);
}
else
{
entry = new CacheEntry(items);
}
cache.Entries.TryAdd(actionDescriptor, entry);
return filters;
}
private IFilterMetadata[] GetFiltersFromEntry(CacheEntry entry, ActionContext actionContext)
{
Debug.Assert(entry.Filters != null || entry.Items != null);
if (entry.Filters != null)
{
return entry.Filters;
}
var items = new List<FilterItem>(entry.Items.Length);
for (var i = 0; i < entry.Items.Length; i++)
{
var item = entry.Items[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++)
{
_filterProviders[i].OnProvidersExecuting(context);
}
for (var i = _filterProviders.Length - 1; i >= 0; i--)
{
_filterProviders[i].OnProvidersExecuted(context);
}
}
private IFilterMetadata[] ExtractFilters(List<FilterItem> items)
{
var count = 0;
for (var i = 0; i < items.Count; i++)
{
if (items[i].Filter != null)
{
count++;
}
}
if (count == 0)
{
return EmptyFilterArray;
}
else
{
var filters = new IFilterMetadata[count];
for (int i = 0, j = 0; i < items.Count; i++)
{
var filter = items[i].Filter;
if (filter != null)
{
filters[j++] = filter;
}
}
return filters;
}
}
private class InnerCache
{
public InnerCache(int version)
{
Version = version;
}
public ConcurrentDictionary<ActionDescriptor, CacheEntry> Entries { get; } =
new ConcurrentDictionary<ActionDescriptor, CacheEntry>();
public int Version { get; }
}
private struct CacheEntry
{
public CacheEntry(IFilterMetadata[] filters)
{
Filters = filters;
Items = null;
}
public CacheEntry(List<FilterItem> items)
{
Items = new FilterItem[items.Count];
for (var i = 0; i < Items.Length; i++)
{
var item = items[i];
if (item.IsReusable)
{
Items[i] = item;
}
else
{
Items[i] = new FilterItem(item.Descriptor);
}
}
Filters = null;
}
public IFilterMetadata[] Filters { get; }
public FilterItem[] Items { get; }
}
}
}

View File

@ -81,11 +81,12 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public string CacheProfileName { get; set; }
/// <summary>
/// The order of the filter.
/// </summary>
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
if (serviceProvider == null)

View File

@ -23,9 +23,13 @@ namespace Microsoft.AspNet.Mvc
ServiceType = type;
}
/// <inheritdoc />
public int Order { get; set; }
public Type ServiceType { get; private set; }
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{

View File

@ -29,8 +29,12 @@ namespace Microsoft.AspNet.Mvc
public Type ImplementationType { get; private set; }
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
if (serviceProvider == null)

View File

@ -34,6 +34,9 @@ namespace Microsoft.AspNet.Mvc.Cors
}
}
/// <inheritdoc />
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
if (serviceProvider == null)

View File

@ -24,6 +24,9 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable => true;
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{

View File

@ -19,8 +19,12 @@ namespace Microsoft.AspNet.Mvc
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAntiForgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<ValidateAntiforgeryTokenAuthorizationFilter>();

View File

@ -11,8 +11,14 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
/// Adds a filter which will save the <see cref="ITempDataDictionary"/> for a request.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SaveTempDataAttribute : Attribute, IFilterFactory
public class SaveTempDataAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public bool IsReusable => true;
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{

View File

@ -14,6 +14,7 @@ using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Routing;
@ -22,7 +23,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -2027,10 +2027,10 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
foreach (var filterMetadata in filters)
{
var filter = new FilterItem(
new FilterDescriptor(filterMetadata, FilterScope.Action),
filterMetadata);
context.Results.Add(filter);
context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action))
{
Filter = filterMetadata,
});
}
});
@ -2106,7 +2106,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
var invoker = new ControllerActionInvoker(
actionContext,
new List<IFilterProvider>(),
CreateFilterCache(),
controllerFactory.Object,
actionDescriptor,
new IInputFormatter[0],
@ -2222,11 +2222,18 @@ namespace Microsoft.AspNet.Mvc.Controllers
}
}
private static FilterCache CreateFilterCache(IFilterProvider[] filterProviders = null)
{
var services = new ServiceCollection().BuildServiceProvider();
var descriptorProvider = new DefaultActionDescriptorCollectionProvider(services);
return new FilterCache(descriptorProvider, filterProviders.AsEnumerable() ?? new List<IFilterProvider>());
}
private class TestControllerActionInvoker : ControllerActionInvoker
{
public TestControllerActionInvoker(
ActionContext actionContext,
IFilterProvider[] filterProvider,
IFilterProvider[] filterProviders,
MockControllerFactory controllerFactory,
ControllerActionDescriptor descriptor,
IReadOnlyList<IInputFormatter> inputFormatters,
@ -2239,7 +2246,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
int maxAllowedErrorsInModelState)
: base(
actionContext,
filterProvider,
CreateFilterCache(filterProviders),
controllerFactory,
descriptor,
inputFormatters,

View File

@ -0,0 +1,157 @@
// 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 Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Controllers;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNet.Mvc.Internal
{
public class FilterCacheTest
{
[Fact]
public void GetFilters_CachesAllFilters()
{
// Arrange
var services = CreateServices();
var cache = CreateCache(new DefaultFilterProvider());
var action = new ControllerActionDescriptor()
{
FilterDescriptors = new[]
{
new FilterDescriptor(new TestFilter(), FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action),
},
};
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
// Act - 1
var filters1 = cache.GetFilters(context);
// Assert - 1
Assert.Collection(
filters1,
f => Assert.Same(action.FilterDescriptors[0].Filter, f), // Copied by provider
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider
// Act - 2
var filters2 = cache.GetFilters(context);
Assert.Same(filters1, filters2);
Assert.Collection(
filters2,
f => Assert.Same(action.FilterDescriptors[0].Filter, f), // Cached
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Cached
}
[Fact]
public void GetFilters_CachesFilterFromFactory()
{
// Arrange
var services = CreateServices();
var cache = CreateCache(new DefaultFilterProvider());
var action = new ControllerActionDescriptor()
{
FilterDescriptors = new[]
{
new FilterDescriptor(new TestFilterFactory() { IsReusable = true }, FilterScope.Action),
new FilterDescriptor(new TestFilter(), FilterScope.Action),
},
};
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
// Act - 1
var filters1 = cache.GetFilters(context);
// Assert - 1
Assert.Collection(
filters1,
f => Assert.NotSame(action.FilterDescriptors[0].Filter, f), // Created by factory
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider
// Act - 2
var filters2 = cache.GetFilters(context);
Assert.Same(filters1, filters2);
Assert.Collection(
filters2,
f => Assert.Same(filters1[0], f), // Cached
f => Assert.Same(filters1[1], f)); // Cached
}
[Fact]
public void GetFilters_DoesNotCacheFiltersWithIsReusableFalse()
{
// 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),
},
};
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), action);
// Act - 1
var filters1 = cache.GetFilters(context);
// Assert - 1
Assert.Collection(
filters1,
f => Assert.NotSame(action.FilterDescriptors[0].Filter, f), // Created by factory
f => Assert.Same(action.FilterDescriptors[1].Filter, f)); // Copied by provider
// Act - 2
var filters2 = cache.GetFilters(context);
Assert.NotSame(filters1, filters2);
Assert.Collection(
filters2,
f => Assert.NotSame(filters1[0], f), // Created by factory (again)
f => Assert.Same(filters1[1], f)); // Cached
}
private class TestFilter : IFilterMetadata
{
}
private class TestFilterFactory : IFilterFactory
{
public bool IsReusable { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new TestFilter();
}
}
private static IServiceProvider CreateServices()
{
return new ServiceCollection().BuildServiceProvider();
}
private static FilterCache CreateCache(params IFilterProvider[] providers)
{
var services = CreateServices();
var descriptorProvider = new DefaultActionDescriptorCollectionProvider(services);
return new FilterCache(descriptorProvider, providers);
}
}
}