From 7a8dc3655353cb16e07d6efc8a6901e561704d08 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 30 Apr 2014 16:38:56 -0700 Subject: [PATCH] Adding controller-as-filter support If the controller implements IFilter, it will be added to the filters collection. It's hardcoded to be 'first' as it was in MVC, but can be overridden by implementing IOrderedFilter. --- .../Filters/DefaultFilterProvider.cs | 41 ++++++++++++++++++- .../Filters/FilterItemOrderComparer.cs | 33 +++++++++++++++ .../Filters/FilterProviderContext.cs | 12 +++--- .../Microsoft.AspNet.Mvc.Core.kproj | 1 + .../ReflectedActionInvoker.cs | 8 ++-- .../ReflectedActionInvokerTest.cs | 2 +- 6 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/FilterItemOrderComparer.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs index 4c04eaf696..f612f3f61c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs @@ -41,14 +41,20 @@ namespace Microsoft.AspNet.Mvc.Filters public virtual void Invoke(FilterProviderContext context, Action callNext) { - if (context.ActionDescriptor.FilterDescriptors != null) + if (context.ActionContext.ActionDescriptor.FilterDescriptors != null) { - foreach (var item in context.Result) + foreach (var item in context.Results) { ProvideFilter(context, item); } } + var controllerFilter = context.ActionContext.Controller as IFilter; + if (controllerFilter != null) + { + InsertControllerAsFilter(context, controllerFilter); + } + if (callNext != null) { callNext(); @@ -84,6 +90,37 @@ 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. + int 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); + + // BinarySearch will return the index of where the item _should_be_ in the list. + // + // If index > 0: + // Other items in the list have the same order and scope - the item was 'found'. + // + // If index < 0: + // No other items in the list have the same order and scope - the item was not 'found' + // Index will be the bitwise compliment of of the 'right' location. + var index = context.Results.BinarySearch(item, FilterItemOrderComparer.Comparer); + if (index < 0) + { + index = ~index; + } + + context.Results.Insert(index, item); + } + private void ApplyFilterToContainer(object actualFilter, IFilter filterMetadata) { Contract.Assert(actualFilter != null, "actualFilter should not be null"); diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/FilterItemOrderComparer.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterItemOrderComparer.cs new file mode 100644 index 0000000000..ebfa7ce79f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterItemOrderComparer.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc +{ + public class FilterItemOrderComparer : IComparer + { + private static readonly FilterItemOrderComparer _comparer = new FilterItemOrderComparer(); + + public static FilterItemOrderComparer Comparer { get { return _comparer; } } + + public int Compare([NotNull] FilterItem x, [NotNull] FilterItem y) + { + return FilterDescriptorOrderComparer.Comparer.Compare(x.Descriptor, y.Descriptor); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs index 4b16870f83..34e0c99d5a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs @@ -21,16 +21,16 @@ namespace Microsoft.AspNet.Mvc { public class FilterProviderContext { - public FilterProviderContext(ActionDescriptor actionDescriptor, List items) + public FilterProviderContext([NotNull] ActionContext actionContext, [NotNull] List items) { - ActionDescriptor = actionDescriptor; - Result = items; + ActionContext = actionContext; + Results = items; } // Input - public ActionDescriptor ActionDescriptor { get; set; } + public ActionContext ActionContext { get; set; } - // Result - public List Result { get; set; } + // Results + public List Results { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 6ac42e8f14..c99bf0c57a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -73,6 +73,7 @@ + diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs index dbc8937fc9..8964f8fd82 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs @@ -74,11 +74,11 @@ namespace Microsoft.AspNet.Mvc public async Task InvokeActionAsync() { + _actionContext.Controller = _controllerFactory.CreateController(_actionContext); + _filters = GetFilters(); _cursor = new FilterCursor(_filters); - _actionContext.Controller = _controllerFactory.CreateController(_actionContext); - // >> ExceptionFilters >> AuthorizationFilters >> ActionFilters >> Action await InvokeActionExceptionFilters(); @@ -116,12 +116,12 @@ namespace Microsoft.AspNet.Mvc private IFilter[] GetFilters() { var filterProviderContext = new FilterProviderContext( - _descriptor, + _actionContext, _descriptor.FilterDescriptors.Select(fd => new FilterItem(fd)).ToList()); _filterProvider.Invoke(filterProviderContext); - return filterProviderContext.Result.Select(item => item.Filter).Where(filter => filter != null).ToArray(); + return filterProviderContext.Results.Select(item => item.Filter).Where(filter => filter != null).ToArray(); } private async Task InvokeActionExceptionFilters() diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs index 6ef4c03ee6..eb363fc6aa 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs @@ -1255,7 +1255,7 @@ namespace Microsoft.AspNet.Mvc filterProvider .Setup(fp => fp.Invoke(It.IsAny())) .Callback( - context => context.Result.AddRange(filters.Select(f => new FilterItem(null, f)))); + context => context.Results.AddRange(filters.Select(f => new FilterItem(null, f)))); var invoker = new ReflectedActionInvoker( actionContext,