moving global filters to options

This commit is contained in:
Ryan Nowak 2014-09-04 13:20:02 -07:00
parent a468986155
commit 7ed2de297e
30 changed files with 848 additions and 35 deletions

13
Mvc.sln
View File

@ -74,6 +74,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AntiForgeryWebSite", "test\
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AddServicesWebSite", "test\WebSites\AddServicesWebSite\AddServicesWebSite.kproj", "{6A0B65CE-6B01-40D0-840D-EFF3680D1547}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FiltersWebSite", "test\WebSites\FiltersWebSite\FiltersWebSite.kproj", "{1976AC4A-FEA4-4587-A158-D9F79736D2B6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -374,6 +376,16 @@ Global
{6A0B65CE-6B01-40D0-840D-EFF3680D1547}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{6A0B65CE-6B01-40D0-840D-EFF3680D1547}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{6A0B65CE-6B01-40D0-840D-EFF3680D1547}.Release|x86.ActiveCfg = Release|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Release|Any CPU.Build.0 = Release|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1976AC4A-FEA4-4587-A158-D9F79736D2B6}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -409,5 +421,6 @@ Global
{C6E5AFFA-890A-448F-8DE3-878B1D3C9FC7} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A353B17E-A940-4CE8-8BF9-179E24A9041F} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{6A0B65CE-6B01-40D0-840D-EFF3680D1547} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -15,6 +15,7 @@
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>51929</DevelopmentServerPort>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DevelopmentServerPort>57394</DevelopmentServerPort>

View File

@ -4,13 +4,13 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using MvcSample.Web.Filters;
using MvcSample.Web.Services;
#if ASPNET50
using Autofac;
using Microsoft.Framework.DependencyInjection.Autofac;
using Microsoft.Framework.OptionsModel;
#endif
namespace MvcSample.Web
@ -42,6 +42,10 @@ namespace MvcSample.Web
// sample's assemblies are loaded. This prevents loading controllers from other assemblies
// when the sample is used in the Functional Tests.
services.AddTransient<IControllerAssemblyProvider, TestAssemblyProvider<Startup>>();
services.SetupOptions<MvcOptions>(options =>
{
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
});
// Create the autofac container
ContainerBuilder builder = new ContainerBuilder();
@ -72,6 +76,11 @@ namespace MvcSample.Web
// sample's assemblies are loaded. This prevents loading controllers from other assemblies
// when the sample is used in the Functional Tests.
services.AddTransient<IControllerAssemblyProvider, TestAssemblyProvider<Startup>>();
services.SetupOptions<MvcOptions>(options =>
{
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
});
});
}

View File

@ -12,7 +12,7 @@ using Microsoft.AspNet.Mvc.Rendering;
namespace Microsoft.AspNet.Mvc
{
public class Controller : IActionFilter, IAsyncActionFilter
public class Controller : IActionFilter, IAsyncActionFilter, IOrderedFilter
{
private DynamicViewData _viewBag;
@ -80,6 +80,15 @@ namespace Microsoft.AspNet.Mvc
}
}
int IOrderedFilter.Order
{
get
{
// Controller-filter methods run closest the action by default.
return int.MaxValue;
}
}
/// <summary>
/// Creates a <see cref="ViewResult"/> object that renders a view to the response.
/// </summary>

View File

