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);
}
}
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
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. –
@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. –