Since MVC 4 does not support asynchronous child actions (via Html.Action
) I'm looking for a way to force the synchronous execution child actions. A simple workaround to this limitation is to provide synchronous versions of all my controllers actions:
public class FooAsyncController : Controller {
public async Task<ActionResult> IndexAsync() {
var model = await service.GetFoo().ConfigureAwait(false);
return View(model);
}
}
public class FooSyncController : FooAsyncController {
public ActionResult Index() {
return IndexAsync().Result; // blocking call
}
}
However, since we allow child action requests on all of our controller actions, doing this for every controller is a real PITA.
Are there any extensibility points in the framework where we could inspect the return value of an action and, if it returns a Task<T>
and we are handling a child action, force a synchronous call?
Implementing Asynchronous Methods in Asp.Net MVC. The first thing to do is to add the async keyword to Action Method. If we use the async Keyword in Method, the Method must also use await Keyword. The return type of an async method must be void, Task or Task<T> we have used Task<T> in Action Method.
ASP.NET 4.5 Web Pages in combination .NET 4.5 enables you to register asynchronous methods that return an object of type Task. The .NET Framework 4 introduced an asynchronous programming concept referred to as a Task and ASP.NET 4.5 supports Task.
Asynchronous action methods have a significant advantage over synchronous methods when an action must perform several independent operations. In the sample provided, the synchronous method PWG (for Products, Widgets and Gizmos) displays the results of three web service calls to get a list of products, widgets, and gizmos.
In .Net 4.5 Framework Microsoft has introduced 3 new keywords async, await, and Task. The Task is a class that comes under the namespace System.Threading.Tasks. Now we had lots of theory, let’s do some Practical. Here we will learn how to use async wait and Task in asp.net mvc project and how it is different from traditional methods (Synchronous).
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With