diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionInvokerProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ActionInvokerProvider.cs index d49f04c2f4..70acb671fa 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionInvokerProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionInvokerProvider.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNet.DependencyInjection; namespace Microsoft.AspNet.Mvc { @@ -8,15 +9,19 @@ namespace Microsoft.AspNet.Mvc private readonly IServiceProvider _serviceProvider; private readonly IControllerFactory _controllerFactory; private readonly IActionBindingContextProvider _bindingProvider; + private readonly INestedProviderManager _filterProvider; + public ActionInvokerProvider(IActionResultFactory actionResultFactory, IControllerFactory controllerFactory, IActionBindingContextProvider bindingProvider, + INestedProviderManager filterProvider, IServiceProvider serviceProvider) { _actionResultFactory = actionResultFactory; _controllerFactory = controllerFactory; _bindingProvider = bindingProvider; + _filterProvider = filterProvider; _serviceProvider = serviceProvider; } @@ -37,6 +42,7 @@ namespace Microsoft.AspNet.Mvc _actionResultFactory, _controllerFactory, _bindingProvider, + _filterProvider, _serviceProvider); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs new file mode 100644 index 0000000000..77a8251174 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.DependencyInjection; + +namespace Microsoft.AspNet.Mvc +{ + public class DefaultFilterProvider : INestedProvider + { + public DefaultFilterProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + + public virtual void Invoke(FilterProviderContext context, Action callNext) + { + List filters = context.ActionDescriptor.Filters; + + if (filters == null) + { + filters = new List(); + } + else + { + filters = filters.ToList(); // make a copy of the list, TODO: Make the actiondescriptor immutable + + } + + AddGlobalFilters(filters); + + if (filters.Count > 0) + { + for (int i = 0; i < filters.Count; i++) + { + GetFilter(context, filters[i]); + } + } + + if (callNext != null) + { + callNext(); + } + } + + public virtual void GetFilter(FilterProviderContext context, IFilter filter) + { + var serviceFilterSignature = filter as IServiceFilter; + if (serviceFilterSignature != null) + { + var serviceFilter = ServiceProvider.GetService(serviceFilterSignature.ServiceType); + + AddFilters(context, serviceFilter, true); + + // if the filter implements more than the just IServiceFilter + AddFilters(context, filter, false); + } + else + { + AddFilters(context, filter, true); + } + } + + public virtual List AddGlobalFilters(List filters) + { + var globalFilters = ServiceProvider.GetService>().AsArray(); + + if (globalFilters == null || globalFilters.Length == 0) + { + return filters; + } + + return MergeSorted(filters, globalFilters); + } + + private List MergeSorted(List filtersFromAction, IFilter[] globalFilters) + { + if (globalFilters.Length == 0) + { + return filtersFromAction; + } + + var list = new List(); + + var count = filtersFromAction.Count + globalFilters.Length; + + for (int i = 0, j = 0; i + j < count; ) + { + if (i >= filtersFromAction.Count) + { + list.Add(globalFilters[j++]); + } + else if (j >= globalFilters.Length) + { + list.Add(filtersFromAction[i++]); + } + else if (filtersFromAction[i].Order >= globalFilters[j].Order) + { + list.Add(filtersFromAction[i++]); + } + else + { + list.Add(globalFilters[j++]); + } + } + + return list; + } + + protected IServiceProvider ServiceProvider { get; private set; } + + public int Order + { + get { return 0; } + } + + private void AddFilters(FilterProviderContext context, object filter, bool throwIfNotFilter) + { + bool shouldThrow = throwIfNotFilter; + + var authFilter = filter as IAuthorizationFilter; + var actionFilter = filter as IActionFilter; + var actionResultFilter = filter as IActionResultFilter; + var exceptionFilter = filter as IExceptionFilter; + + if (authFilter != null) + { + if (context.AuthorizationFilters == null) + { + context.AuthorizationFilters = new List(); + } + + context.AuthorizationFilters.Add(authFilter); + shouldThrow = false; + } + + if (actionFilter != null) + { + if (context.ActionFilters == null) + { + context.ActionFilters = new List(); + } + + context.ActionFilters.Add(actionFilter); + shouldThrow = false; + } + + if (actionResultFilter != null) + { + if (context.ActionResultFilters == null) + { + context.ActionResultFilters = new List(); + } + + context.ActionResultFilters.Add(actionResultFilter); + shouldThrow = false; + } + + if (exceptionFilter != null) + { + if (context.ExceptionFilters == null) + { + context.ExceptionFilters = new List(); + } + + context.ExceptionFilters.Add(exceptionFilter); + shouldThrow = false; + } + + if (shouldThrow) + { + throw new InvalidOperationException("Filter has to be IActionResultFilter, IActionFilter, IExceptionFilter or IAuthorizationFilter."); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs new file mode 100644 index 0000000000..e563889810 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterProviderContext.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc +{ + public class FilterProviderContext + { + public FilterProviderContext(ActionDescriptor actionDescriptor) + { + ActionDescriptor = actionDescriptor; + } + + // Input + public ActionDescriptor ActionDescriptor { get; set; } + + // Results + public List AuthorizationFilters { get; set; } + + public List ActionFilters { get; set; } + + public List ActionResultFilters { get; set; } + + public List ExceptionFilters { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs index 693e524adf..717c3c8bd5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Internal; +using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.Mvc.ModelBinding; namespace Microsoft.AspNet.Mvc @@ -17,12 +18,14 @@ namespace Microsoft.AspNet.Mvc private readonly IServiceProvider _serviceProvider; private readonly IControllerFactory _controllerFactory; private readonly IActionBindingContextProvider _bindingProvider; + private readonly INestedProviderManager _filterProvider; public ReflectedActionInvoker(ActionContext actionContext, ReflectedActionDescriptor descriptor, IActionResultFactory actionResultFactory, IControllerFactory controllerFactory, IActionBindingContextProvider bindingContextProvider, + INestedProviderManager filterProvider, IServiceProvider serviceProvider) { _actionContext = actionContext; @@ -30,6 +33,8 @@ namespace Microsoft.AspNet.Mvc _actionResultFactory = actionResultFactory; _controllerFactory = controllerFactory; _bindingProvider = bindingContextProvider; + _filterProvider = filterProvider; + _serviceProvider = serviceProvider; } @@ -56,8 +61,12 @@ namespace Microsoft.AspNet.Mvc { var parameterValues = await GetParameterValues(modelState); + var context = new FilterProviderContext(_descriptor); + _filterProvider.Invoke(context); + object actionReturnValue = method.Invoke(controller, GetArgumentValues(parameterValues)); + actionResult = _actionResultFactory.CreateActionResult(method.ReturnType, actionReturnValue, _actionContext); } } diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index f0081faaa2..4ad1ceade9 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -65,6 +65,9 @@ namespace Microsoft.AspNet.Mvc yield return DescribeService(configuration); yield return DescribeService(configuration); + yield return DescribeService, NestedProviderManager>(configuration); + yield return DescribeService, DefaultFilterProvider>(configuration); + yield return DescribeService(configuration); } @@ -90,6 +93,7 @@ namespace Microsoft.AspNet.Mvc { var serviceTypeName = serviceType.FullName; var implementationTypeName = configuration.Get(serviceTypeName); + if (!String.IsNullOrEmpty(implementationTypeName)) { try