Dopo aver trascinato per ore il codice sorgente ASP.NET MVC per la soluzione migliore che sono riuscito a ottenere (oltre a creare versioni sincrone di ogni azione del controller) è necessario richiamare manualmente il descrittore di azione per l'azione asincrona metodi entro Controller.HandleUnknownAction
.
Non sono particolarmente soddisfatto di questo codice e spero che possa essere migliorato, ma è lavoro.
L'idea è di richiedere espressamente un'azione non valida (preceduta da "_") che invocherà il metodo HandleUnknownAction
sul controller. Qui cerchiamo un'azione di sincronizzazione asincrona (rimuovendo prima il carattere di sottolineatura da actionName
) e invochiamo il metodo AsyncActionDescriptor.BeginExecute
. Chiamando immediatamente il metodo EndExecute
stiamo effettivamente eseguendo il descrittore di azione in modo sincrono.
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);
}
}
La vista
<h2>Index</h2>
@Html.Action("_widget", new { page = 5 }) <!-- note the underscore prefix -->
Sono quasi certo c'è un modo migliore sovrascrivendo Controller.BeginExecute
. Di seguito l'implementazione predefinita. L'idea sarebbe quella di eseguire immediatamente Controller.EndExecuteCore
anche se finora non ho avuto alcun successo.
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);
}
}
fonte
2012-11-03 14:56:23
La prima cosa che viene in mente è quello di fare un nuovo metodo di estensione 'Html.ActionAsync' che semplicemente invocare il gasdotto come si vuole, ma aggiungere il' .Result' chiamare alla fine del metodo di supporto corpo, trasformando così le chiamate asincrone in chiamate di sincronizzazione. Problema interessante, di sicuro. – Tejs
Se questo fosse, vorrei creare un nuovo controller che sovrascriva CreateActionInvoker o HandleUnknownAction e con lo sguardo riflesso su un controller di destinazione per eseguire un'invocazione sincrona. Dovresti solo implementare il controller una volta e riutilizzarlo per qualsiasi azione asincrona. –
@Tejs sì questa sarebbe la soluzione idea anche se questa area del framework non è molto async friendly. Il problema sembra essere da qualche parte nel profondo 'HttpServerUtilityBase.Execute'. @ThikingSites - perché non pubblicare una risposta con maggiori dettagli? questo suona come una buona soluzione. –