From 6fed92695b4661abf5848f8c5ca62a4aed611ead Mon Sep 17 00:00:00 2001 From: Yishai Galatzer Date: Wed, 5 Mar 2014 13:11:01 -0800 Subject: [PATCH] Filter execution --- samples/MvcSample/FiltersController.cs | 4 +- .../Filters/ActionFilterAttribute.cs | 2 +- .../Filters/ActionFilterContext.cs | 7 ++- .../Filters/ActionFilterEndPoint.cs | 31 +++++++++++ .../Filters/ActionResultFilterAttribute.cs | 2 +- .../Filters/ActionResultFilterContext.cs | 3 +- .../Filters/ActionResultFilterEndPoint.cs | 13 +++++ .../Filters/AuthorizationFilterAttribute.cs | 2 +- .../Filters/AuthorizationFilterEndPoint.cs | 17 +++++++ .../Filters/ExceptionFilterAttribute.cs | 2 +- .../Filters/FilterPipelineBuilder.cs | 51 +++++++++++++++++++ .../Filters/IFilterOfTContext.cs | 2 +- .../ReflectedActionInvoker.cs | 49 ++++++++++++++++-- 13 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterEndPoint.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterEndPoint.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterEndPoint.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/FilterPipelineBuilder.cs diff --git a/samples/MvcSample/FiltersController.cs b/samples/MvcSample/FiltersController.cs index 909acb000c..8dd85ec9d2 100644 --- a/samples/MvcSample/FiltersController.cs +++ b/samples/MvcSample/FiltersController.cs @@ -25,9 +25,9 @@ namespace MvcSample public class PassThroughAttribute : AuthorizationFilterAttribute { - public async override Task Invoke(AuthorizationFilterContext context, Func next) + public async override Task Invoke(AuthorizationFilterContext context, Func next) { - await next(context); + await next(); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterAttribute.cs index bdf4e6f40b..b342870270 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilter { - public abstract Task Invoke(ActionFilterContext context, Func next); + public abstract Task Invoke(ActionFilterContext context, Func next); public int Order { get; set; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterContext.cs index a5508cece3..e469ba1628 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterContext.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Microsoft.AspNet.Mvc { @@ -14,6 +15,8 @@ namespace Microsoft.AspNet.Mvc public virtual ActionContext ActionContext { get; private set; } - public virtual IActionResult Result { get; set; } + public virtual Type MethodReturnType { get; private set; } + + public virtual object Result { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterEndPoint.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterEndPoint.cs new file mode 100644 index 0000000000..11c580bbfd --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterEndPoint.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc.Filters +{ + public class ReflectedActionFilterEndPoint : IActionFilter + { + private readonly Func> _coreMethodInvoker; + private readonly IActionResultFactory _actionResultFactory; + + public ReflectedActionFilterEndPoint(Func> coreMethodInvoker, + IActionResultFactory actionResultFactory) + { + _coreMethodInvoker = coreMethodInvoker; + _actionResultFactory = actionResultFactory; + } + + public async Task Invoke(ActionFilterContext context, Func next) + { + // TODO: match the parameter names here. + var tempArray = context.ActionParameters.Values.ToArray(); // seriously broken for now, need to organize names to match. + + var actionReturnValue = await _coreMethodInvoker(tempArray); + + context.Result = _actionResultFactory.CreateActionResult(context.MethodReturnType, + actionReturnValue, + context.ActionContext); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterAttribute.cs index fbb87049fe..0a745c3908 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ActionResultFilterAttribute : Attribute, IActionResultFilter, IFilter { - public abstract Task Invoke(ActionResultFilterContext context, Func next); + public abstract Task Invoke(ActionResultFilterContext context, Func next); public int Order { get; set; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterContext.cs index 1ab9bedb64..e355c1607d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterContext.cs @@ -2,9 +2,10 @@ { public class ActionResultFilterContext { - public ActionResultFilterContext(ActionContext actionContext) + public ActionResultFilterContext(ActionContext actionContext, IActionResult initialResult) { ActionContext = actionContext; + Result = initialResult; } public ActionContext ActionContext { get; private set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterEndPoint.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterEndPoint.cs new file mode 100644 index 0000000000..32ddd87cbd --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionResultFilterEndPoint.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc.Filters +{ + public class ActionResultFilterEndPoint : IActionResultFilter + { + public async Task Invoke(ActionResultFilterContext context, Func next) + { + await context.Result.ExecuteResultAsync(context.ActionContext); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterAttribute.cs index c17dafbe44..b589972673 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class AuthorizationFilterAttribute : Attribute, IFilter, IAuthorizationFilter { - public abstract Task Invoke(AuthorizationFilterContext context, Func next); + public abstract Task Invoke(AuthorizationFilterContext context, Func next); public int Order { get; set; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterEndPoint.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterEndPoint.cs new file mode 100644 index 0000000000..27337e14c9 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/AuthorizationFilterEndPoint.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc.Filters +{ + public class AuthorizationFilterEndPoint : IAuthorizationFilter + { + public bool EndPointCalled { get; private set; } + + public Task Invoke(AuthorizationFilterContext context, Func next) + { + EndPointCalled = true; + + return Task.FromResult(true); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs index d05907e17e..9b1ec22493 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ExceptionFilterAttribute : Attribute, IExceptionFilter, IFilter { - public abstract Task Invoke(ExceptionFilterContext context, Func next); + public abstract Task Invoke(ExceptionFilterContext context, Func next); public int Order { get; set; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/FilterPipelineBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterPipelineBuilder.cs new file mode 100644 index 0000000000..f0cb174d82 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/FilterPipelineBuilder.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc.Filters +{ + public class FilterPipelineBuilder + { + private readonly IFilter[] _filters; + private readonly T _context; + + // Filters are already ordered externally. + public FilterPipelineBuilder(IEnumerable> filters, T context) + { + _filters = filters.ToArray(); + _context = context; + } + + public async Task InvokeAsync() + { + var caller = new CallNextAsync(_context, _filters); + + await caller.CallNextProvider(); + } + + private class CallNextAsync + { + private readonly T _context; + private readonly IFilter[] _filters; + private readonly Func _next; + + private int _index; + + public CallNextAsync(T context, IFilter[] filters) + { + _context = context; + _next = CallNextProvider; + _filters = filters; + } + + public async Task CallNextProvider() + { + if (_filters.Length > _index) + { + await _filters[_index++].Invoke(_context, _next); + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterOfTContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterOfTContext.cs index 962c11177f..c4fa6787ac 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterOfTContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IFilterOfTContext.cs @@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Mvc.Filters { public interface IFilter { - Task Invoke(T context, Func next); + Task Invoke(T context, Func next); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs index 717c3c8bd5..4bf32dc8d6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs @@ -7,6 +7,8 @@ using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.DependencyInjection.NestedProviders; +using Microsoft.AspNet.Mvc.Filters; namespace Microsoft.AspNet.Mvc { @@ -41,6 +43,8 @@ namespace Microsoft.AspNet.Mvc public async Task InvokeActionAsync() { IActionResult actionResult; + var context = new FilterProviderContext(_descriptor); + _filterProvider.Invoke(context); var modelState = new ModelStateDictionary(); object controller = _controllerFactory.CreateController(_actionContext, modelState); @@ -61,18 +65,53 @@ namespace Microsoft.AspNet.Mvc { var parameterValues = await GetParameterValues(modelState); - var context = new FilterProviderContext(_descriptor); - _filterProvider.Invoke(context); + var authZFilters = context.AuthorizationFilters; + var authZEndPoint = new AuthorizationFilterEndPoint(); + authZFilters.Add(authZEndPoint); + var authZContext = new AuthorizationFilterContext(_actionContext); + var authZPipeline = new FilterPipelineBuilder(authZFilters, authZContext); - object actionReturnValue = method.Invoke(controller, GetArgumentValues(parameterValues)); + await authZPipeline.InvokeAsync(); + + if (authZContext.ActionResult == null && + !authZContext.HasFailed && + authZEndPoint.EndPointCalled) + { + var actionFilters = context.ActionFilters; + var actionFilterContext = new ActionFilterContext(_actionContext, + new Dictionary()); - actionResult = _actionResultFactory.CreateActionResult(method.ReturnType, actionReturnValue, _actionContext); + // TODO: This is extremely temporary and is going to get soon replaced with the action executer + var actionEndPoint = new ReflectedActionFilterEndPoint(async (inArray) => method.Invoke(controller, inArray), + _actionResultFactory); + + actionFilters.Add(actionEndPoint); + + var actionFilterPipeline = new FilterPipelineBuilder(actionFilters, + actionFilterContext); + + await actionFilterPipeline.InvokeAsync(); + + object actionReturnValue = method.Invoke(controller, null); + actionResult = _actionResultFactory.CreateActionResult(method.ReturnType, actionReturnValue, _actionContext); + } + else + { + actionResult = authZContext.ActionResult ?? new HttpStatusCodeResult(401); + } } } - // TODO: This will probably move out once we got filters + var actionResultFilters = context.ActionResultFilters; await actionResult.ExecuteResultAsync(_actionContext); + var actionResultFilterContext = new ActionResultFilterContext(_actionContext, actionResult); + var actionResultFilterEndPoint = new ActionResultFilterEndPoint(); + actionResultFilters.Add(actionResultFilterEndPoint); + + var actionResultPipeline = new FilterPipelineBuilder(actionResultFilters, actionResultFilterContext); + + await actionResultPipeline.InvokeAsync(); } private async Task> GetParameterValues(ModelStateDictionary modelState)