WebException ¿cómo obtener una respuesta completa con un cuerpo?

109

En WebException no puedo ver el cuerpo de GetResponse. Este es mi código en C #:

try {                
  return GetResponse(url + "." + ext.ToString(), method, headers, bodyParams);
} catch (WebException ex) {
    switch (ex.Status) {
      case WebExceptionStatus.ConnectFailure:
         throw new ConnectionException();                        
     case WebExceptionStatus.Timeout:
         throw new RequestTimeRanOutException();                     
     case WebExceptionStatus.NameResolutionFailure:
         throw new ConnectionException();                        
     case WebExceptionStatus.ProtocolError:
          if (ex.Message == "The remote server returned an error: (401) unauthorized.") {
              throw new CredentialsOrPortalException();
          }
          throw new ProtocolErrorExecption();                    
     default:
          throw;
    }

Veo el encabezado pero no veo el cuerpo. Esta es la salida de Wireshark para la solicitud:

POST /api/1.0/authentication.json HTTP/1.1    
Content-Type: application/x-www-form-urlencoded    
Accept: application/json    
Host: nbm21tm1.teamlab.com    
Content-Length: 49    
Connection: Keep-Alive    

userName=XXX&password=YYYHTTP/1.1 500 Server error    
Cache-Control: private, max-age=0    
Content-Length: 106    
Content-Type: application/json; charset=UTF-8    
Server: Microsoft-IIS/7.5    
X-AspNet-Version: 2.0.50727    
X-Powered-By: ASP.NET    
X-Powered-By: ARR/2.5

Date: Mon, 06 Aug 2012 12:49:41 GMT    
Connection: close    

{"count":0,"startIndex":0,"status":1,"statusCode":500,"error":{"message":"Invalid username or password."}}

¿Es posible de alguna manera ver el texto del mensaje en WebException? Gracias.

iwtu
fuente
¿Ha probado (HttpWebResponse) we.Response; ¿Dónde 'nosotros' está su WebException atrapada?
Justin Harvey
2
Para preservar el seguimiento de la pila en la excepción relanzada, no use throw ex;pero simplemente throw;(en el caso predeterminado). Además (si es necesario) pondría la WebException original en la InnerException de sus excepciones personalizadas (a través del constructor apropiado).
user1713059

Respuestas:

202
var resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();

dynamic obj = JsonConvert.DeserializeObject(resp);
var messageFromServer = obj.error.message;
LB
fuente
8
Para cualquiera que no esté familiarizado con JsonConvert, necesita obtener Newtonsoft.Json del administrador de paquetes nuget.
Kyle
Actualice la respuesta con la explicación de Kyle ya que Newtonsoft.Json es opcional.
Jeroen
3
Además, explique que este código debe ir dentro de la cláusula de respaldo Catch de un bloque de código Try-Catch en el que debe ir la solicitud. Sé que en este caso es obvio para el lector que presta atención y para @iwtu, pero las respuestas completamente completas pueden marcar la diferencia real para los principiantes que lean esta respuesta;)
Jeroen
2
StreamReader implementa IDisposable, entonces, ¿no es una buena práctica envolver esto en una declaración de uso? Un vistazo rápido al método Dispose de StreamReader sugiere que realiza una limpieza importante allí.
sammy34
@ sammy34 No se preocupe, dado que no hay código / datos no administrados aquí en este caso , el recolector de basura puede manejarlo fácilmente ... (Pero usar el uso es siempre un buen hábito)
LB
42
try {
 WebClient client = new WebClient();
 client.Encoding = Encoding.UTF8;
 string content = client.DownloadString("https://sandiegodata.atlassian.net/wiki/pages/doaddcomment.action?pageId=524365");
 Console.WriteLine(content);
 Console.ReadKey();
} catch (WebException ex) {
 var resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
 Console.WriteLine(resp);
 Console.ReadKey();
}
Kobe Bryant
fuente
5

Esto solo mejora las respuestas existentes. He escrito un método que se ocupa de los detalles de lanzar / volver a lanzar con un mensaje mejorado, que incluye el cuerpo de la respuesta:

Aquí está mi código (en Client.cs):

/// <summary>
///     Tries to rethrow the WebException with the data from the body included, if possible. 
///     Otherwise just rethrows the original message.
/// </summary>
/// <param name="wex">The web exception.</param>
/// <exception cref="WebException"></exception>
/// <remarks>
///     By default, on protocol errors, the body is not included in web exceptions. 
///     This solutions includes potentially relevant information for resolving the
///     issue.
/// </remarks>
private void ThrowWithBody(WebException wex) {
    if (wex.Status == WebExceptionStatus.ProtocolError) {
        string responseBody;
        try {
            //Get the message body for rethrow with body included
            responseBody = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();

        } catch (Exception) {
            //In case of failure to get the body just rethrow the original web exception.
            throw wex;
        }

        //include the body in the message
        throw new WebException(wex.Message + $" Response body: '{responseBody}'", wex, wex.Status, wex.Response);
    }

    //In case of non-protocol errors no body is available anyway, so just rethrow the original web exception.
    throw wex;
}

Lo usa en una cláusula de captura como mostró el OP:

//Execute Request, catch the exception to eventually get the body
try {
    //GetResponse....
    }
} catch (WebException wex) {
    if (wex.Status == WebExceptionStatus.ProtocolError) {
        ThrowWithBody(wex);
    }

    //otherwise rethrow anyway
    throw;
}
Marcel
fuente