通过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 - 为什么不发布更多细节的答案?这听起来像是一个很好的解决方法。 –