Ben*_*ter 6 asp.net-mvc asp.net-mvc-4
由于MVC 4不支持异步子操作(via Html.Action),我正在寻找一种强制同步执行子操作的方法.解决此限制的一个简单方法是提供所有控制器操作的同步版本:
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
}
}
Run Code Online (Sandbox Code Playgroud)
但是,由于我们允许对所有控制器操作执行子操作请求,因此对每个控制器执行此操作都是真正的PITA.
在框架中是否有任何可扩展性点,我们可以检查操作的返回值,如果它返回a Task<T>并且我们正在处理子操作,则强制进行同步调用?
在仔细研究 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);
}
}
Run Code Online (Sandbox Code Playgroud)
风景
<h2>Index</h2>
@Html.Action("_widget", new { page = 5 }) <!-- note the underscore prefix -->
Run Code Online (Sandbox Code Playgroud)
我几乎可以肯定有更好的方法来覆盖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);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3988 次 |
| 最近记录: |