@ -10,12 +10,9 @@ namespace Microsoft.AspNet.Mvc.Filters
{
public class DefaultFilterProvider : INestedProvider<FilterProviderContext>
{
private readonly ITypeActivator _typeActivator;
public DefaultFilterProvider(IServiceProvider serviceProvider, ITypeActivator typeActivator)
public DefaultFilterProvider(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
_typeActivator = typeActivator;
}
public int Order
@ -78,15 +75,6 @@ namespace Microsoft.AspNet.Mvc.Filters
private void InsertControllerAsFilter(FilterProviderContext context, IFilter controllerFilter)
{
// If the controller implements a filter, and doesn't specify order, then it should
// run closest to the action.
var order = Int32.MaxValue;
var orderedControllerFilter = controllerFilter as IOrderedFilter;
if (orderedControllerFilter != null)
{
order = orderedControllerFilter.Order;
}
var descriptor = new FilterDescriptor(controllerFilter, FilterScope.Controller);
var item = new FilterItem(descriptor, controllerFilter);

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Filters
{
/// <summary>
/// An implementation of <see cref="IGlobalFilterProvider"/> based on <see cref="MvcOptions"/>.
/// </summary>
public class DefaultGlobalFilterProvider : IGlobalFilterProvider
{
private readonly IReadOnlyList<IFilter> _filters;
/// <summary>
/// Creates a new instance of <see cref="DefaultGlobalFilterProvider"/>.
/// </summary>
/// <param name="optionsAccessor">The options accessor for <see cref="MvcOptions"/>.</param>
public DefaultGlobalFilterProvider(IOptionsAccessor<MvcOptions> optionsAccessor)
{
var filters = optionsAccessor.Options.Filters;
_filters = filters.ToList();
}
/// <inheritdoc />
public IReadOnlyList<IFilter> Filters
{
get
{
return _filters;
}
}
}
}

View File

@ -0,0 +1,105 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Extension methods for adding filters to the global filters collection.
/// </summary>
public static class FilterCollectionExtensions
{
/// <summary>
/// Adds a type representing an <see cref="IFilter"/> to a filter collection.
/// </summary>
/// <param name="filters">A collection of <see cref="IFilter"/>.</param>
/// <param name="filterType">Type representing an <see cref="IFilter"/>.</param>
/// <returns>An <see cref="IFilter"/> representing the added type.</returns>
/// <remarks>
/// Filter instances will be created using <see cref="Microsoft.Framework.DependencyInjection.ITypeActivator"/>.
/// Use <see cref="AddService(ICollection{IFilter}, Type)"/> to register a service as a filter.
/// </remarks>
public static IFilter Add(
[NotNull] this ICollection<IFilter> filters,
[NotNull] Type filterType)
{
return Add(filters, filterType, order: 0);
}
/// <summary>
/// Adds a type representing an <see cref="IFilter"/> to a filter collection.
/// </summary>
/// <param name="filters">A collection of <see cref="IFilter"/>.</param>
/// <param name="filterType">Type representing an <see cref="IFilter"/>.</param>
/// <param name="order">The order of the added filter.</param>
/// <returns>An <see cref="IFilter"/> representing the added type.</returns>
/// <remarks>
/// Filter instances will be created using <see cref="Microsoft.Framework.DependencyInjection.ITypeActivator"/>.
/// Use <see cref="AddService(ICollection{IFilter}, Type)"/> to register a service as a filter.
/// </remarks>
public static IFilter Add(
[NotNull] this ICollection<IFilter> filters,
[NotNull] Type filterType,
int order)
{
if (!typeof(IFilter).IsAssignableFrom(filterType))
{
var message = Resources.FormatTypeMustDeriveFromType(filterType.FullName, typeof(IFilter).FullName);
throw new ArgumentException(message, nameof(filterType));
}
var filter = new TypeFilterAttribute(filterType) { Order = order };
filters.Add(filter);
return filter;
}
/// <summary>
/// Adds a type representing an <see cref="IFilter"/> to a filter collection.
/// </summary>
/// <param name="filters">A collection of <see cref="IFilter"/>.</param>
/// <param name="filterType">Type representing an <see cref="IFilter"/>.</param>
/// <returns>An <see cref="IFilter"/> representing the added service type.</returns>
/// <remarks>
/// Filter instances will created through dependency injection. Use
/// <see cref="AddService(ICollection{IFilter}, Type)"/> to register a service that will be created via
/// type activation.
/// </remarks>
public static IFilter AddService(
[NotNull] this ICollection<IFilter> filters,
[NotNull] Type filterType)
{
return AddService(filters, filterType, order: 0);
}
/// <summary>
/// Adds a type representing an <see cref="IFilter"/> to a filter collection.
/// </summary>
/// <param name="filters">A collection of <see cref="IFilter"/>.</param>
/// <param name="filterType">Type representing an <see cref="IFilter"/>.</param>
/// <param name="order">The order of the added filter.</param>
/// <returns>An <see cref="IFilter"/> representing the added service type.</returns>
/// <remarks>
/// Filter instances will created through dependency injection. Use
/// <see cref="AddService(ICollection{IFilter}, Type)"/> to register a service that will be created via
/// type activation.
/// </remarks>
public static IFilter AddService(
[NotNull] this ICollection<IFilter> filters,
[NotNull] Type filterType,
int order)
{
if (!typeof(IFilter).IsAssignableFrom(filterType))
{
var message = Resources.FormatTypeMustDeriveFromType(filterType.FullName, typeof(IFilter).FullName);
throw new ArgumentException(message, nameof(filterType));
}
var filter = new ServiceFilterAttribute(filterType) { Order = order };
filters.Add(filter);
return filter;
}
}
}

View File

@ -3,8 +3,34 @@
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Descriptor for an <see cref="IFilter"/>.
/// </summary>
/// <remarks>
/// <see cref="FilterDescriptor"/> describes an <see cref="IFilter"/> with an order and scope.
///
/// Order and scope control the execution order of filters. Filters with a higher value of Order execute
/// later in the pipeline.
///
/// When filters have the same Order, the Scope value is used to determine the order of execution. Filters
/// with a higher value of Scope execute later in the pipeline. See <see cref="FilterScope"/> for commonly
/// used scopes.
///
/// For <see cref="IExceptionFilter"/> implementions, the filter runs only after an exception has occurred,
/// and so the observed order of execution will be opposite that of other filters.
/// </remarks>
public class FilterDescriptor
{
/// <summary>
/// Creates a new <see cref="FilterDescriptor"/>.
/// </summary>
/// <param name="filter">The <see cref="IFilter"/>.</param>
/// <param name="filterScope">The filter scope.</param>
/// <remarks>
/// If the <paramref name="filter"/> implements <see cref="IOrderedFilter"/>, then the value of
/// <see cref="Order"/> will be taken from <see cref="IOrderedFilter.Order"/>. Otherwise the value
/// of <see cref="Order"/> will default to <c>0</c>.
/// </remarks>
public FilterDescriptor([NotNull] IFilter filter, int filterScope)
{
Filter = filter;
@ -18,10 +44,19 @@ namespace Microsoft.AspNet.Mvc
}
}
/// <summary>
/// The <see cref="IFilter"/> instance.
/// </summary>
public IFilter Filter { get; private set; }
public int Order { get; private set; }
/// <summary>
/// The filter order.
/// </summary>
public int Order { get; set; }
/// <summary>
/// The filter scope.
/// </summary>
public int Scope { get; private set; }
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.Filters
{
/// <summary>
/// Provides access to the collection of <see cref="IFilter"/> for globally registered filters.
/// </summary>
public interface IGlobalFilterProvider
{
/// <summary>
/// Gets the collection of <see cref="IFilter"/>.
/// </summary>
IReadOnlyList<IFilter> Filters { get; }
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.OptionDescriptors;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
@ -25,6 +26,7 @@ namespace Microsoft.AspNet.Mvc
ValueProviderFactories = new List<ValueProviderFactoryDescriptor>();
OutputFormatters = new List<OutputFormatterDescriptor>();
InputFormatters = new List<InputFormatterDescriptor>();
Filters = new List<IFilter>();
}
/// <summary>
@ -51,13 +53,19 @@ namespace Microsoft.AspNet.Mvc
}
/// <summary>
/// Get a list of the <see cref="OutputFormatterDescriptor" /> which are used to construct
/// Gets a list of <see cref="IFilter"/> which are used to construct filters that
/// apply to all actions.
/// </summary>
public ICollection<IFilter> Filters { get; private set; }
/// <summary>
/// Gets a list of the <see cref="OutputFormatterDescriptor" /> which are used to construct
/// a list of <see cref="IOutputFormatter"/> by <see cref="IOutputFormattersProvider"/>.
/// </summary>
public List<OutputFormatterDescriptor> OutputFormatters { get; private set; }
/// <summary>
/// Get a list of the <see cref="InputFormatterDescriptor" /> which are used to construct
/// Gets a list of the <see cref="InputFormatterDescriptor" /> which are used to construct
/// a list of <see cref="IInputFormatter"/> by <see cref="IInputFormattersProvider"/>.
/// </summary>
public List<InputFormatterDescriptor> InputFormatters { get; private set; }
@ -86,13 +94,13 @@ namespace Microsoft.AspNet.Mvc
}
/// <summary>
/// Get a list of the <see cref="ModelBinderDescriptor" /> used by the
/// Gets a list of the <see cref="ModelBinderDescriptor" /> used by the
/// <see cref="ModelBinding.CompositeModelBinder" />.
/// </summary>
public List<ModelBinderDescriptor> ModelBinders { get; private set; }
/// <summary>
/// Get a list of the <see cref="ModelValidatorProviderDescriptor" />s used by
/// Gets a list of the <see cref="ModelValidatorProviderDescriptor" />s used by
/// <see cref="ModelBinding.CompositeModelValidatorProvider"/>.
/// </summary>
public List<ModelValidatorProviderDescriptor> ModelValidatorProviders { get; } =

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Routing;
@ -27,19 +28,19 @@ namespace Microsoft.AspNet.Mvc
private readonly IControllerAssemblyProvider _controllerAssemblyProvider;
private readonly IActionDiscoveryConventions _conventions;
private readonly IEnumerable<IFilter> _globalFilters;
private readonly IReadOnlyList<IFilter> _globalFilters;
private readonly IEnumerable<IReflectedApplicationModelConvention> _modelConventions;
private readonly IInlineConstraintResolver _constraintResolver;
public ReflectedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider,
IActionDiscoveryConventions conventions,
IEnumerable<IFilter> globalFilters,
IGlobalFilterProvider globalFilters,
IOptionsAccessor<MvcOptions> optionsAccessor,
IInlineConstraintResolver constraintResolver)
{
_controllerAssemblyProvider = controllerAssemblyProvider;
_conventions = conventions;
_globalFilters = globalFilters ?? Enumerable.Empty<IFilter>();
_globalFilters = globalFilters.Filters;
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
_constraintResolver = constraintResolver;
}
@ -352,8 +353,8 @@ namespace Microsoft.AspNet.Mvc
IEnumerable<IFilter> controllerFilters,
IEnumerable<IFilter> globalFilters)
{
actionDescriptor.FilterDescriptors = actionFilters
.Select(f => new FilterDescriptor(f, FilterScope.Action))
actionDescriptor.FilterDescriptors =
actionFilters.Select(f => new FilterDescriptor(f, FilterScope.Action))
.Concat(controllerFilters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
.Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)

View File

@ -18,7 +18,16 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
// is needed to so that the result of ToList() is List<object>
Attributes = actionMethod.GetCustomAttributes(inherit: true).OfType<object>().ToList();
Filters = Attributes.OfType<IFilter>().ToList();
Filters = Attributes
.OfType<IFilter>()
.ToList();
var routeTemplateAttribute = Attributes.OfType<IRouteTemplateProvider>().FirstOrDefault();
if (routeTemplateAttribute != null)
{
AttributeRouteModel = new ReflectedAttributeRouteModel(routeTemplateAttribute);
}
HttpMethods = new List<string>();
Parameters = new List<ReflectedParameterModel>();
}
@ -39,4 +48,4 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
public ReflectedAttributeRouteModel AttributeRouteModel { get; set; }
}
}
}

