2012-10-31 85 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); 
    } 
}