New test.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / Async / AsyncControllerActionInvoker.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\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
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc.Async {\r
14     using System;\r
15     using System.Collections.Generic;\r
16     using System.Linq;\r
17     using System.Threading;\r
18 \r
19     public class AsyncControllerActionInvoker : ControllerActionInvoker, IAsyncActionInvoker {\r
20 \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
24 \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
28             }\r
29             if (String.IsNullOrEmpty(actionName)) {\r
30                 throw Error.ParameterCannotBeNullOrEmpty("actionName");\r
31             }\r
32 \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
38 \r
39                 BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {\r
40                     try {\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
45                         }\r
46                         else {\r
47                             if (controllerContext.Controller.ValidateRequest) {\r
48                                 ValidateRequest(controllerContext);\r
49                             }\r
50 \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
56                             };\r
57                             return asyncResult;\r
58                         }\r
59                     }\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
63                         throw;\r
64                     }\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
69                             throw;\r
70                         }\r
71 \r
72                         continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);\r
73                     }\r
74 \r
75                     return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);\r
76                 };\r
77 \r
78                 EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {\r
79                     try {\r
80                         continuation();\r
81                     }\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
85                         throw;\r
86                     }\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
91                             throw;\r
92                         }\r
93                         InvokeActionResult(controllerContext, exceptionContext.Result);\r
94                     }\r
95 \r
96                     return true;\r
97                 };\r
98 \r
99                 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);\r
100             }\r
101             else {\r
102                 // Notify the controller that no action was found.\r
103                 return BeginInvokeAction_ActionNotFound(callback, state);\r
104             }\r
105         }\r
106 \r
107         private static IAsyncResult BeginInvokeAction_ActionNotFound(AsyncCallback callback, object state) {\r
108             BeginInvokeDelegate beginDelegate = BeginInvokeAction_MakeSynchronousAsyncResult;\r
109 \r
110             EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {\r
111                 return false;\r
112             };\r
113 \r
114             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);\r
115         }\r
116 \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
121         }\r
122 \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
127             }\r
128             else {\r
129                 return BeginInvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters, callback, state);\r
130             }\r
131         }\r
132 \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
135 \r
136             BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState) {\r
137                 ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);\r
138                 IAsyncResult innerAsyncResult = null;\r
139 \r
140                 Func<Func<ActionExecutedContext>> beginContinuation = () => {\r
141                     innerAsyncResult = BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters, asyncCallback, asyncState);\r
142                     return () =>\r
143                         new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {\r
144                             Result = EndInvokeActionMethod(innerAsyncResult)\r
145                         };\r
146                 };\r
147 \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
152 \r
153                 if (innerAsyncResult != null) {\r
154                     // we're just waiting for the inner result to complete\r
155                     return innerAsyncResult;\r
156                 }\r
157                 else {\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
162                 }\r
163             };\r
164 \r
165             EndInvokeDelegate<ActionExecutedContext> endDelegate = delegate(IAsyncResult asyncResult) {\r
166                 return endContinuation();\r
167             };\r
168 \r
169             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodWithFiltersTag);\r
170         }\r
171 \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
175             };\r
176 \r
177             EndInvokeDelegate<ActionResult> endDelegate = delegate(IAsyncResult asyncResult) {\r
178                 object returnValue = actionDescriptor.EndExecute(asyncResult);\r
179                 ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);\r
180                 return result;\r
181             };\r
182 \r
183             return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodTag);\r
184         }\r
185 \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
190         }\r
191 \r
192         public virtual bool EndInvokeAction(IAsyncResult asyncResult) {\r
193             return AsyncResultWrapper.End<bool>(asyncResult, _invokeActionTag);\r
194         }\r
195 \r
196         protected internal virtual ActionResult EndInvokeActionMethod(IAsyncResult asyncResult) {\r
197             return AsyncResultWrapper.End<ActionResult>(asyncResult, _invokeActionMethodTag);\r
198         }\r
199 \r
200         protected internal virtual ActionExecutedContext EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) {\r
201             return AsyncResultWrapper.End<ActionExecutedContext>(asyncResult, _invokeActionMethodWithFiltersTag);\r
202         }\r
203 \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
208         }\r
209 \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
215                 };\r
216                 return () => shortCircuitedPostContext;\r
217             }\r
218 \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
223             // method.\r
224 \r
225             try {\r
226                 Func<ActionExecutedContext> continuation = nextInChain();\r
227 \r
228                 // add our own continuation, then return the new function\r
229                 return () => {\r
230                     ActionExecutedContext postContext;\r
231                     bool wasError = true;\r
232 \r
233                     try {\r
234                         postContext = continuation();\r
235                         wasError = false;\r
236                     }\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
242                         throw;\r
243                     }\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
248                             throw;\r
249                         }\r
250                     }\r
251                     if (!wasError) {\r
252                         filter.OnActionExecuted(postContext);\r
253                     }\r
254 \r
255                     return postContext;\r
256                 };\r
257             }\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
263                 throw;\r
264             }\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
270                 }\r
271                 else {\r
272                     throw;\r
273                 }\r
274             }\r
275         }\r
276 \r
277         private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {\r
278             return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);\r
279         }\r
280 \r
281     }\r
282 }\r