1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\r
5 * This software is subject to the Microsoft Public License (Ms-PL).
\r
6 * A copy of the license can be found in the license.htm file included
\r
7 * in this distribution.
\r
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc.Async {
\r
15 using System.Collections.Generic;
\r
17 using System.Threading;
\r
19 public class AsyncControllerActionInvoker : ControllerActionInvoker, IAsyncActionInvoker {
\r
21 private static readonly object _invokeActionTag = new object();
\r
22 private static readonly object _invokeActionMethodTag = new object();
\r
23 private static readonly object _invokeActionMethodWithFiltersTag = new object();
\r
25 public virtual IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state) {
\r
26 if (controllerContext == null) {
\r
27 throw new ArgumentNullException("controllerContext");
\r
29 if (String.IsNullOrEmpty(actionName)) {
\r
30 throw Error.ParameterCannotBeNullOrEmpty("actionName");
\r
33 ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
\r
34 ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
\r
35 if (actionDescriptor != null) {
\r
36 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
\r
37 Action continuation = null;
\r
39 BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
\r
41 AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
\r
42 if (authContext.Result != null) {
\r
43 // the auth filter signaled that we should let it short-circuit the request
\r
44 continuation = () => InvokeActionResult(controllerContext, authContext.Result);
\r
47 if (controllerContext.Controller.ValidateRequest) {
\r
48 ValidateRequest(controllerContext);
\r
51 IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
\r
52 IAsyncResult asyncResult = BeginInvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters, asyncCallback, asyncState);
\r
53 continuation = () => {
\r
54 ActionExecutedContext postActionContext = EndInvokeActionMethodWithFilters(asyncResult);
\r
55 InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
\r
60 catch (ThreadAbortException) {
\r
61 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
\r
62 // the filters don't see this as an error.
\r
65 catch (Exception ex) {
\r
66 // something blew up, so execute the exception filters
\r
67 ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
\r
68 if (!exceptionContext.ExceptionHandled) {
\r
72 continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);
\r
75 return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);
\r
78 EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
\r
82 catch (ThreadAbortException) {
\r
83 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
\r
84 // the filters don't see this as an error.
\r
87 catch (Exception ex) {
\r
88 // something blew up, so execute the exception filters
\r
89 ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
\r
90 if (!exceptionContext.ExceptionHandled) {
\r
93 InvokeActionResult(controllerContext, exceptionContext.Result);
\r
99 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
\r
102 // Notify the controller that no action was found.
\r
103 return BeginInvokeAction_ActionNotFound(callback, state);
\r
107 private static IAsyncResult BeginInvokeAction_ActionNotFound(AsyncCallback callback, object state) {
\r
108 BeginInvokeDelegate beginDelegate = BeginInvokeAction_MakeSynchronousAsyncResult;
\r
110 EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
\r
114 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
\r
117 private static IAsyncResult BeginInvokeAction_MakeSynchronousAsyncResult(AsyncCallback callback, object state) {
\r
118 SimpleAsyncResult asyncResult = new SimpleAsyncResult(state);
\r
119 asyncResult.MarkCompleted(true /* completedSynchronously */, callback);
\r
120 return asyncResult;
\r
123 protected internal virtual IAsyncResult BeginInvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
\r
124 AsyncActionDescriptor asyncActionDescriptor = actionDescriptor as AsyncActionDescriptor;
\r
125 if (asyncActionDescriptor != null) {
\r
126 return BeginInvokeAsynchronousActionMethod(controllerContext, asyncActionDescriptor, parameters, callback, state);
\r
129 return BeginInvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters, callback, state);
\r
133 protected internal virtual IAsyncResult BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
\r
134 Func<ActionExecutedContext> endContinuation = null;
\r
136 BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
\r
137 ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
\r
138 IAsyncResult innerAsyncResult = null;
\r
140 Func<Func<ActionExecutedContext>> beginContinuation = () => {
\r
141 innerAsyncResult = BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters, asyncCallback, asyncState);
\r
143 new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
\r
144 Result = EndInvokeActionMethod(innerAsyncResult)
\r
148 // need to reverse the filter list because the continuations are built up backward
\r
149 Func<Func<ActionExecutedContext>> thunk = filters.Reverse().Aggregate(beginContinuation,
\r
150 (next, filter) => () => InvokeActionMethodFilterAsynchronously(filter, preContext, next));
\r
151 endContinuation = thunk();
\r
153 if (innerAsyncResult != null) {
\r
154 // we're just waiting for the inner result to complete
\r
155 return innerAsyncResult;
\r
158 // something was short-circuited and the action was not called, so this was a synchronous operation
\r
159 SimpleAsyncResult newAsyncResult = new SimpleAsyncResult(asyncState);
\r
160 newAsyncResult.MarkCompleted(true /* completedSynchronously */, asyncCallback);
\r
161 return newAsyncResult;
\r
165 EndInvokeDelegate<ActionExecutedContext> endDelegate = delegate(IAsyncResult asyncResult) {
\r
166 return endContinuation();
\r
169 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodWithFiltersTag);
\r
172 private IAsyncResult BeginInvokeAsynchronousActionMethod(ControllerContext controllerContext, AsyncActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
\r
173 BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
\r
174 return actionDescriptor.BeginExecute(controllerContext, parameters, asyncCallback, asyncState);
\r
177 EndInvokeDelegate<ActionResult> endDelegate = delegate(IAsyncResult asyncResult) {
\r
178 object returnValue = actionDescriptor.EndExecute(asyncResult);
\r
179 ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
\r
183 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodTag);
\r
186 private IAsyncResult BeginInvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state) {
\r
187 return AsyncResultWrapper.BeginSynchronous(callback, state,
\r
188 () => InvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters),
\r
189 _invokeActionMethodTag);
\r
192 public virtual bool EndInvokeAction(IAsyncResult asyncResult) {
\r
193 return AsyncResultWrapper.End<bool>(asyncResult, _invokeActionTag);
\r
196 protected internal virtual ActionResult EndInvokeActionMethod(IAsyncResult asyncResult) {
\r
197 return AsyncResultWrapper.End<ActionResult>(asyncResult, _invokeActionMethodTag);
\r
200 protected internal virtual ActionExecutedContext EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) {
\r
201 return AsyncResultWrapper.End<ActionExecutedContext>(asyncResult, _invokeActionMethodWithFiltersTag);
\r
204 protected override ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext) {
\r
205 Type controllerType = controllerContext.Controller.GetType();
\r
206 ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(controllerType, () => new ReflectedAsyncControllerDescriptor(controllerType));
\r
207 return controllerDescriptor;
\r
210 internal static Func<ActionExecutedContext> InvokeActionMethodFilterAsynchronously(IActionFilter filter, ActionExecutingContext preContext, Func<Func<ActionExecutedContext>> nextInChain) {
\r
211 filter.OnActionExecuting(preContext);
\r
212 if (preContext.Result != null) {
\r
213 ActionExecutedContext shortCircuitedPostContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */) {
\r
214 Result = preContext.Result
\r
216 return () => shortCircuitedPostContext;
\r
219 // There is a nested try / catch block here that contains much the same logic as the outer block.
\r
220 // Since an exception can occur on either side of the asynchronous invocation, we need guards on
\r
221 // on both sides. In the code below, the second side is represented by the nested delegate. This
\r
222 // is really just a parallel of the synchronous ControllerActionInvoker.InvokeActionMethodFilter()
\r
226 Func<ActionExecutedContext> continuation = nextInChain();
\r
228 // add our own continuation, then return the new function
\r
230 ActionExecutedContext postContext;
\r
231 bool wasError = true;
\r
234 postContext = continuation();
\r
237 catch (ThreadAbortException) {
\r
238 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
\r
239 // the filters don't see this as an error.
\r
240 postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
\r
241 filter.OnActionExecuted(postContext);
\r
244 catch (Exception ex) {
\r
245 postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
\r
246 filter.OnActionExecuted(postContext);
\r
247 if (!postContext.ExceptionHandled) {
\r
252 filter.OnActionExecuted(postContext);
\r
255 return postContext;
\r
258 catch (ThreadAbortException) {
\r
259 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
\r
260 // the filters don't see this as an error.
\r
261 ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
\r
262 filter.OnActionExecuted(postContext);
\r
265 catch (Exception ex) {
\r
266 ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
\r
267 filter.OnActionExecuted(postContext);
\r
268 if (postContext.ExceptionHandled) {
\r
269 return () => postContext;
\r
277 private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
\r
278 return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
\r