From 75bccbae21ad9c102fd1d41ddeeb117cebbee9f8 Mon Sep 17 00:00:00 2001 From: Yishai Galatzer Date: Mon, 3 Mar 2014 01:49:55 -0800 Subject: [PATCH] Filter discovery --- samples/MvcSample/FiltersController.cs | 21 ++++++++ .../Filters/IFilter.cs | 1 + .../Filters/IServiceFilter.cs | 9 ++++ .../Filters/ServiceFilterAttribute.cs | 19 +++++++ .../ReflectedActionDescriptorProvider.cs | 53 +++++++++++++++++-- 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 samples/MvcSample/FiltersController.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs diff --git a/samples/MvcSample/FiltersController.cs b/samples/MvcSample/FiltersController.cs new file mode 100644 index 0000000000..60a408574b --- /dev/null +++ b/samples/MvcSample/FiltersController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNet.Mvc; +using MvcSample.Models; + +namespace MvcSample +{ + // Expected order in descriptor - object -> int -> string + // TODO: Add a real filter here + [ServiceFilter(typeof(object), Order = 1)] + [ServiceFilter(typeof(string))] + public class FiltersController : Controller + { + private readonly User _user = new User() { Name = "User Name", Address = "Home Address" }; + + // TODO: Add a real filter here + [ServiceFilter(typeof(int))] + public IActionResult Index() + { + return View("MyView", _user); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilter.cs index 0622bf727c..908f477ab2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/IFilter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilter.cs @@ -3,5 +3,6 @@ namespace Microsoft.AspNet.Mvc public interface IFilter { // Marker only interface to any IFilter gets picked up by the DefaultActionDescriptorProvider + int Order { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs new file mode 100644 index 0000000000..a5763f452b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IServiceFilter.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.AspNet.Mvc +{ + public interface IServiceFilter : IFilter + { + Type ServiceType { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs new file mode 100644 index 0000000000..e5ed6ae706 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ServiceFilterAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Diagnostics; + +namespace Microsoft.AspNet.Mvc +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [DebuggerDisplay("ServiceFilter: Type={ServiceType} Order={Order}")] + public class ServiceFilterAttribute : Attribute, IServiceFilter + { + public ServiceFilterAttribute(Type type) + { + ServiceType = type; + } + + public Type ServiceType { get; private set; } + + public int Order { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs index 94252cb972..33718595aa 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs @@ -43,6 +43,8 @@ namespace Microsoft.AspNet.Mvc foreach (var cd in controllerDescriptors) { + var controllerFilters = GetOrderedFilterAttributes(cd.ControllerTypeInfo); + foreach (var methodInfo in cd.ControllerTypeInfo.DeclaredMethods) { var actionInfos = _conventions.GetActions(methodInfo); @@ -54,13 +56,24 @@ namespace Microsoft.AspNet.Mvc foreach (var actionInfo in actionInfos) { - yield return BuildDescriptor(cd, methodInfo, actionInfo); + yield return BuildDescriptor(cd, methodInfo, actionInfo, controllerFilters); } } } } - private ReflectedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor, MethodInfo methodInfo, ActionInfo actionInfo) + private IFilter[] GetOrderedFilterAttributes(MemberInfo memberInfo) + { + var attributes = memberInfo.GetCustomAttributes(inherit: true); + var filters = attributes.OfType().OrderByDescending(filter => filter.Order); + + return filters.ToArray(); + } + + private ReflectedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor, + MethodInfo methodInfo, + ActionInfo actionInfo, + IFilter[] controllerFilters) { var ad = new ReflectedActionDescriptor { @@ -85,7 +98,7 @@ namespace Microsoft.AspNet.Mvc if (actionInfo.RequireActionNameMatch) { - ad.RouteConstraints.Add(new RouteDataActionConstraint("action", actionInfo.ActionName)); + ad.RouteConstraints.Add(new RouteDataActionConstraint("action", actionInfo.ActionName)); } else { @@ -94,7 +107,41 @@ namespace Microsoft.AspNet.Mvc ad.Parameters = methodInfo.GetParameters().Select(p => _parameterDescriptorFactory.GetDescriptor(p)).ToList(); + // TODO: add ordering support such that action filters are ahead of controller filters if they have the same order + var actionFilters = GetOrderedFilterAttributes(methodInfo); + + ad.Filters = MergeSorted(actionFilters, controllerFilters); + return ad; } + + internal List MergeSorted(IFilter[] actionFilters, IFilter[] controllerFilters) + { + var list = new List(); + + var count = actionFilters.Length + controllerFilters.Length; + + for (int i = 0, j = 0; i + j < count; ) + { + if (i >= actionFilters.Length) + { + list.Add(controllerFilters[j++]); + } + else if (j >= controllerFilters.Length) + { + list.Add(actionFilters[i++]); + } + else if (actionFilters[i].Order >= controllerFilters[j].Order) + { + list.Add(actionFilters[i++]); + } + else + { + list.Add(controllerFilters[j++]); + } + } + + return list; + } } }