2011-01-23 17 views
12

Dado un controlador asíncrono:Detección de desconexión de un cliente asíncrono en ASP.NET MVC

public class MyController : AsyncController 
{ 
    [NoAsyncTimeout] 
    public void MyActionAsync() { ... } 

    public void MyActionCompleted() { ... } 
} 

Supongamos MyActionAsync arranca un proceso que toma varios minutos. Si el usuario ahora va a la acción MyAction, el navegador esperará con la conexión abierta. Si el usuario cierra su navegador, la conexión se cierra. ¿Es posible detectar cuándo ocurre eso en el servidor (preferiblemente dentro del controlador)? ¿Si es así, cómo? Intenté reemplazar a OnException, pero eso nunca se dispara en este escenario.

Nota: hago apreciar las respuestas útiles a continuación, pero el aspecto clave de esta pregunta es que estoy usando un AsyncController. Esto significa que las solicitudes HTTP siguen abiertas (son de larga vida como COMET o BOSH) lo que significa que es una conexión de socket en vivo. ¿Por qué no se puede notificar al servidor cuando finaliza la conexión en vivo (es decir, "restablecimiento de conexión por pares", el paquete TCP RST)?

Respuesta

23

Me doy cuenta de que esta pregunta es antigua, pero apareció con frecuencia en mi búsqueda de la misma respuesta. Los detalles a continuación sólo se aplican a .NET 4.5

HttpContext.Response.ClientDisconnectedToken es lo que desea. Eso le dará un CancellationToken que puede pasar a sus llamadas asincrónicas/esperadas.

public async Task<ActionResult> Index() 
{ 
    //The Connected Client 'manages' this token. 
    //HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects 
    try 
    { 
     using (var client = new System.Net.Http.HttpClient()) 
     { 
      var url = "http://google.com"; 
      var html = await client.GetAsync(url, HttpContext.Response.ClientDisconnectedToken); 
     } 
    } 
    catch (TaskCanceledException e) 
    { 
     //The Client has gone 
     //you can handle this and the request will keep on being processed, but no one is there to see the resonse 
    } 
    return View(); 
} 

puede probar el fragmento anterior, poniendo un punto de interrupción en el inicio de la función a continuación, cierre la ventana del navegador.


Y otro fragmento, no directamente relacionada con su pregunta, pero útil, de todos modos ...

También se puede poner un límite estricto sobre la cantidad de tiempo que una acción puede ejecutar por el uso de la AsyncTimeout atributo. Para usar este uso, agregue un parámetro adicional de tipo CancellationToken.Este token permitirá a ASP.Net agotar el tiempo de espera de la solicitud si la ejecución lleva demasiado tiempo.

[AsyncTimeout(500)] //500ms 
public async Task<ActionResult> Index(CancellationToken cancel) 
{ 
    //ASP.Net manages the cancel token. 
    //cancel.IsCancellationRequested will be set to true after 500ms 
    try 
    { 
     using (var client = new System.Net.Http.HttpClient()) 
     { 
      var url = "http://google.com"; 
      var html = await client.GetAsync(url, cancel); 
     } 
    } 
    catch (TaskCanceledException e) 
    { 
     //ASP.Net has killed the request 
     //Yellow Screen Of Death with System.TimeoutException 
     //the return View() below wont render 
    } 
    return View(); 
} 

Puede probar esto uno por poner un punto de interrupción en el inicio de la función (con lo que la solicitud de tomar más de 500 ms si se llega al punto de interrupción) y luego dejar que se agote.

+0

Para mí, obtuve el token de cancelación a través de System.Web.HttpContext.Current.Response.ClientDisconnectedToken – Tony

+1

Me pregunto cómo hacer que funcione con la aplicación autohospedada de OWIN. – greatvovan

0

