Merge pull request #249 from pcc/xgetinputfocus
[mono.git] / mcs / class / System.Web.Mvc3 / Mvc / Async / AsyncControllerActionInvoker.cs
1 namespace System.Web.Mvc.Async {
2     using System;
3     using System.Collections.Generic;
4     using System.Linq;
5     using System.Threading;
6
7     public class AsyncControllerActionInvoker : ControllerActionInvoker, IAsyncActionInvoker {
8
9         private static readonly object _invokeActionTag = new object();
10         private static readonly object _invokeActionMethodTag = new object();
11         private static readonly object _invokeActionMethodWithFiltersTag = new object();
12
13         public virtual IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state) {
14             if (controllerContext == null) {
15                 throw new ArgumentNullException("controllerContext");
16             }
17             if (String.IsNullOrEmpty(actionName)) {
18                 throw Error.ParameterCannotBeNullOrEmpty("actionName");
19             }
20
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;
26
27                 BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
28                     try {
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);
33                         }
34                         else {
35                             if (controllerContext.Controller.ValidateRequest) {
36                                 ValidateRequest(controllerContext);
37                             }
38
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);
44                             };
45                             return asyncResult;
46                         }
47                     }
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.
51                         throw;
52                     }
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) {
57                             throw;
58                         }
59
60                         continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);
61                     }
62
63                     return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);
64                 };
65
66                 EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
67                     try {
68                         continuation();
69                     }
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.
73                         throw;
74                     }
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) {
79                             throw;
80                         }
81                         InvokeActionResult(controllerContext, exceptionContext.Result);
82                     }
83
84                     return true;
85                 };
86
87                 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
88             }
89             else {
90                 // Notify the controller that no action was found.
91                 return BeginInvokeAction_ActionNotFound(callback, state);
92             }
93         }
94
95         private static IAsyncResult BeginInvokeAction_ActionNotFound(AsyncCallback callback, object state) {
96             BeginInvokeDelegate beginDelegate = BeginInvokeAction_MakeSynchronousAsyncResult;
97
98             EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
99                 return false;
100             };
101
102             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
103         }
104
105         private static IAsyncResult BeginInvokeAction_MakeSynchronousAsyncResult(AsyncCallback callback, object state) {
106             SimpleAsyncResult asyncResult = new SimpleAsyncResult(state);
107             asyncResult.MarkCompleted(true /* completedSynchronously */, callback);
108             return asyncResult;
109         }
110
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);
115             }
116             else {
117                 return BeginInvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters, callback, state);
118             }
119         }
120
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;
123
124             BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {
125                 ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
126                 IAsyncResult innerAsyncResult = null;
127
128                 Func<Func<ActionExecutedContext>> beginContinuation = () => {
129                     innerAsyncResult = BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters, asyncCallback, asyncState);
130                     return () =>
131                         new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
132                             Result = EndInvokeActionMethod(innerAsyncResult)
133                         };
134                 };
135
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();
140
141                 if (innerAsyncResult != null) {
142                     // we're just waiting for the inner result to complete
143                     return innerAsyncResult;
144                 }
145                 else {
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;
150                 }
151             };
152
153             EndInvokeDelegate<ActionExecutedContext> endDelegate = delegate(IAsyncResult asyncResult) {
154                 return endContinuation();
155             };
156
157             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodWithFiltersTag);
158         }
159
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);
163             };
164
165             EndInvokeDelegate<ActionResult> endDelegate = delegate(IAsyncResult asyncResult) {
166                 object returnValue = actionDescriptor.EndExecute(asyncResult);
167                 ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
168                 return result;
169             };
170
171             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodTag);
172         }
173
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);
178         }
179
180         public virtual bool EndInvokeAction(IAsyncResult asyncResult) {
181             return AsyncResultWrapper.End<bool>(asyncResult, _invokeActionTag);
182         }
183
184         protected internal virtual ActionResult EndInvokeActionMethod(IAsyncResult asyncResult) {
185             return AsyncResultWrapper.End<ActionResult>(asyncResult, _invokeActionMethodTag);
186         }
187
188         protected internal virtual ActionExecutedContext EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) {
189             return AsyncResultWrapper.End<ActionExecutedContext>(asyncResult, _invokeActionMethodWithFiltersTag);
190         }
191
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;
196         }
197
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
203                 };
204                 return () => shortCircuitedPostContext;
205             }
206
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()
211             // method.
212
213             try {
214                 Func<ActionExecutedContext> continuation = nextInChain();
215
216                 // add our own continuation, then return the new function
217                 return () => {
218                     ActionExecutedContext postContext;
219                     bool wasError = true;
220
221                     try {
222                         postContext = continuation();
223                         wasError = false;
224                     }
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);
230                         throw;
231                     }
232                     catch (Exception ex) {
233                         postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
234                         filter.OnActionExecuted(postContext);
235                         if (!postContext.ExceptionHandled) {
236                             throw;
237                         }
238                     }
239                     if (!wasError) {
240                         filter.OnActionExecuted(postContext);
241                     }
242
243                     return postContext;
244                 };
245             }
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);
251                 throw;
252             }
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;
258                 }
259                 else {
260                     throw;
261                 }
262             }
263         }
264
265         private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
266             return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
267         }
268
269     }
270 }