MVC3 integrated, with some changes to make it compile on Mono and with Razor2
[mono-project.git] / mcs / class / System.Web.Mvc3 / Mvc / Async / AsyncControllerActionInvoker.cs
blobfe96d3729242e5cf31017ee87510f3cf7ed5db75
1 namespace System.Web.Mvc.Async {
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
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) {
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);
34 else {
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);
45 return asyncResult;
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;
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;
60 continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);
63 return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);
66 EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult) {
67 try {
68 continuation();
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;
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;
81 InvokeActionResult(controllerContext, exceptionContext.Result);
84 return true;
87 return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
89 else {
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) {
99 return false;
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);
108 return asyncResult;
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);
116 else {
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);
130 return () =>
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;
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;
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);
168 return result;
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()
211 // method.
213 try {
214 Func<ActionExecutedContext> continuation = nextInChain();
216 // add our own continuation, then return the new function
217 return () => {
218 ActionExecutedContext postContext;
219 bool wasError = true;
221 try {
222 postContext = continuation();
223 wasError = false;
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;
232 catch (Exception ex) {
233 postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
234 filter.OnActionExecuted(postContext);
235 if (!postContext.ExceptionHandled) {
236 throw;
239 if (!wasError) {
240 filter.OnActionExecuted(postContext);
243 return 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);
251 throw;
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;
259 else {
260 throw;
265 private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
266 return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);