Por razones obvias, no se puede notificar al servidor que el cliente ha cerrado su navegador. O que fue al baño :-) Lo que podrías hacer es que el cliente haga sondeos continuamente en el servidor con solicitudes AJAX a intervalos regulares (window.setInterval) y si el servidor detecta que ya no se sondea significa que el cliente ya no está allí .

+1

Agradezco la respuesta, pero se dan cuenta de que HTTP no permite que yo sepa cuando un usuario cierra el navegador * después de que se entregó la respuesta *. Sin embargo, eso no es lo que pedí. Parte de un 'AsyncController' es que la solicitud * no está terminada * - permanece abierta. En principio, parece posible saber que no hay ningún cliente al que enviar la respuesta. –

+1

@Kirk, en términos del protocolo HTTP un 'AsyncController' no es diferente de un' controlador' normal. El protocolo siempre permanece sin estado. –

+1

es sin estado, y es solicitud/respuesta. Pero esa transacción todavía * puede * tomar mucho tiempo. Esto es exactamente cómo COMET y Bosh funciona (o francamente, incluso Gmail cuando haya chat habilitado), y yo no entiendo por qué la terminación de esa conexión en directo (* * es una conexión en directo, por eso se puede enviar una respuesta de vuelta al final) no es reconocible. –

5

Es como dice @Darin. HTTP es un protocolo sin estado que significa que no hay forma (mediante el uso de HTTP) para detectar si el cliente todavía está allí o no. HTTP 1.0 cierra el socket después de cada solicitud, mientras que HTTP/1.1 puede mantenerlo abierto durante un tiempo (se puede establecer un tiempo de espera Keep-alive como un encabezado). Que un cliente HTTP/1.1 cierre el socket (o el servidor para el caso) no significa que el cliente se haya ido, solo que el socket no se ha usado por un tiempo.

Hay algo llamado COMET servers que se utiliza para permitir que el cliente/servidor continúe "chateando" a través de HTTP. Busca el cometa aquí en SO o en la red, hay varias implementaciones disponibles.

+3

@jgauffin, gracias por la respuesta. Es interesante que menciones COMET porque eso es esencialmente lo que estoy tratando de usar. Al usar un 'AsyncController', usted está participando en conexiones HTTP de larga duración, * como COMET *. Por lo tanto, si el usuario cierra el navegador ** mientras la conexión sigue abierta **, ¿por qué el servidor no puede saberlo y no informarme? –

+0

@Kirk, cuando se usa un AsyncController, el cliente no se involucra en ninguna conexión duradera. Es lo mismo que un controlador normal en términos del protocolo HTTP. La única diferencia es que ASP.NET comenzará la solicitud en un hilo de trabajo y podría terminar en otro y en el medio podría usar IOCP si su API lo admite. Pero mirando el nivel de protocolo, exactamente lo mismo se intercambia si no está usando un controlador asíncrono. –

+2

@Darin, creo que estás equivocado. En Firebug o incluso solo en el navegador directamente, puede ver que la conexión está abierta. Puede presionar la tecla de escape y finalizará la conexión. Es exactamente como una solicitud que tarda mucho tiempo en procesarse. (O para decirlo de otra manera, usted es correcta * * - que es exactamente igual que un controlador normal de * que está teniendo un tiempo muy largo * para dar servicio a una acción -. Que es de hecho una conexión HTTP durante más tiempo) –

7

¿No funciona bastante bien Response.IsClientConnected para esto? Acabo de probar en mi caso cancelar cargas de archivos grandes. Con esto quiero decir que si un cliente aborta sus solicitudes (en mi caso, Ajax), puedo ver eso en mi Acción. No digo que sea 100% exacto, pero mis pruebas a pequeña escala muestran que el navegador del cliente aborta la solicitud y que la Acción recibe la respuesta correcta de IsClientConnected.

+1

Sí, esta es la mejor respuesta aquí. IsClientConnected será falso en el medio de una solicitud asincrónica si el cliente cierra su navegador. – eoleary

Cuestiones relacionadas