2012-10-31 86 views
6

由于MVC 4不支持异步子操作(通过Html.Action)我正在寻找一种强制执行同步执行子操作的方法。一个简单的解决方法,以这种限制是提供我的所有控制器动作同步版本:强制同步执行ASP.NET MVC中的异步操作4

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 
    } 
} 

然而,由于我们让我们所有的控制器操作的子操作请求,这样做的每个控制器是一个真正的PITA。

是否有框架,我们可以检查一个动作的返回值,如果它返回一个Task<T>,我们正在处理孩子的行动,迫使同步调用任何扩展点?

+1

,想到的第一件事是做一个新的扩展方法'Html.ActionAsync'像你想将简单地调用了管道,但加上'.Result'调用辅助方法结束正文,从而将您的异步呼叫转换为同步呼叫。有趣的问题,当然。 – Tejs

+1

如果是我,我会创建一个覆盖CreateActionInvoker或HandleUnknownAction的新控制器,并使用反射来查看目标控制器以执行同步调用。你只需要实现一次控制器,并可以重用它来执行任何异步操作。 –

+0

@Tejs是的这将是想法的解决方案,虽然这个框架的领域不是非常异步友好。问题似乎在'HttpServerUtilityBase.Execute'深处。 @ThikingSites - 为什么不发布更多细节的答案?这听起来像是一个很好的解决方法。 –

回答

3

通过ASP.NET MVC源代码拖了几个小时,我已经能够提出的最佳解决方案(除了创建每个控制器操作的同步版本)之外,还是手动调用异步操作的操作描述符方法在Controller.HandleUnknownAction之内。

我不是特别满意这段代码,我希望它可以改进,但它确实工作。

这个想法是故意要求一个无效的动作(以“_”为前缀),它将调用控制器上的HandleUnknownAction方法。在这里,我们寻找匹配的异步操作(首先从actionName中删除下划线)并调用AsyncActionDescriptor.BeginExecute方法。通过立即调用EndExecute方法,我们正在同步执行操作描述符

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); 
    } 
} 

视图

<h2>Index</h2> 

@Html.Action("_widget", new { page = 5 }) <!-- note the underscore prefix --> 

我几乎可以肯定存在通过重写Controller.BeginExecute一个更好的办法。默认的实现可以在下面看到。这个想法将立即执行Controller.EndExecuteCore,尽管到目前为止我还没有取得任何成功。

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); 
    } 
}