View File

@ -21,7 +21,10 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
// is needed to so that the result of ToList() is List<object>
Attributes = ControllerType.GetCustomAttributes(inherit: true).OfType<object>().ToList();
Filters = Attributes.OfType<IFilter>().ToList();
Filters = Attributes
.OfType<IFilter>()
.ToList();
RouteConstraints = Attributes.OfType<RouteConstraintAttribute>().ToList();
AttributeRoutes = Attributes.OfType<IRouteTemplateProvider>()

View File

@ -15,4 +15,4 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// </summary>
IReadOnlyList<IViewEngine> ViewEngines { get; }
}
}
}

View File

@ -79,6 +79,10 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Instance<JsonOutputFormatter>(
new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false));
// The IGlobalFilterProvider is used to build the action descriptors (likely once) and so should
// remain transient to avoid keeping it in memory.
yield return describe.Transient<IGlobalFilterProvider, DefaultGlobalFilterProvider>();
yield return describe.Transient<INestedProvider<FilterProviderContext>, DefaultFilterProvider>();
yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>();

View File

@ -226,7 +226,7 @@ namespace Microsoft.AspNet.Mvc
return new ReflectedActionDescriptorProvider(
controllerAssemblyProvider,
actionDiscoveryConventions,
null,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
}

View File

