6

MVC 4 は (経由で) 非同期の子アクションをサポートしていないため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
    }   
}

ただし、すべてのコントローラー アクションで子アクション リクエストを許可しているため、すべてのコントローラーに対してこれを行うことは、実際の PITA です。

フレームワークに、アクションの戻り値を調べて、それが を返し、Task<T>子アクションを処理している場合、同期呼び出しを強制できる拡張ポイントはありますか?

4

1 に答える 1

3

ASP.NET MVC ソース コードを何時間も調べた結果、(すべてのコントローラー アクションの同期バージョンを作成する以外に) 思いついた最善の解決策は、.NET 内の非同期アクション メソッドのアクション記述子を手動で呼び出すこと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);
    }
}
于 2012-11-03T14:56:23.700 に答える