2012-10-31 3 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 
    } 
} 

이 제한에 대한 간단한 해결 방법은 내 모든 컨트롤러 행동의 동기 버전을 제공하는 것입니다.

조치의 리턴 값을 조사 할 수있는 프레임 워크에 확장 점이 있습니까? Task<T>을 리턴하고 하위 조치를 처리하는 경우 동기 호출을 강제 수행합니까?

+1

마음에 오는 첫번째 일은'헬퍼 방법의 끝에 전화 .Result' 당신이 원하는처럼 단순히 파이프 라인을 호출하는 새로운 확장 메서드 'Html.ActionAsync'를 만들지 만 추가하는 것입니다 따라서 비동기 호출을 동기화 호출로 변환 할 수 있습니다. 흥미로운 문제 야. – 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); 
    } 
} 
관련 문제