Having trawled through the ASP.NET MVC source code for hours the best solution I've been able to come up with (aside from creating synchronous versions of every controller action) is to manually invoke the action descriptor for the async Action methods within Controller.HandleUnknownAction.
I'm not particularly happy with this code and I hope that it can be improved, but it does work.
The idea is to purposely request an invalid action (prefixed with "_") which will invoke the HandleUnknownAction method on the controller. Here we look for a matching async action (by first removing the underscore from the actionName) and invoke the AsyncActionDescriptor.BeginExecute method. By immediately calling the EndExecute method we are effectively executing the action descriptor synchronously.
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> Widget(int page = 10)
{
var content = await new HttpClient().GetStringAsync("http://www.foo.com")
.ConfigureAwait(false);
ViewBag.Page = page;
return View(model: content);
}
protected override void HandleUnknownAction(string actionName)
{
if (actionName.StartsWith("_"))
{
var asyncActionName = actionName.Substring(1, actionName.Length - 1);
RouteData.Values["action"] = asyncActionName;
var controllerDescriptor = new ReflectedAsyncControllerDescriptor(this.GetType());
var actionDescriptor = controllerDescriptor.FindAction(ControllerContext, asyncActionName)
as AsyncActionDescriptor;
if (actionDescriptor != null)
{
AsyncCallback endDelegate = delegate(IAsyncResult asyncResult)
{
};
IAsyncResult ar = actionDescriptor.BeginExecute(ControllerContext, RouteData.Values, endDelegate, null);
var actionResult = actionDescriptor.EndExecute(ar) as ActionResult;
if (actionResult != null)
{
actionResult.ExecuteResult(ControllerContext);
}
}
}
else
{
base.HandleUnknownAction(actionName);
}
}
The view
<h2>Index</h2>
@Html.Action("_widget", new { page = 5 }) <!-- note the underscore prefix -->
I'm almost certain there is a better way by overriding Controller.BeginExecute. The default implementation can be seen below. The idea would be to execute Controller.EndExecuteCore immediately although I've not had any success with this so far.
protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
{
if (DisableAsyncSupport)
{
// For backwards compat, we can disallow async support and just chain to the sync Execute() function.
Action action = () =>
{
Execute(requestContext);
};
return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeTag);
}
else
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
// Support Asynchronous behavior.
// Execute/ExecuteCore are no longer called.
VerifyExecuteCalledOnce();
Initialize(requestContext);
return AsyncResultWrapper.Begin(callback, state, BeginExecuteCore, EndExecuteCore, _executeTag);
}
}
Html.ActionAsyncthat will simply invoke the pipeline like you want, but add the.Resultcall to the end of the helper method body, thus turning your async calls into sync calls. Interesting problem, for sure.HttpServerUtilityBase.Execute. @ThikingSites - why not post an answer with more details? this sounds like a good workaround.