1
namespace System
.Web
.Mvc
.Async
{
3 using System
.Collections
.Generic
;
5 using System
.Threading
;
7 public class AsyncControllerActionInvoker
: ControllerActionInvoker
, IAsyncActionInvoker
{
9 private static readonly object _invokeActionTag
= new object();
10 private static readonly object _invokeActionMethodTag
= new object();
11 private static readonly object _invokeActionMethodWithFiltersTag
= new object();
13 public virtual IAsyncResult
BeginInvokeAction(ControllerContext controllerContext
, string actionName
, AsyncCallback callback
, object state
) {
14 if (controllerContext
== null) {
15 throw new ArgumentNullException("controllerContext");
17 if (String
.IsNullOrEmpty(actionName
)) {
18 throw Error
.ParameterCannotBeNullOrEmpty("actionName");
21 ControllerDescriptor controllerDescriptor
= GetControllerDescriptor(controllerContext
);
22 ActionDescriptor actionDescriptor
= controllerDescriptor
.FindAction(controllerContext
, actionName
);
23 if (actionDescriptor
!= null) {
24 FilterInfo filterInfo
= GetFilters(controllerContext
, actionDescriptor
);
25 Action continuation
= null;
27 BeginInvokeDelegate beginDelegate
= delegate(AsyncCallback asyncCallback
, object asyncState
) {
29 AuthorizationContext authContext
= InvokeAuthorizationFilters(controllerContext
, filterInfo
.AuthorizationFilters
, actionDescriptor
);
30 if (authContext
.Result
!= null) {
31 // the auth filter signaled that we should let it short-circuit the request
32 continuation
= () => InvokeActionResult(controllerContext
, authContext
.Result
);
35 if (controllerContext
.Controller
.ValidateRequest
) {
36 ValidateRequest(controllerContext
);
39 IDictionary
<string, object> parameters
= GetParameterValues(controllerContext
, actionDescriptor
);
40 IAsyncResult asyncResult
= BeginInvokeActionMethodWithFilters(controllerContext
, filterInfo
.ActionFilters
, actionDescriptor
, parameters
, asyncCallback
, asyncState
);
41 continuation
= () => {
42 ActionExecutedContext postActionContext
= EndInvokeActionMethodWithFilters(asyncResult
);
43 InvokeActionResultWithFilters(controllerContext
, filterInfo
.ResultFilters
, postActionContext
.Result
);
48 catch (ThreadAbortException
) {
49 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
50 // the filters don't see this as an error.
53 catch (Exception ex
) {
54 // something blew up, so execute the exception filters
55 ExceptionContext exceptionContext
= InvokeExceptionFilters(controllerContext
, filterInfo
.ExceptionFilters
, ex
);
56 if (!exceptionContext
.ExceptionHandled
) {
60 continuation
= () => InvokeActionResult(controllerContext
, exceptionContext
.Result
);
63 return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback
, asyncState
);
66 EndInvokeDelegate
<bool> endDelegate
= delegate(IAsyncResult asyncResult
) {
70 catch (ThreadAbortException
) {
71 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
72 // the filters don't see this as an error.
75 catch (Exception ex
) {
76 // something blew up, so execute the exception filters
77 ExceptionContext exceptionContext
= InvokeExceptionFilters(controllerContext
, filterInfo
.ExceptionFilters
, ex
);
78 if (!exceptionContext
.ExceptionHandled
) {
81 InvokeActionResult(controllerContext
, exceptionContext
.Result
);
87 return AsyncResultWrapper
.Begin(callback
, state
, beginDelegate
, endDelegate
, _invokeActionTag
);
90 // Notify the controller that no action was found.
91 return BeginInvokeAction_ActionNotFound(callback
, state
);
95 private static IAsyncResult
BeginInvokeAction_ActionNotFound(AsyncCallback callback
, object state
) {
96 BeginInvokeDelegate beginDelegate
= BeginInvokeAction_MakeSynchronousAsyncResult
;
98 EndInvokeDelegate
<bool> endDelegate
= delegate(IAsyncResult asyncResult
) {
102 return AsyncResultWrapper
.Begin(callback
, state
, beginDelegate
, endDelegate
, _invokeActionTag
);
105 private static IAsyncResult
BeginInvokeAction_MakeSynchronousAsyncResult(AsyncCallback callback
, object state
) {
106 SimpleAsyncResult asyncResult
= new SimpleAsyncResult(state
);
107 asyncResult
.MarkCompleted(true /* completedSynchronously */, callback
);
111 protected internal virtual IAsyncResult
BeginInvokeActionMethod(ControllerContext controllerContext
, ActionDescriptor actionDescriptor
, IDictionary
<string, object> parameters
, AsyncCallback callback
, object state
) {
112 AsyncActionDescriptor asyncActionDescriptor
= actionDescriptor
as AsyncActionDescriptor
;
113 if (asyncActionDescriptor
!= null) {
114 return BeginInvokeAsynchronousActionMethod(controllerContext
, asyncActionDescriptor
, parameters
, callback
, state
);
117 return BeginInvokeSynchronousActionMethod(controllerContext
, actionDescriptor
, parameters
, callback
, state
);
121 protected internal virtual IAsyncResult
BeginInvokeActionMethodWithFilters(ControllerContext controllerContext
, IList
<IActionFilter
> filters
, ActionDescriptor actionDescriptor
, IDictionary
<string, object> parameters
, AsyncCallback callback
, object state
) {
122 Func
<ActionExecutedContext
> endContinuation
= null;
124 BeginInvokeDelegate beginDelegate
= delegate(AsyncCallback asyncCallback
, object asyncState
) {
125 ActionExecutingContext preContext
= new ActionExecutingContext(controllerContext
, actionDescriptor
, parameters
);
126 IAsyncResult innerAsyncResult
= null;
128 Func
<Func
<ActionExecutedContext
>> beginContinuation
= () => {
129 innerAsyncResult
= BeginInvokeActionMethod(controllerContext
, actionDescriptor
, parameters
, asyncCallback
, asyncState
);
131 new ActionExecutedContext(controllerContext
, actionDescriptor
, false /* canceled */, null /* exception */) {
132 Result
= EndInvokeActionMethod(innerAsyncResult
)
136 // need to reverse the filter list because the continuations are built up backward
137 Func
<Func
<ActionExecutedContext
>> thunk
= filters
.Reverse().Aggregate(beginContinuation
,
138 (next
, filter
) => () => InvokeActionMethodFilterAsynchronously(filter
, preContext
, next
));
139 endContinuation
= thunk();
141 if (innerAsyncResult
!= null) {
142 // we're just waiting for the inner result to complete
143 return innerAsyncResult
;
146 // something was short-circuited and the action was not called, so this was a synchronous operation
147 SimpleAsyncResult newAsyncResult
= new SimpleAsyncResult(asyncState
);
148 newAsyncResult
.MarkCompleted(true /* completedSynchronously */, asyncCallback
);
149 return newAsyncResult
;
153 EndInvokeDelegate
<ActionExecutedContext
> endDelegate
= delegate(IAsyncResult asyncResult
) {
154 return endContinuation();
157 return AsyncResultWrapper
.Begin(callback
, state
, beginDelegate
, endDelegate
, _invokeActionMethodWithFiltersTag
);
160 private IAsyncResult
BeginInvokeAsynchronousActionMethod(ControllerContext controllerContext
, AsyncActionDescriptor actionDescriptor
, IDictionary
<string, object> parameters
, AsyncCallback callback
, object state
) {
161 BeginInvokeDelegate beginDelegate
= delegate(AsyncCallback asyncCallback
, object asyncState
) {
162 return actionDescriptor
.BeginExecute(controllerContext
, parameters
, asyncCallback
, asyncState
);
165 EndInvokeDelegate
<ActionResult
> endDelegate
= delegate(IAsyncResult asyncResult
) {
166 object returnValue
= actionDescriptor
.EndExecute(asyncResult
);
167 ActionResult result
= CreateActionResult(controllerContext
, actionDescriptor
, returnValue
);
171 return AsyncResultWrapper
.Begin(callback
, state
, beginDelegate
, endDelegate
, _invokeActionMethodTag
);
174 private IAsyncResult
BeginInvokeSynchronousActionMethod(ControllerContext controllerContext
, ActionDescriptor actionDescriptor
, IDictionary
<string, object> parameters
, AsyncCallback callback
, object state
) {
175 return AsyncResultWrapper
.BeginSynchronous(callback
, state
,
176 () => InvokeSynchronousActionMethod(controllerContext
, actionDescriptor
, parameters
),
177 _invokeActionMethodTag
);
180 public virtual bool EndInvokeAction(IAsyncResult asyncResult
) {
181 return AsyncResultWrapper
.End
<bool>(asyncResult
, _invokeActionTag
);
184 protected internal virtual ActionResult
EndInvokeActionMethod(IAsyncResult asyncResult
) {
185 return AsyncResultWrapper
.End
<ActionResult
>(asyncResult
, _invokeActionMethodTag
);
188 protected internal virtual ActionExecutedContext
EndInvokeActionMethodWithFilters(IAsyncResult asyncResult
) {
189 return AsyncResultWrapper
.End
<ActionExecutedContext
>(asyncResult
, _invokeActionMethodWithFiltersTag
);
192 protected override ControllerDescriptor
GetControllerDescriptor(ControllerContext controllerContext
) {
193 Type controllerType
= controllerContext
.Controller
.GetType();
194 ControllerDescriptor controllerDescriptor
= DescriptorCache
.GetDescriptor(controllerType
, () => new ReflectedAsyncControllerDescriptor(controllerType
));
195 return controllerDescriptor
;
198 internal static Func
<ActionExecutedContext
> InvokeActionMethodFilterAsynchronously(IActionFilter filter
, ActionExecutingContext preContext
, Func
<Func
<ActionExecutedContext
>> nextInChain
) {
199 filter
.OnActionExecuting(preContext
);
200 if (preContext
.Result
!= null) {
201 ActionExecutedContext shortCircuitedPostContext
= new ActionExecutedContext(preContext
, preContext
.ActionDescriptor
, true /* canceled */, null /* exception */) {
202 Result
= preContext
.Result
204 return () => shortCircuitedPostContext
;
207 // There is a nested try / catch block here that contains much the same logic as the outer block.
208 // Since an exception can occur on either side of the asynchronous invocation, we need guards on
209 // on both sides. In the code below, the second side is represented by the nested delegate. This
210 // is really just a parallel of the synchronous ControllerActionInvoker.InvokeActionMethodFilter()
214 Func
<ActionExecutedContext
> continuation
= nextInChain();
216 // add our own continuation, then return the new function
218 ActionExecutedContext postContext
;
219 bool wasError
= true;
222 postContext
= continuation();
225 catch (ThreadAbortException
) {
226 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
227 // the filters don't see this as an error.
228 postContext
= new ActionExecutedContext(preContext
, preContext
.ActionDescriptor
, false /* canceled */, null /* exception */);
229 filter
.OnActionExecuted(postContext
);
232 catch (Exception ex
) {
233 postContext
= new ActionExecutedContext(preContext
, preContext
.ActionDescriptor
, false /* canceled */, ex
);
234 filter
.OnActionExecuted(postContext
);
235 if (!postContext
.ExceptionHandled
) {
240 filter
.OnActionExecuted(postContext
);
246 catch (ThreadAbortException
) {
247 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
248 // the filters don't see this as an error.
249 ActionExecutedContext postContext
= new ActionExecutedContext(preContext
, preContext
.ActionDescriptor
, false /* canceled */, null /* exception */);
250 filter
.OnActionExecuted(postContext
);
253 catch (Exception ex
) {
254 ActionExecutedContext postContext
= new ActionExecutedContext(preContext
, preContext
.ActionDescriptor
, false /* canceled */, ex
);
255 filter
.OnActionExecuted(postContext
);
256 if (postContext
.ExceptionHandled
) {
257 return () => postContext
;
265 private ActionResult
InvokeSynchronousActionMethod(ControllerContext controllerContext
, ActionDescriptor actionDescriptor
, IDictionary
<string, object> parameters
) {
266 return base.InvokeActionMethod(controllerContext
, actionDescriptor
, parameters
);