Filters version 2.0

This is functionally much more similar to legacy MVC.

Rather than a pure single pipeline, filter execution takes place in more
stages.
This commit is contained in:
Louis DeJardin 2014-04-01 16:16:17 -07:00 committed by Ryan Nowak
parent 93c9b3419e
commit f19fe0cbef
54 changed files with 2276 additions and 444 deletions

View File

@ -1,12 +1,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.Filters
{
public class AgeEnhancerAttribute : ActionFilterAttribute
{
public async override Task Invoke(ActionFilterContext context, Func<Task> next)
public override void OnActionExecuting(ActionExecutingContext context)
{
object age = null;
@ -28,8 +26,6 @@ namespace MvcSample.Web.Filters
context.ActionArguments["age"] = intAge;
}
}
await next();
}
}
}

View File

@ -1,19 +1,15 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.Filters
{
public class BlockAnonymous : AuthorizationFilterAttribute
{
public override async Task Invoke(AuthorizationFilterContext context, Func<Task> next)
public override void OnAuthorization(AuthorizationContext context)
{
if (!context.HasAllowAnonymous())
if (!HasAllowAnonymous(context))
{
context.Fail();
context.Result = new HttpStatusCodeResult(401);
}
await next();
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.Filters
{
public class DelayAttribute : ActionFilterAttribute
{
public DelayAttribute(int milliseconds)
{
Delay = TimeSpan.FromMilliseconds(milliseconds);
}
public TimeSpan Delay { get; private set; }
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.HttpContext.Request.Method == "GET")
{
// slow down incoming GET requests
await Task.Delay(Delay);
}
var executedContext = await next();
if (executedContext.Result is ViewResult)
{
// slow down outgoing view results
await Task.Delay(Delay);
}
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
namespace MvcSample.Web
{
public class ErrorMessagesAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null && !context.ExceptionHandled)
{
context.ExceptionHandled = true;
context.Result = new ContentResult
{
ContentType = "text/plain",
Content = "Boom " + context.Exception.Message
};
}
}
}
}

View File

@ -1,15 +1,16 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
using MvcSample.Web.Models;
namespace MvcSample.Web.Filters
{
public class InspectResultPageAttribute : ActionResultFilterAttribute
public class InspectResultPageAttribute : ActionFilterAttribute
{
public async override Task Invoke(ActionResultFilterContext context, Func<Task> next)
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var viewResult = context.ActionResult as ViewResult;
var viewResult = context.Result as ViewResult;
if (viewResult != null)
{

View File

@ -1,14 +1,16 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
namespace MvcSample.Web
{
public class PassThroughAttribute : AuthorizationFilterAttribute
{
public async override Task Invoke(AuthorizationFilterContext context, Func<Task> next)
#pragma warning disable 1998
public override async Task OnAuthorizationAsync(AuthorizationContext context)
{
await next();
}
#pragma warning restore 1998
}
}

View File

@ -1,6 +1,4 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.Filters
{
@ -9,7 +7,7 @@ namespace MvcSample.Web.Filters
private static readonly string[] _userNames = new[] { "Jon", "David", "Goliath" };
private static int _index;
public override async Task Invoke(ActionFilterContext context, Func<Task> next)
public override void OnActionExecuting(ActionExecutingContext context)
{
object originalUserName = null;
@ -21,8 +19,6 @@ namespace MvcSample.Web.Filters
{
context.ActionArguments["userName"] = _userNames[(_index++)%3];
}
await next();
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNet.Mvc;
using System;
using Microsoft.AspNet.Mvc;
using MvcSample.Web.Filters;
using MvcSample.Web.Models;
@ -19,6 +20,7 @@ namespace MvcSample.Web
[ServiceFilter(typeof(PassThroughAttribute))]
[AllowAnonymous]
[AgeEnhancer]
[Delay(500)]
public IActionResult Index(int age, string userName)
{
if (!string.IsNullOrEmpty(userName))
@ -35,5 +37,11 @@ namespace MvcSample.Web
{
return Index(age, userName);
}
[ErrorMessages, AllowAnonymous]
public IActionResult Crash(string message)
{
throw new Exception(message);
}
}
}

View File

@ -47,6 +47,8 @@
<Compile Include="FiltersController.cs" />
<Compile Include="Filters\AgeEnhancerFilterAttribute.cs" />
<Compile Include="Filters\BlockAnonymous.cs" />
<Compile Include="Filters\DelayAttribute.cs" />
<Compile Include="Filters\ErrorMessagesAttribute.cs" />
<Compile Include="Filters\InspectResultPageAttribute.cs" />
<Compile Include="Filters\PassThroughAttribute.cs" />
<Compile Include="Filters\UserNameProvider.cs" />

View File

@ -1,13 +0,0 @@
using System.Linq;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public static class FilterContextExtensions
{
public static bool HasAllowAnonymous([NotNull] this FilterContext context)
{
return context.FilterItems.Any(item => item.Filter is IAllowAnonymous);
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
namespace Microsoft.AspNet.Mvc
{
public class ActionExecutedContext : FilterContext
{
private Exception _exception;
private ExceptionDispatchInfo _exceptionDispatchInfo;
public ActionExecutedContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters)
: base(actionContext, filters)
{
}
public virtual bool Canceled { get; set; }
public virtual Exception Exception
{
get
{
if (_exception == null && _exceptionDispatchInfo != null)
{
return _exceptionDispatchInfo.SourceException;
}
else
{
return _exception;
}
}
set
{
_exceptionDispatchInfo = null;
_exception = value;
}
}
public virtual ExceptionDispatchInfo ExceptionDispatchInfo
{
get
{
return _exceptionDispatchInfo;
}
set
{
_exception = null;
_exceptionDispatchInfo = value;
}
}
public virtual bool ExceptionHandled { get; set; }
public virtual IActionResult Result { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
public class ActionExecutingContext : FilterContext
{
public ActionExecutingContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters,
[NotNull] IDictionary<string, object> actionArguments)
: base(actionContext, filters)
{
ActionArguments = actionArguments;
}
public virtual IActionResult Result { get; set; }
public virtual IDictionary<string, object> ActionArguments { get; private set; }
}
}

View File

@ -0,0 +1,6 @@
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public delegate Task<ActionExecutedContext> ActionExecutionDelegate();
}

View File

@ -3,12 +3,43 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
// TODO: Consider making this user a before and after pattern instead of just Invoke, same for all other filter attributes.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IOrderedFilter
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
{
public abstract Task Invoke(ActionFilterContext context, Func<Task> next);
public int Order { get; set; }
public virtual void OnActionExecuting([NotNull] ActionExecutingContext context)
{
}
public virtual void OnActionExecuted([NotNull] ActionExecutedContext context)
{
}
public virtual async Task OnActionExecutionAsync([NotNull] ActionExecutingContext context, [NotNull] ActionExecutionDelegate next)
{
OnActionExecuting(context);
if (context.Result == null)
{
OnActionExecuted(await next());
}
}
public virtual void OnResultExecuting([NotNull] ResultExecutingContext context)
{
}
public virtual void OnResultExecuted([NotNull] ResultExecutedContext context)
{
}
public virtual async Task OnResultExecutionAsync([NotNull] ResultExecutingContext context, [NotNull] ResultExecutionDelegate next)
{
OnResultExecuting(context);
if (!context.Cancel)
{
OnResultExecuted(await next());
}
}
}
}
}

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public class ActionFilterContext : FilterContext
{
public ActionFilterContext([NotNull] ActionContext actionContext,
[NotNull] IReadOnlyList<FilterItem> filterItems,
[NotNull] IDictionary<string, object> actionArguments)
: base(actionContext, filterItems)
{
ActionArguments = actionArguments;
}
public virtual IDictionary<string, object> ActionArguments { get; private set; }
}
}

View File

@ -1,40 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc.Filters
{
// This one lives in the Filters namespace, and only intended to be consumed by folks that rewrite the action invoker.
public class ReflectedActionFilterEndPoint : IActionFilter
{
private readonly IActionResultFactory _actionResultFactory;
private readonly object _controllerInstance;
public ReflectedActionFilterEndPoint(IActionResultFactory actionResultFactory, object controllerInstance)
{
_actionResultFactory = actionResultFactory;
_controllerInstance = controllerInstance;
}
public async Task Invoke(ActionFilterContext context, Func<Task> next)
{
var reflectedActionDescriptor = context.ActionContext.ActionDescriptor as ReflectedActionDescriptor;
if (reflectedActionDescriptor == null)
{
throw new ArgumentException(Resources.ReflectedActionFilterEndPoint_UnexpectedActionDescriptor);
}
var actionMethodInfo = reflectedActionDescriptor.MethodInfo;
var actionReturnValue = await ReflectedActionExecutor.ExecuteAsync(
actionMethodInfo,
_controllerInstance,
context.ActionArguments);
var underlyingReturnType = TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? actionMethodInfo.ReturnType;
context.ActionResult = _actionResultFactory.CreateActionResult(
underlyingReturnType,
actionReturnValue,
context.ActionContext);
}
}
}

View File

@ -1,13 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionResultFilterAttribute : Attribute, IActionResultFilter, IOrderedFilter
{
public abstract Task Invoke(ActionResultFilterContext context, Func<Task> next);
public int Order { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public class ActionResultFilterContext : FilterContext
{
public ActionResultFilterContext(ActionContext actionContext, IReadOnlyList<FilterItem> filterItems, IActionResult initialResult)
: base(actionContext, filterItems)
{
ActionResult = initialResult;
}
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Filters
{
// This one lives in the Filters namespace, and only intended to be consumed by folks that rewrite the action invoker.
public class ActionResultFilterEndPoint : IActionResultFilter
{
public async Task Invoke(ActionResultFilterContext context, Func<Task> next)
{
// result can get cleared at any point in the pipeline
if (context.ActionResult == null)
{
context.ActionResult = new EmptyResult();
}
await context.ActionResult.ExecuteResultAsync(context.ActionContext);
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public class AuthorizationContext : FilterContext
{
public AuthorizationContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters)
: base(actionContext, filters)
{
}
public virtual IActionResult Result { get; set; }
}
}

View File

@ -1,13 +1,28 @@
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter, IOrderedFilter
public abstract class AuthorizationFilterAttribute : Attribute, IAsyncAuthorizationFilter, IAuthorizationFilter, IOrderedFilter
{
public abstract Task Invoke(AuthorizationFilterContext context, Func<Task> next);
public int Order { get; set; }
#pragma warning disable 1998
public virtual async Task OnAuthorizationAsync([NotNull] AuthorizationContext context)
{
OnAuthorization(context);
}
#pragma warning restore 1998
public virtual void OnAuthorization([NotNull] AuthorizationContext context)
{
}
protected virtual bool HasAllowAnonymous([NotNull] AuthorizationContext context)
{
return context.Filters.Any(item => item is IAllowAnonymous);
}
}
}

View File

@ -1,37 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public class AuthorizationFilterContext : FilterContext
{
private IActionResult _actionResult;
public AuthorizationFilterContext([NotNull] ActionContext actionContext, [NotNull] IReadOnlyList<FilterItem> filterItems)
: base(actionContext, filterItems)
{
}
public bool HasFailed { get; private set; }
// Result
public override IActionResult ActionResult
{
get { return _actionResult; }
set
{
if (value != null)
{
Fail();
}
_actionResult = value;
}
}
public void Fail()
{
HasFailed = true;
}
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Filters
{
// This one lives in the Filters namespace, and only intended to be consumed by folks that rewrite the action invoker.
public class AuthorizationFilterEndPoint : IAuthorizationFilter
{
public bool WasEndPointCalled { get; private set; }
public Task Invoke(AuthorizationFilterContext context, Func<Task> next)
{
WasEndPointCalled = true;
return Task.FromResult(true);
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
namespace Microsoft.AspNet.Mvc
{
public class ExceptionContext : FilterContext
{
private Exception _exception;
private ExceptionDispatchInfo _exceptionDispatchInfo;
public ExceptionContext([NotNull] ActionContext actionContext, [NotNull] IList<IFilter> filters)
: base(actionContext, filters)
{
}
public virtual Exception Exception
{
get
{
if (_exception == null && _exceptionDispatchInfo != null)
{
return _exceptionDispatchInfo.SourceException;
}
else
{
return _exception;
}
}
set
{
_exceptionDispatchInfo = null;
_exception = value;
}
}
public virtual ExceptionDispatchInfo ExceptionDispatchInfo
{
get
{
return _exceptionDispatchInfo;
}
set
{
_exception = null;
_exceptionDispatchInfo = value;
}
}
public virtual IActionResult Result { get; set; }
}
}

View File

@ -4,10 +4,19 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ExceptionFilterAttribute : Attribute, IExceptionFilter, IOrderedFilter
public abstract class ExceptionFilterAttribute : Attribute, IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter
{
public abstract Task Invoke(ExceptionFilterContext context, Func<Task> next);
public int Order { get; set; }
#pragma warning disable 1998
public async Task OnActionExecutedAsync([NotNull] ExceptionContext context)
{
OnActionExecuted(context);
}
#pragma warning restore 1998
public void OnActionExecuted([NotNull] ExceptionContext context)
{
}
}
}

View File

@ -1,22 +0,0 @@
using System;
namespace Microsoft.AspNet.Mvc
{
// TODO: For now we have not implemented the ExceptionFilter pipeline, leaving this in until we decide if we are going
// down this path or implementing an ExceptionFilterAttribute being all three filter types with a higher scope.
public class ExceptionFilterContext
{
public ExceptionFilterContext(ActionContext actionContext, Exception exception)
{
ActionContext = actionContext;
Exception = exception;
}
// TODO: Should we let the exception mutate in the pipeline. MVC lets you do that.
public virtual Exception Exception { get; set; }
public virtual ActionContext ActionContext { get; private set; }
public virtual IActionResult Result { get; set; }
}
}

View File

@ -1,19 +1,17 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.Filters
namespace Microsoft.AspNet.Mvc
{
public class FilterContext
public abstract class FilterContext : ActionContext
{
public FilterContext([NotNull] ActionContext actionContext, [NotNull] IReadOnlyList<FilterItem> filterItems)
public FilterContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters)
: base(actionContext)
{
ActionContext = actionContext;
FilterItems = filterItems;
Filters = filters;
}
public ActionContext ActionContext { get; private set; }
public IReadOnlyList<FilterItem> FilterItems { get; private set; }
// Result
public virtual IActionResult ActionResult { get; set; }
public virtual IList<IFilter> Filters { get; private set; }
}
}

View File

@ -2,7 +2,7 @@
{
public class FilterDescriptor
{
public FilterDescriptor([NotNull]IFilter filter, int filterScope)
public FilterDescriptor([NotNull] IFilter filter, int filterScope)
{
Filter = filter;
Scope = filterScope;

View File

@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Filters
{
public class FilterPipelineBuilder<TContext>
{
private readonly IFilter<TContext>[] _filters;
private readonly TContext _context;
// FilterDescriptors are already ordered externally.
public FilterPipelineBuilder(IEnumerable<IFilter<TContext>> filters, TContext context)
{
_filters = filters.ToArray();
_context = context;
}
public async Task InvokeAsync()
{
var caller = new CallNextAsync(_context, _filters);
await caller.CallNextProvider();
}
private class CallNextAsync
{
private readonly TContext _context;
private readonly IFilter<TContext>[] _filters;
private readonly Func<Task> _next;
private int _index;
public CallNextAsync(TContext context, IFilter<TContext>[] filters)
{
_context = context;
_next = CallNextProvider;
_filters = filters;
}
public async Task CallNextProvider()
{
if (_filters.Length > _index)
{
await _filters[_index++].Invoke(_context, _next);
}
}
}
}
}

View File

@ -1,8 +1,9 @@
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
namespace Microsoft.AspNet.Mvc
{
public interface IActionFilter : IFilter<ActionFilterContext>
public interface IActionFilter : IFilter
{
void OnActionExecuting([NotNull] ActionExecutingContext context);
void OnActionExecuted([NotNull] ActionExecutedContext context);
}
}
}

View File

@ -1,8 +0,0 @@
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc
{
public interface IActionResultFilter : IFilter<ActionResultFilterContext>
{
}
}

View File

@ -1,4 +1,4 @@
namespace Microsoft.AspNet.Mvc.Filters
namespace Microsoft.AspNet.Mvc
{
public interface IAllowAnonymous : IFilter
{

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IAsyncActionFilter : IFilter
{
Task OnActionExecutionAsync([NotNull] ActionExecutingContext context, [NotNull] ActionExecutionDelegate next);
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IAsyncAuthorizationFilter : IFilter
{
Task OnAuthorizationAsync([NotNull] AuthorizationContext context);
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IAsyncExceptionFilter : IFilter
{
Task OnActionExecutedAsync([NotNull] ExceptionContext context);
}
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IAsyncResultFilter : IFilter
{
Task OnResultExecutionAsync([NotNull] ResultExecutingContext context, [NotNull] ResultExecutionDelegate next);
}
}

View File

@ -1,8 +1,10 @@
using Microsoft.AspNet.Mvc.Filters;
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IAuthorizationFilter : IFilter<AuthorizationFilterContext>
public interface IAuthorizationFilter : IFilter
{
void OnAuthorization([NotNull] AuthorizationContext context);
}
}

View File

@ -1,8 +1,10 @@
using Microsoft.AspNet.Mvc.Filters;
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IExceptionFilter : IFilter<ExceptionFilterContext>
public interface IExceptionFilter : IFilter
{
void OnActionExecuted([NotNull] ExceptionContext context);
}
}

View File

@ -1,10 +0,0 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Filters
{
public interface IFilter<TContext>
{
Task Invoke(TContext context, Func<Task> next);
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNet.Mvc
{
public interface IResultFilter : IFilter
{
void OnResultExecuting([NotNull] ResultExecutingContext context);
void OnResultExecuted([NotNull] ResultExecutedContext context);
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
namespace Microsoft.AspNet.Mvc
{
public class ResultExecutedContext : FilterContext
{
private Exception _exception;
private ExceptionDispatchInfo _exceptionDispatchInfo;
public ResultExecutedContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters,
[NotNull] IActionResult result)
: base(actionContext, filters)
{
Result = result;
}
public virtual bool Canceled { get; set; }
public virtual Exception Exception
{
get
{
if (_exception == null && _exceptionDispatchInfo != null)
{
return _exceptionDispatchInfo.SourceException;
}
else
{
return _exception;
}
}
set
{
_exceptionDispatchInfo = null;
_exception = value;
}
}
public virtual ExceptionDispatchInfo ExceptionDispatchInfo
{
get
{
return _exceptionDispatchInfo;
}
set
{
_exception = null;
_exceptionDispatchInfo = value;
}
}
public virtual bool ExceptionHandled { get; set; }
public virtual IActionResult Result { get; private set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
public class ResultExecutingContext : FilterContext
{
public ResultExecutingContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters,
[NotNull] IActionResult result)
: base(actionContext, filters)
{
Result = result;
}
public virtual IActionResult Result { get; set; }
public virtual bool Cancel { get; set; }
}
}

View File

@ -0,0 +1,6 @@
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public delegate Task<ResultExecutedContext> ResultExecutionDelegate();
}

View File

@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ResultFilterAttribute : Attribute, IResultFilter, IAsyncResultFilter, IOrderedFilter
{
public int Order { get; set; }
public virtual void OnResultExecuting([NotNull] ResultExecutingContext context)
{
}
public virtual void OnResultExecuted([NotNull] ResultExecutedContext context)
{
}
public virtual async Task OnResultExecutionAsync([NotNull] ResultExecutingContext context, [NotNull] ResultExecutionDelegate next)
{
OnResultExecuting(context);
if (context.Result == null)
{
OnResultExecuted(await next());
}
}
}
}

View File

@ -53,39 +53,41 @@
<Compile Include="DefaultControllerDescriptorFactory.cs" />
<Compile Include="DefaultControllerFactory.cs" />
<Compile Include="DefaultParameterDescriptorFactory.cs" />
<Compile Include="Extensions\FilterContextExtensions.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Filters\ActionExecutedContext.cs" />
<Compile Include="Filters\ActionExecutingContext.cs" />
<Compile Include="Filters\ActionExecutionDelegate.cs" />
<Compile Include="Filters\ActionFilterAttribute.cs" />
<Compile Include="Filters\ActionFilterContext.cs" />
<Compile Include="Filters\ActionFilterEndPoint.cs" />
<Compile Include="Filters\ActionResultFilterAttribute.cs" />
<Compile Include="Filters\ActionResultFilterContext.cs" />
<Compile Include="Filters\ActionResultFilterEndPoint.cs" />
<Compile Include="Filters\AllowAnonymousAttribute.cs" />
<Compile Include="Filters\AuthorizationContext.cs" />
<Compile Include="Filters\AuthorizationFilterAttribute.cs" />
<Compile Include="Filters\AuthorizationFilterContext.cs" />
<Compile Include="Filters\AuthorizationFilterEndPoint.cs" />
<Compile Include="Filters\DefaultFilterProvider.cs" />
<Compile Include="Filters\ExceptionContext.cs" />
<Compile Include="Filters\ExceptionFilterAttribute.cs" />
<Compile Include="Filters\ExceptionFilterContext.cs" />
<Compile Include="Filters\FilterContext.cs" />
<Compile Include="Filters\FilterDescriptor.cs" />
<Compile Include="Filters\FilterDescriptorOrderComparer.cs" />
<Compile Include="Filters\FilterItem.cs" />
<Compile Include="Filters\FilterPipelineBuilder.cs" />
<Compile Include="Filters\FilterProviderContext.cs" />
<Compile Include="Filters\FilterScope.cs" />
<Compile Include="Filters\IActionFilter.cs" />
<Compile Include="Filters\IActionResultFilter.cs" />
<Compile Include="Filters\IAllowAnonymous.cs" />
<Compile Include="Filters\IAsyncActionFilter.cs" />
<Compile Include="Filters\IAsyncAuthorizationFilter.cs" />
<Compile Include="Filters\IAsyncExceptionFilter.cs" />
<Compile Include="Filters\IAsyncResultFilter.cs" />
<Compile Include="Filters\IAuthorizationFilter.cs" />
<Compile Include="Filters\IExceptionFilter.cs" />
<Compile Include="Filters\IFilter.cs" />
<Compile Include="Filters\IFilterContainer.cs" />
<Compile Include="Filters\IFilterOfTContext.cs" />
<Compile Include="Filters\IOrderedFilter.cs" />
<Compile Include="Filters\IResultFilter.cs" />
<Compile Include="Filters\IServiceFilter.cs" />
<Compile Include="Filters\ITypeFilter.cs" />
<Compile Include="Filters\ResultExecutedContext.cs" />
<Compile Include="Filters\ResultExecutingContext.cs" />
<Compile Include="Filters\ResultExecutionDelegate.cs" />
<Compile Include="Filters\ResultFilterAttribute.cs" />
<Compile Include="Filters\ServiceFilterAttribute.cs" />
<Compile Include="FormContext.cs" />
<Compile Include="HttpDeleteAttribute.cs" />

View File

@ -554,12 +554,44 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("NoRoutesMatched");
}
/// <summary>
/// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then the it should not call the next filter by invoking {3}.
/// </summary>
internal static string AsyncActionFilter_InvalidShortCircuit
{
get { return GetString("AsyncActionFilter_InvalidShortCircuit"); }
}
/// <summary>
/// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then the it should not call the next filter by invoking {3}.
/// </summary>
internal static string FormatAsyncActionFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AsyncActionFilter_InvalidShortCircuit"), p0, p1, p2, p3);
}
/// <summary>
/// If an {0} cancels execution by setting the {1} property of {2} to 'true', then the it should not call the next filter by invoking {3}.
/// </summary>
internal static string AsyncResultFilter_InvalidShortCircuit
{
get { return GetString("AsyncResultFilter_InvalidShortCircuit"); }
}
/// <summary>
/// If an {0} cancels execution by setting the {1} property of {2} to 'true', then the it should not call the next filter by invoking {3}.
/// </summary>
internal static string FormatAsyncResultFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3)
{
return string.Format(CultureInfo.CurrentCulture, GetString("AsyncResultFilter_InvalidShortCircuit"), p0, p1, p2, p3);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)

View File

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
@ -19,9 +19,18 @@ namespace Microsoft.AspNet.Mvc
private readonly IActionBindingContextProvider _bindingProvider;
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
private readonly List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
private readonly List<IActionFilter> _actionFilters = new List<IActionFilter>();
private readonly List<IActionResultFilter> _actionResultFilters = new List<IActionResultFilter>();
private IFilter[] _filters;
private FilterCursor _cursor;
private ExceptionContext _exceptionContext;
private AuthorizationContext _authorizationContext;
private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext;
private ResultExecutingContext _resultExecutingContext;
private ResultExecutedContext _resultExecutedContext;
public ReflectedActionInvoker([NotNull] ActionContext actionContext,
[NotNull] ReflectedActionDescriptor descriptor,
@ -48,71 +57,184 @@ namespace Microsoft.AspNet.Mvc
public async Task InvokeActionAsync()
{
var filterMetaItems = GetAndArrangeFilters();
_filters = GetFilters();
_cursor = new FilterCursor(_filters);
var controller = _controllerFactory.CreateController(_actionContext);
// >> ExceptionFilters >> AuthorizationFilters >> ActionFilters >> Action
await InvokeActionExceptionFilters();
if (controller == null)
// If Exception Filters or Authorization Filters provide a result, it's a short-circuit, we don't execute
// result filters around it.
if (_authorizationContext.Result != null)
{
throw new InvalidOperationException(
Resources.FormatMethodMustReturnNotNullValue(typeof(IControllerFactory),
"controller"));
await _authorizationContext.Result.ExecuteResultAsync(_actionContext);
}
try
else if (_exceptionContext.Result != null)
{
var actionResult = await RunAuthorizationFilters(filterMetaItems) ??
await RunActionFiltersAndActions(filterMetaItems, controller);
await RunActionResultFilters(actionResult, filterMetaItems);
await _exceptionContext.Result.ExecuteResultAsync(_actionContext);
}
finally
else if (_exceptionContext.Exception != null)
{
_controllerFactory.ReleaseController(controller);
}
}
private FilterItem[] GetAndArrangeFilters()
{
var filterProviderContext =
new FilterProviderContext(_descriptor,
_descriptor.
FilterDescriptors.
Select(fd => new FilterItem(fd)).ToList());
_filterProvider.Invoke(filterProviderContext);
var filterMetaItems = filterProviderContext.Result.ToArray();
PreArrangeFiltersInPipeline(filterProviderContext);
return filterMetaItems;
}
private async Task<IActionResult> RunAuthorizationFilters(FilterItem[] filterMetaItems)
{
if (_authorizationFilters.Count > 0)
{
var authZEndPoint = new AuthorizationFilterEndPoint();
_authorizationFilters.Add(authZEndPoint);
var authZContext = new AuthorizationFilterContext(_actionContext, filterMetaItems);
var authZPipeline = new FilterPipelineBuilder<AuthorizationFilterContext>(_authorizationFilters, authZContext);
await authZPipeline.InvokeAsync();
if (authZContext.ActionResult != null ||
authZContext.HasFailed ||
!authZEndPoint.WasEndPointCalled)
// If we get here, this means that we have an unhandled exception
if (_exceptionContext.ExceptionDispatchInfo != null)
{
// User cleaned out the result but we failed or short circuited the end point.
return authZContext.ActionResult ?? new HttpStatusCodeResult(401);
_exceptionContext.ExceptionDispatchInfo.Throw();
}
else
{
throw _exceptionContext.Exception;
}
}
else
{
var result = _actionExecutedContext.Result;
return null;
// >> ResultFilters >> (Result)
await InvokeActionResultWithFilters(result);
}
}
private async Task<IDictionary<string, object>> GetParameterValues(ModelStateDictionary modelState)
private IFilter[] GetFilters()
{
var filterProviderContext = new FilterProviderContext(
_descriptor,
_descriptor.FilterDescriptors.Select(fd => new FilterItem(fd)).ToList());
_filterProvider.Invoke(filterProviderContext);
return filterProviderContext.Result.Select(item => item.Filter).Where(filter => filter != null).ToArray();
}
private async Task InvokeActionExceptionFilters()
{
_cursor.SetStage(FilterStage.ExceptionFilters);
await InvokeExceptionFilter();
}
private async Task InvokeExceptionFilter()
{
var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
if (current.FilterAsync != null)
{
// Exception filters run "on the way out" - so the filter is run after the rest of the
// pipeline.
await InvokeExceptionFilter();
Contract.Assert(_exceptionContext != null);
if (_exceptionContext.Exception != null)
{
// Exception filters only run when there's an exception - unsetting it will short-circuit
// other exception filters.
await current.FilterAsync.OnActionExecutedAsync(_exceptionContext);
}
}
else if (current.Filter != null)
{
// Exception filters run "on the way out" - so the filter is run after the rest of the
// pipeline.
await InvokeExceptionFilter();
Contract.Assert(_exceptionContext != null);
if (_exceptionContext.Exception != null)
{
// Exception filters only run when there's an exception - unsetting it will short-circuit
// other exception filters.
current.Filter.OnActionExecuted(_exceptionContext);
}
}
else
{
// We've reached the 'end' of the exception filter pipeline - this means that one stack frame has
// been built for each exception. When we return from here, these frames will either:
//
// 1) Call the filter (if we have an exception)
// 2) No-op (if we don't have an exception)
Contract.Assert(_exceptionContext == null);
_exceptionContext = new ExceptionContext(_actionContext, _filters);
try
{
await InvokeActionAuthorizationFilters();
Contract.Assert(_authorizationContext != null);
if (_authorizationContext.Result == null)
{
// Authorization passed, run authorization filters and the action
await InvokeActionMethodWithFilters();
// Action filters might 'return' an unahndled exception instead of throwing
Contract.Assert(_actionExecutedContext != null);
if (_actionExecutedContext.Exception != null && !_actionExecutedContext.ExceptionHandled)
{
_exceptionContext.Exception = _actionExecutedContext.Exception;
if (_actionExecutedContext.ExceptionDispatchInfo != null)
{
_exceptionContext.ExceptionDispatchInfo = _actionExecutedContext.ExceptionDispatchInfo;
}
}
}
}
catch (Exception exception)
{
_exceptionContext.ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
}
}
}
private async Task InvokeActionAuthorizationFilters()
{
_cursor.SetStage(FilterStage.AuthorizationFilters);
_authorizationContext = new AuthorizationContext(_actionContext, _filters);
await InvokeAuthorizationFilter();
}
private async Task InvokeAuthorizationFilter()
{
// We should never get here if we already have a result.
Contract.Assert(_authorizationContext != null);
Contract.Assert(_authorizationContext.Result == null);
var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
if (current.FilterAsync != null)
{
await current.FilterAsync.OnAuthorizationAsync(_authorizationContext);
if (_authorizationContext.Result == null)
{
// Only keep going if we don't have a result
await InvokeAuthorizationFilter();
}
}
else if (current.Filter != null)
{
current.Filter.OnAuthorization(_authorizationContext);
if (_authorizationContext.Result == null)
{
// Only keep going if we don't have a result
await InvokeAuthorizationFilter();
}
}
else
{
// We've run out of Authorization Filters - if we haven't short circuited by now then this
// request is authorized.
}
}
private async Task InvokeActionMethodWithFilters()
{
_cursor.SetStage(FilterStage.ActionFilters);
var arguments = await GetActionArguments(_actionContext.ModelState);
_actionExecutingContext = new ActionExecutingContext(_actionContext, _filters, arguments);
await InvokeActionMethodFilter();
}
private async Task<IDictionary<string, object>> GetActionArguments(ModelStateDictionary modelState)
{
var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(_actionContext);
var parameters = _descriptor.Parameters;
@ -164,69 +286,304 @@ namespace Microsoft.AspNet.Mvc
return parameterValues;
}
private async Task<IActionResult> RunActionFiltersAndActions(FilterItem[] filterMetaItems, object controller)
private async Task<ActionExecutedContext> InvokeActionMethodFilter()
{
var parameterValues = await GetParameterValues(_actionContext.ModelState);
var actionFilterContext = new ActionFilterContext(_actionContext,
filterMetaItems,
parameterValues);
var actionEndPoint = new ReflectedActionFilterEndPoint(_actionResultFactory, controller);
_actionFilters.Add(actionEndPoint);
var actionFilterPipeline = new FilterPipelineBuilder<ActionFilterContext>(_actionFilters,
actionFilterContext);
await actionFilterPipeline.InvokeAsync();
return actionFilterContext.ActionResult;
}
private async Task RunActionResultFilters(IActionResult actionResult, FilterItem[] filterMetaItems)
{
var actionResultFilterContext = new ActionResultFilterContext(_actionContext, filterMetaItems, actionResult);
var actionResultFilterEndPoint = new ActionResultFilterEndPoint();
_actionResultFilters.Add(actionResultFilterEndPoint);
var actionResultPipeline = new FilterPipelineBuilder<ActionResultFilterContext>(_actionResultFilters,
actionResultFilterContext);
await actionResultPipeline.InvokeAsync();
}
private void PreArrangeFiltersInPipeline(FilterProviderContext context)
{
if (context.Result == null || context.Result.Count == 0)
Contract.Assert(_actionExecutingContext != null);
if (_actionExecutingContext.Result != null)
{
return;
// If we get here, it means that an async filter set a result AND called next(). This is forbidden.
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
typeof(IAsyncActionFilter).Name,
"Result",
typeof(ActionExecutingContext).Name,
typeof(ActionExecutionDelegate).Name);
throw new InvalidOperationException(message);
}
foreach (var filter in context.Result)
var item = _cursor.GetNextFilter<IActionFilter, IAsyncActionFilter>();
try
{
PlaceFilter(filter.Filter);
if (item.FilterAsync != null)
{
await item.FilterAsync.OnActionExecutionAsync(_actionExecutingContext, InvokeActionMethodFilter);
if (_actionExecutedContext == null)
{
// If we get here then the filter didn't call 'next' indicating a short circuit
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
{
Canceled = true,
Result = _actionExecutingContext.Result,
};
}
}
else if (item.Filter != null)
{
item.Filter.OnActionExecuting(_actionExecutingContext);
if (_actionExecutingContext.Result != null)
{
// Short-circuited by setting a result.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
{
Canceled = true,
Result = _actionExecutingContext.Result,
};
}
else
{
item.Filter.OnActionExecuted(await InvokeActionMethodFilter());
}
}
else
{
// All action filters have run, execute the action method.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
{
Result = await InvokeActionMethod()
};
}
}
catch (Exception exception)
{
// Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
{
Exception = exception,
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
};
}
return _actionExecutedContext;
}
private async Task<IActionResult> InvokeActionMethod()
{
_cursor.SetStage(FilterStage.ActionMethod);
var controller = _controllerFactory.CreateController(_actionContext);
var actionMethodInfo = _descriptor.MethodInfo;
var actionReturnValue = await ReflectedActionExecutor.ExecuteAsync(
actionMethodInfo,
controller,
_actionExecutingContext.ActionArguments);
var underlyingReturnType = TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? actionMethodInfo.ReturnType;
var actionResult = _actionResultFactory.CreateActionResult(
underlyingReturnType,
actionReturnValue,
_actionContext);
return actionResult;
}
private async Task InvokeActionResultWithFilters(IActionResult result)
{
_cursor.SetStage(FilterStage.ResultFilters);
_resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, result);
await InvokeActionResultFilter();
Contract.Assert(_resultExecutingContext != null);
if (_resultExecutedContext.Exception != null && !_resultExecutedContext.ExceptionHandled)
{
// There's an unhandled exception in filters
if (_resultExecutedContext.ExceptionDispatchInfo != null)
{
_resultExecutedContext.ExceptionDispatchInfo.Throw();
}
else
{
throw _resultExecutedContext.Exception;
}
}
}
private void PlaceFilter(object filter)
private async Task<ResultExecutedContext> InvokeActionResultFilter()
{
var authFilter = filter as IAuthorizationFilter;
var actionFilter = filter as IActionFilter;
var actionResultFilter = filter as IActionResultFilter;
if (authFilter != null)
Contract.Assert(_resultExecutingContext != null);
if (_resultExecutingContext.Cancel == true)
{
_authorizationFilters.Add(authFilter);
// If we get here, it means that an async filter set cancel == true AND called next(). This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name,
"Cancel",
typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name);
throw new InvalidOperationException(message);
}
if (actionFilter != null)
try
{
_actionFilters.Add(actionFilter);
var item = _cursor.GetNextFilter<IResultFilter, IAsyncResultFilter>();
if (item.FilterAsync != null)
{
await item.FilterAsync.OnResultExecutionAsync(_resultExecutingContext, InvokeActionResultFilter);
if (_resultExecutedContext == null)
{
// Short-circuited by not calling next
_resultExecutedContext = new ResultExecutedContext(_resultExecutingContext, _filters, _resultExecutingContext.Result)
{
Canceled = true,
};
}
else if (_resultExecutingContext.Cancel == true)
{
// Short-circuited by setting Cancel == true
_resultExecutedContext = new ResultExecutedContext(_resultExecutingContext, _filters, _resultExecutingContext.Result)
{
Canceled = true,
};
}
}
else if (item.Filter != null)
{
item.Filter.OnResultExecuting(_resultExecutingContext);
if (_resultExecutingContext.Cancel == true)
{
// Short-circuited by setting Cancel == true
_resultExecutedContext = new ResultExecutedContext(_resultExecutingContext, _filters, _resultExecutingContext.Result)
{
Canceled = true,
};
}
else
{
item.Filter.OnResultExecuted(await InvokeActionResultFilter());
}
}
else
{
await InvokeActionResult();
Contract.Assert(_resultExecutedContext == null);
_resultExecutedContext = new ResultExecutedContext(_resultExecutingContext, _filters, _resultExecutingContext.Result);
}
}
catch (Exception exception)
{
_resultExecutedContext = new ResultExecutedContext(_resultExecutingContext, _filters, _resultExecutingContext.Result)
{
Exception = exception,
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
};
}
if (actionResultFilter != null)
return _resultExecutedContext;
}
private async Task InvokeActionResult()
{
_cursor.SetStage(FilterStage.ActionResult);
// The empty result is always flowed back as the 'executed' result
if (_resultExecutingContext.Result == null)
{
_actionResultFilters.Add(actionResultFilter);
_resultExecutingContext.Result = new EmptyResult();
}
await _resultExecutingContext.Result.ExecuteResultAsync(_resultExecutingContext);
}
private enum FilterStage
{
Undefined,
ExceptionFilters,
AuthorizationFilters,
ActionFilters,
ActionMethod,
ResultFilters,
ActionResult
};
/// <summary>
/// A one-way cursor for filters.
/// </summary>
/// <remarks>
/// This will iterate the filter collection once per-stage, and skip any filters that don't have
/// the one of interfaces that applies to the current stage.
///
/// Filters are always executed in the following order, but short circuiting plays a role.
///
/// Indentation reflects nesting.
///
/// 1. Exception Filters
/// 2. Authorization Filters
/// 3. Action Filters
/// Action
///
/// 4. Result Filters
/// Result
///
/// </remarks>
private struct FilterCursor
{
private FilterStage Stage;
private int Index;
private readonly IFilter[] Filters;
public FilterCursor(FilterStage stage, int index, IFilter[] filters)
{
Stage = stage;
Index = index;
Filters = filters;
}
public FilterCursor(IFilter[] filters)
{
Stage = FilterStage.Undefined;
Index = 0;
Filters = filters;
}
public void SetStage(FilterStage stage)
{
Stage = stage;
Index = 0;
}
public FilterCursorItem<TFilter, TFilterAsync> GetNextFilter<TFilter, TFilterAsync>()
where TFilter : class
where TFilterAsync : class
{
while (Index < Filters.Length)
{
var filter = Filters[Index] as TFilter;
var filterAsync = Filters[Index] as TFilterAsync;
Index += 1;
if (filter != null || filterAsync != null)
{
return new FilterCursorItem<TFilter, TFilterAsync>(Stage, Index, filter, filterAsync);
}
}
return default(FilterCursorItem<TFilter, TFilterAsync>);
}
public bool StillAt<TFilter, TFilterAsync>(FilterCursorItem<TFilter, TFilterAsync> current)
{
return current.Stage == Stage && current.Index == Index;
}
}
private struct FilterCursorItem<TFilter, TFilterAsync>
{
public readonly FilterStage Stage;
public readonly int Index;
public readonly TFilter Filter;
public readonly TFilterAsync FilterAsync;
public FilterCursorItem(FilterStage stage, int index, TFilter filter, TFilterAsync filterAsync)
{
Stage = stage;
Index = index;
Filter = filter;
FilterAsync = filterAsync;
}
}
}

View File

@ -219,4 +219,10 @@
<data name="NoRoutesMatched" xml:space="preserve">
<value>No route matches the supplied values.</value>
</data>
</root>
<data name="AsyncActionFilter_InvalidShortCircuit" xml:space="preserve">
<value>If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then the it should not call the next filter by invoking {3}.</value>
</data>
<data name="AsyncResultFilter_InvalidShortCircuit" xml:space="preserve">
<value>If an {0} cancels execution by setting the {1} property of {2} to 'true', then the it should not call the next filter by invoking {3}.</value>
</data>
</root>

View File

@ -383,7 +383,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)

View File

@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)

View File

@ -239,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)

View File

@ -31,6 +31,7 @@
<Compile Include="DefaultControllerAssemblyProviderTests.cs" />
<Compile Include="DefaultControllerFactoryTest.cs" />
<Compile Include="PropertyHelperTest.cs" />
<Compile Include="ReflectedActionInvokerTest.cs" />
<Compile Include="Rendering\HtmlAttributePropertyHelperTest.cs" />
<Compile Include="Rendering\ViewContextTests.cs" />
<Compile Include="Rendering\ViewDataOfTTest.cs" />

File diff suppressed because it is too large Load Diff