2012-10-31 3 views
6

Da MVC 4 asynchrone untergeordnete Aktionen (über Html.Action) nicht unterstützt, suche ich nach einer Möglichkeit, die untergeordneten Synchronaktionsaktionen zu erzwingen. Eine einfache Abhilfe für diese Einschränkung ist synchrone Versionen aller Aktionen meiner Controller zur Verfügung zu stellen:Erzwingen der synchronen Ausführung der asynchronen Aktion in 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 
    } 
} 

Da wir jedoch Kinderaktionsanforderungen auf all unseren Controller-Aktionen erlauben, dies für jeden Controller tut, ist eine echte PITA.

Gibt es Erweiterbarkeitspunkte im Framework, in denen wir den Rückgabewert einer Aktion überprüfen können und, wenn sie Task<T> zurückgibt und wir eine untergeordnete Aktion verarbeiten, einen synchronen Aufruf erzwingen?

+1

Das erste, was in den Sinn kommt, ist eine neue Erweiterung Methode, um 'Html.ActionAsync', die einfach die Pipeline aufrufen, wie Sie wollen, aber fügen Sie die' bis zum Ende des Hilfsmethode .Result' nennen Körper, wodurch Ihre asynchronen Anrufe in Synchronisierungsanrufe umgewandelt werden. Interessantes Problem, sicher. – Tejs

+1

Wäre dies ich, würde ich einen neuen Controller erstellen, der CreateActionInvoker oder HandleUnknownAction überschreibt und mit Reflektion einen Zielcontroller für einen synchronen Aufruf betrachtet. Sie müssten den Controller nur einmal implementieren und ihn für jede asynchrone Aktion wiederverwenden. –

+0

@Tejs Ja das wäre die Ideallösung, obwohl dieser Bereich des Frameworks nicht sehr async freundlich ist. Das Problem scheint irgendwo tief in 'HttpServerUtilityBase.Execute' zu ​​liegen. @ThikingSites - warum nicht eine Antwort mit mehr Details posten? das klingt nach einer guten Problemumgehung. –

Antwort

3

Nachdem ich stundenlang durch den ASP.NET MVC-Quellcode geforscht habe, war die beste Lösung, die ich (abgesehen von der Erstellung von synchronen Versionen jeder Controller-Aktion), manuell den Aktionsdeskriptor für die asynchrone Aktion aufzurufen Methoden innerhalb Controller.HandleUnknownAction.

Ich bin nicht besonders glücklich mit diesem Code und ich hoffe, dass es verbessert werden kann, aber es funktioniert funktioniert.

Die Idee besteht darin, absichtlich eine ungültige Aktion (mit dem Präfix "_") anzufordern, die die HandleUnknownAction Methode auf dem Controller aufruft. Hier suchen wir nach einer übereinstimmenden asynchronen Aktion (indem Sie zuerst den Unterstrich von actionName entfernen) und rufen die Methode AsyncActionDescriptor.BeginExecute auf. Durch sofortiges Aufrufen der EndExecute-Methode führen wir effektiv den Aktionsdeskriptor synchron aus.

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); 
    } 
} 

Der Blick

<h2>Index</h2> 

@Html.Action("_widget", new { page = 5 }) <!-- note the underscore prefix --> 

Ich bin fast sicher, dass es durch zwingende Controller.BeginExecute einen besseren Weg. Die Standardimplementierung ist unten zu sehen. Die Idee wäre, sofort Controller.EndExecuteCore auszuführen, obwohl ich bis jetzt noch keinen Erfolg hatte.

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); 
    } 
} 
Verwandte Themen