@ -196,7 +196,7 @@ namespace Microsoft.AspNet.Mvc
return new ReflectedActionDescriptorProvider(
controllerAssemblyProvider.Object,
actionDiscoveryConventions,
null,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
}

View File

@ -0,0 +1,224 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Design;
using System.Linq;
using Microsoft.AspNet.PipelineCore;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Xunit;
using Moq;
namespace Microsoft.AspNet.Mvc.Filters
{
public class DefaultFilterProviderTest
{
[Fact]
public void DefaultFilterProvider_UsesFilter_WhenItsNotIFilterFactory()
{
// Arrange
var filter = Mock.Of<IFilter>();
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filter, FilterScope.Global)),
});
var provider = CreateProvider();
//System.Diagnostics.Debugger.Launch();
//System.Diagnostics.Debugger.Break();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var item = Assert.Single(results);
Assert.Same(filter, item.Filter);
Assert.Same(filter, item.Descriptor.Filter);
Assert.Equal(0, item.Descriptor.Order);
}
[Fact]
public void DefaultFilterProvider_UsesFilterFactory()
{
// Arrange
var filter = Mock.Of<IFilter>();
var filterFactory = new Mock<IFilterFactory>();
filterFactory
.Setup(ff => ff.CreateInstance(It.IsAny<IServiceProvider>()))
.Returns(filter);
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filterFactory.Object, FilterScope.Global)),
});
var provider = CreateProvider();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var item = Assert.Single(results);
Assert.Same(filter, item.Filter);
Assert.Same(filterFactory.Object, item.Descriptor.Filter);
Assert.Equal(0, item.Descriptor.Order);
}
[Fact]
public void DefaultFilterProvider_UsesFilterFactory_WithOrder()
{
// Arrange
var filter = Mock.Of<IFilter>();
var filterFactory = new Mock<IFilterFactory>();
filterFactory
.Setup(ff => ff.CreateInstance(It.IsAny<IServiceProvider>()))
.Returns(filter);
filterFactory.As<IOrderedFilter>().SetupGet(ff => ff.Order).Returns(17);
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filterFactory.Object, FilterScope.Global)),
});
var provider = CreateProvider();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var item = Assert.Single(results);
Assert.Same(filter, item.Filter);
Assert.Same(filterFactory.Object, item.Descriptor.Filter);
Assert.Equal(17, item.Descriptor.Order);
}
[Fact]
public void DefaultFilterProvider_UsesFilterFactory_WithIFilterContainer()
{
// Arrange
var filter = new Mock<IFilterContainer>();
filter.SetupAllProperties();
var filterFactory = new Mock<IFilterFactory>();
filterFactory
.Setup(ff => ff.CreateInstance(It.IsAny<IServiceProvider>()))
.Returns(filter.As<IFilter>().Object);
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filterFactory.Object, FilterScope.Global)),
});
var provider = CreateProvider();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var item = Assert.Single(results);
Assert.Same(filter.Object, item.Filter);
Assert.Same(filterFactory.Object, ((IFilterContainer)item.Filter).FilterDefinition);
Assert.Same(filterFactory.Object, item.Descriptor.Filter);
Assert.Equal(0, item.Descriptor.Order);
}
[Fact]
public void DefaultFilterProvider_InsertsController_DefaultOrder()
{
// Arrange
var filter1 = new Mock<IOrderedFilter>();
filter1.SetupGet(f => f.Order).Returns(int.MaxValue);
var filter2 = new Mock<IOrderedFilter>();
filter2.SetupGet(f => f.Order).Returns(int.MaxValue);
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filter1.Object, FilterScope.Global)),
new FilterItem(new FilterDescriptor(filter2.Object, FilterScope.Action)),
});
var controller = new Controller();
context.ActionContext.Controller = controller;
var provider = CreateProvider();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var controllerItem = results[1];
Assert.Same(controller, controllerItem.Filter);
Assert.Same(controller, controllerItem.Descriptor.Filter);
Assert.Equal(FilterScope.Controller, controllerItem.Descriptor.Scope);
Assert.Equal(Int32.MaxValue, controllerItem.Descriptor.Order);
}
[Fact]
public void DefaultFilterProvider_InsertsController_CustomOrder()
{
// Arrange
var filter1 = new Mock<IOrderedFilter>();
filter1.SetupGet(f => f.Order).Returns(0);
var filter2 = new Mock<IOrderedFilter>();
filter2.SetupGet(f => f.Order).Returns(int.MaxValue);
var context = CreateFilterContext(new List<FilterItem>()
{
new FilterItem(new FilterDescriptor(filter1.Object, FilterScope.Global)),
new FilterItem(new FilterDescriptor(filter2.Object, FilterScope.Action)),
});
var controller = new Mock<IOrderedFilter>();
controller.SetupGet(f => f.Order).Returns(17);
context.ActionContext.Controller = controller.Object;
var provider = CreateProvider();
// Act
provider.Invoke(context, () => { });
var results = context.Results;
// Assert
var controllerItem = results[1];
Assert.Same(controller.Object, controllerItem.Filter);
Assert.Same(controller.Object, controllerItem.Descriptor.Filter);
Assert.Equal(FilterScope.Controller, controllerItem.Descriptor.Scope);
Assert.Equal(17, controllerItem.Descriptor.Order);
}
private DefaultFilterProvider CreateProvider()
{
var services = new ServiceContainer();
return new DefaultFilterProvider(services);
}
private FilterProviderContext CreateFilterContext(List<FilterItem> items)
{
var actionContext = CreateActionContext();
actionContext.ActionDescriptor.FilterDescriptors = new List<FilterDescriptor>(
items.Select(item => item.Descriptor));
return new FilterProviderContext(actionContext, items);
}
private ActionContext CreateActionContext()
{
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
}
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.ObjectModel;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class FilterCollectionExtensionsTest
{
[Fact]
public void Add_UsesTypeFilterAttribute()
{
// Arrange
var collection = new Collection<IFilter>();
// Act
var added = collection.Add(typeof(MyFilter));
// Assert
var typeFilter = Assert.IsType<TypeFilterAttribute>(added);
Assert.Equal(typeof(MyFilter), typeFilter.ImplementationType);
Assert.Same(typeFilter, Assert.Single(collection));
}
[Fact]
public void Add_WithOrder_SetsOrder()
{
// Arrange
var collection = new Collection<IFilter>();
// Act
var added = collection.Add(typeof(MyFilter), 17);
// Assert
Assert.Equal(17, Assert.IsAssignableFrom<IOrderedFilter>(added).Order);
}
[Fact]
public void Add_ThrowsOnNonIFilter()
{
// Arrange
var collection = new Collection<IFilter>();
var expectedMessage =
"The type 'Microsoft.AspNet.Mvc.FilterCollectionExtensionsTest+NonFilter' must derive from " +
"'Microsoft.AspNet.Mvc.IFilter'." + Environment.NewLine +
"Parameter name: filterType";
// Act & Assert
var ex = Assert.Throws<ArgumentException>(() => { collection.Add(typeof(NonFilter)); });
// Assert
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
public void AddService_UsesServiceFilterAttribute()
{
// Arrange
var collection = new Collection<IFilter>();
// Act
var added = collection.AddService(typeof(MyFilter));
// Assert
var serviceFilter = Assert.IsType<ServiceFilterAttribute>(added);
Assert.Equal(typeof(MyFilter), serviceFilter.ServiceType);
Assert.Same(serviceFilter, Assert.Single(collection));
}
[Fact]
public void AddService_SetsOrder()
{
// Arrange
var collection = new Collection<IFilter>();
// Act
var added = collection.AddService(typeof(MyFilter), 17);
// Assert
Assert.Equal(17, Assert.IsAssignableFrom<IOrderedFilter>(added).Order);
}
[Fact]
public void AddService_ThrowsOnNonIFilter()
{
// Arrange
var collection = new Collection<IFilter>();
var expectedMessage =
"The type 'Microsoft.AspNet.Mvc.FilterCollectionExtensionsTest+NonFilter' must derive from " +
"'Microsoft.AspNet.Mvc.IFilter'." + Environment.NewLine +
"Parameter name: filterType";
// Act & Assert
var ex = Assert.Throws<ArgumentException>(() => { collection.AddService(typeof(NonFilter)); });
// Assert
Assert.Equal(expectedMessage, ex.Message);
}
private class MyFilter : IFilter, IOrderedFilter
{
public int Order
{
get;
set;
}
}
private class NonFilter
{
}
}
}

