通過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);
}
}
,想到的第一件事是做一個新的擴展方法'Html.ActionAsync'像你想將簡單地調用了管道,但加上'.Result'調用輔助方法結束正文,從而將您的異步呼叫轉換爲同步呼叫。有趣的問題,當然。 – Tejs
如果是我,我會創建一個覆蓋CreateActionInvoker或HandleUnknownAction的新控制器,並使用反射來查看目標控制器以執行同步調用。你只需要實現一次控制器,並可以重用它來執行任何異步操作。 –
@Tejs是的這將是想法的解決方案,雖然這個框架的領域不是非常異步友好。問題似乎在'HttpServerUtilityBase.Execute'深處。 @ThikingSites - 爲什麼不發佈更多細節的答案?這聽起來像是一個很好的解決方法。 –