2009-08-08 14 views
8

Tengo una aplicación ASP.NET MVC que actualmente usa la clase WebClient para realizar una simple llamada a un servicio web externo desde una acción del controlador.¿Cómo usar WebClient dentro de ASP.NET MVC de forma asincrónica?

Actualmente estoy usando el método DownloadString, que se ejecuta de forma síncrona. Me he encontrado con problemas en los que el servicio web externo no responde, lo que hace que toda mi aplicación ASP.NET carezca de hilos y no responda.

¿Cuál es la mejor manera de solucionar este problema? Hay un método DownloadStringAsync, pero no estoy seguro de cómo llamarlo desde el controlador. ¿Necesito usar la clase AsyncController? Si es así, ¿cómo interactúan el AsyncController y el método DownloadStringAsync?

Gracias por la ayuda.

Respuesta

7

Creo que el uso de AsyncControllers le ayudará aquí ya que descargan el procesamiento del subproceso de solicitud.

que haría uso de algo como esto (utilizando el patrón de eventos como se describe en this article):

public class MyAsyncController : AsyncController 
{ 
    // The async framework will call this first when it matches the route 
    public void MyAction() 
    { 
     // Set a default value for our result param 
     // (will be passed to the MyActionCompleted method below) 
     AsyncManager.Parameters["webClientResult"] = "error"; 
     // Indicate that we're performing an operation we want to offload 
     AsyncManager.OutstandingOperations.Increment(); 

     var client = new WebClient(); 
     client.DownloadStringCompleted += (s, e) => 
     { 
      if (!e.Cancelled && e.Error == null) 
      { 
       // We were successful, set the result 
       AsyncManager.Parameters["webClientResult"] = e.Result; 
      } 
      // Indicate that we've completed the offloaded operation 
      AsyncManager.OutstandingOperations.Decrement(); 
     }; 
     // Actually start the download 
     client.DownloadStringAsync(new Uri("http://www.apple.com")); 
    } 

    // This will be called when the outstanding operation(s) have completed 
    public ActionResult MyActionCompleted(string webClientResult) 
    { 
     ViewData["result"] = webClientResult; 
     return View(); 
    } 
} 

y asegúrese de que la configuración sea cual sea rutas que necesita, por ejemplo (en Global.asax.cs):

public class MvcApplication : System.Web.HttpApplication 
{ 
    public static void RegisterRoutes(RouteCollection routes) 
    { 
     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 

     routes.MapAsyncRoute(
      "Default", 
      "{controller}/{action}/{id}", 
      new { controller = "Home", action = "Index", id = "" } 
     ); 
    } 
} 
+0

¿hay alguna manera de agregar un tiempo de espera a la llamada asincrónica del cliente web? –

+1

Solo una nota, parece que esta respuesta se publicó en 09 en el MVC 1.0 días. Ahora con MVC 2/3 la respuesta es LIGERAMENTE diferente. El método MapAsyncRoute se ha ido y ya no es necesario. Además, el método MyAction necesita ser ahora renombrado como MyActionAsync.De lo contrario, todo funciona de la misma manera. – BFree

3

El método DownloadStringAsync usa un modelo de evento, elevando el DownloadStringCompleted cuando haya finalizado. También puede detener la solicitud si tarda demasiado llamando al WebClient.CancelAsync(). Esto permitirá que el hilo de su solicitud principal y el hilo de su WebClient se ejecuten en paralelo, y le permitirá decidir exactamente cuánto tiempo quiere que espere el hilo principal antes de volver.

En el siguiente ejemplo, iniciamos la descarga y establecemos el controlador de eventos que queremos invocar cuando termina. DownloadStringAsync se devuelve inmediatamente, por lo que podemos seguir procesando el resto de nuestra solicitud.

Para demostrar un control más granular de esta operación, cuando lleguemos al final de nuestra acción de controlador, podemos verificar si la descarga ya está completa; si no, déjalo 3 segundos más y luego aborta.

string downloadString = null; 

ActionResult MyAction() 
{ 
    //get the download location 
    WebClient client = StartDownload(uri); 
    //do other stuff 
    CheckAndFinalizeDownload(client); 
    client.Dispose(); 
} 

WebClient StartDownload(Uri uri) 
{ 
    WebClient client = new WebClient(); 
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Download_Completed); 
    client.DownloadStringAsync(uri); 
    return client; 
} 

void CheckAndFinalizeDownload(WebClient client) 
{ 
    if(this.downloadString == null) 
    { 
     Thread.Sleep(3000); 
    } 
    if(this.downloadString == null) 
    { 
     client.CancelAsync(); 
     this.downloadString = string.Empty; 
    } 
} 

void Download_Completed(object sender, DownloadStringCompletedEventArgs e) 
{ 
    if(!e.Cancelled && e.Error == null) 
    { 
     this.downloadString = (string)e.Result; 
    } 
} 
+0

Creo que este enfoque aún bloqueará el hilo de solicitud, sin embargo, aún está en el hilo de solicitud durante su llamada de Suspensión. Los controladores asíncronos ayudarán aquí porque liberan el subproceso de solicitud mientras espera el recurso externo. –

+0

@Michael de hecho. Este enfoque se ejecutará en paralelo con el subproceso de solicitud a menos que la solicitud del WebClient no finalice cuando el subproceso del controlador haya hecho todo lo demás que necesita hacer, momento en el que el controlador elige bloquear/suspender o continuar. No tuve la impresión de que la pregunta sea específicamente sobre cómo usar AsyncController, sino cómo hacer que el WebClient funcione de forma asíncrona. Esa puede ser una lectura incorrecta de la pregunta. –

+0

@Rex Sí, creo que a partir de la descripción de la aplicación web que está "sin hilos y sin respuesta" es probable que el OP se esté quedando sin hilos de solicitud. Limitar la respuesta externa a 3 segundos ayudará un poco (si sus solicitudes externas anteriormente tomaban más tiempo que eso), pero aún es un tiempo bastante largo para retener el hilo y, por lo tanto, no se escalaría. –

Cuestiones relacionadas