View File

@ -9,6 +9,7 @@ using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Mvc.Routing;
using Moq;
using Xunit;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc.Test
{
@ -1029,7 +1030,7 @@ namespace Microsoft.AspNet.Mvc.Test
var provider = new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
filters,
new TestGlobalFilterProvider(filters),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
@ -1049,7 +1050,7 @@ namespace Microsoft.AspNet.Mvc.Test
var provider = new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
Enumerable.Empty<IFilter>(),
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
@ -1068,7 +1069,7 @@ namespace Microsoft.AspNet.Mvc.Test
var provider = new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
null,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
null);

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public class TestGlobalFilterProvider : IGlobalFilterProvider
{
public TestGlobalFilterProvider()
: this(null)
{
}
public TestGlobalFilterProvider(IEnumerable<IFilter> filters)
{
var filterList = new List<IFilter>();
Filters = filterList;
if (filters != null)
{
filterList.AddRange(filters);
}
}
public IReadOnlyList<IFilter> Filters { get; private set; }
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class FiltersTest
{
private readonly IServiceProvider _services = TestHelper.CreateServices("FiltersWebSite");
private readonly Action<IApplicationBuilder> _app = new FiltersWebSite.Startup().Configure;
[Fact]
public async Task ListAllFilters()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Products/GetPrice/5");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<decimal>(body);
Assert.Equal(19.95m, result);
var filters = response.Headers.GetValues("filters");
Assert.Equal(
new string[]
{
// This one uses order to set itself 'first' even though it appears on the controller
"FiltersWebSite.PassThroughActionFilter",
// Configured as global with default order
"FiltersWebSite.GlobalExceptionFilter",
// Configured on the controller with default order
"FiltersWebSite.PassThroughResultFilter",
// Configured on the action with default order
"FiltersWebSite.PassThroughActionFilter",
// The controller itself
"FiltersWebSite.ProductsController",
},
filters);
}
}
}

View File

@ -8,7 +8,8 @@
"AntiForgeryWebSite": "",
"BasicWebSite": "",
"CompositeViewEngine": "",
"ConnegWebsite": "",
"ConnegWebsite": "",
"FiltersWebSite": "",
"FormatterWebSite": "",
"InlineConstraintsWebSite": "",
"Microsoft.AspNet.TestHost": "1.0.0-*",

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using System.Linq;
namespace FiltersWebSite
{
// This controller will list the filters that are configured for each action in a header.
// This exercises the merging of filters with the global filters collection.
[PassThroughActionFilter(Order = -2)]
[PassThroughResultFilter]
public class ProductsController : Controller
{
[PassThroughActionFilter]
public decimal GetPrice(int id)
{
return 19.95m;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Log the filter names in a header
context.HttpContext.Response.Headers.Add(
"filters",
context.Filters.Select(f => f.GetType().FullName).ToArray());
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace FiltersWebSite
{
public class GlobalExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.Result = new ContentResult()
{
Content = "GlobalExceptionFilter.OnException",
ContentType = "text/plain",
};
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace FiltersWebSite
{
public class PassThroughActionFilter : ActionFilterAttribute
{
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace FiltersWebSite
{
public class PassThroughResultFilter : ResultFilterAttribute
{
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>1976ac4a-fea4-4587-a158-d9f79736d2b6</ProjectGuid>
<OutputType>Web</OutputType>
<RootNamespace>FiltersWebSite</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>25980</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace FiltersWebSite
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddMvc(configuration);
services.SetupOptions<MvcOptions>(options =>
{
options.Filters.Add(new GlobalExceptionFilter());
});
});
app.UseMvc();
}
}
}

View File

@ -0,0 +1,11 @@
{
"dependencies": {
"Microsoft.AspNet.Mvc": "",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": ""
},
"frameworks" : {
"aspnet50": { },
"aspnetcore50": { }
}
}