2010-08-28 9 views
15

Me gustaría atrapar cualquier excepción no controlada lanzada en un servicio web ASP.NET, pero nada de lo que he probado ha funcionado hasta ahora.¿Hay alguna manera de atrapar todos los errores en un servicio web AJAX?

En primer lugar, el evento HttpApplication.Error no se dispara en los servicios web, por lo que está fuera ..

El siguiente método fue implementar una extensión de jabón, y añadirlo a web.config con:

<soapExtensionTypes> 
    <add type="Foo" priority="1" group="0" /> 
</soapExtensionTypes> 

sin embargo, esto no funciona si se llama al método web a través de JSON (que mi sitio web no exclusivamente) ..

mi siguiente idea sería escribir mi propia HttpHandler para asmx, lo que haría es de esperar que proceda de System.Web.Script.Services.ScriptHandlerFac tory y hacer algo inteligente. No he intentado esto todavía.

¿Hay un enfoque que me falta? ¡Gracias!

Mike

ACTUALIZACIÓN:

voy a resumir la posiblemente soluciones aquí:

1) Actualizar a WCF lo que hace que todo esto mucho, mucho más fácil.

2) Como no puede subclasificar o anular la clase RestHandler, debería volver a implementar todo como su propio IHttpHandler o utilizar el reflejo para llamar manualmente a sus métodos. Debido a que la fuente de RestHandler es pública y tiene solo unas 500 líneas de longitud, hacer su propia versión puede no ser una gran cantidad de trabajo, pero usted sería responsable de mantenerla. Tampoco estoy al tanto de las restricciones de licencia involucradas con este código.

3) Puede ajustar sus métodos en try/catch blocks, o quizás usar expresiones LAMBDA para hacer que este código sea un poco más limpio. Todavía requeriría que modifiques cada método en tu servicio web.

+0

¿Hay alguna flexibilidad en ¿el requisito de que no puedes modificar los métodos web? –

+0

Bueno, sí, siempre y cuando no tenga que rodearlos a todos en bloques gigantes de prueba/captura ... Si la solución está limpia, entonces estoy abierto a ella. –

+0

Tengo una solución que requiere un bloque Try y una Catch de una sola línea. Es posible que puedas mejorarlo para eliminar el try/catch por completo, no he cavado tan profundo. ¿Quieres publicarlo? –

Respuesta

11

Como se describe en Capture all unhandled exceptions automatically with WebService realmente no hay una buena solución.

El motivo por el que no puede capturar HttpApplication.Error etc. tiene que ver con la forma en que RestHandler ha sido implementado por las buenas personas de Microsoft. En concreto, el RestHandler atrapa de forma explícita (asas) la excepción y escribe los detalles de la excepción a la Respuesta:

internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData) 
{ 
    try 
    { 
     NamedPermissionSet namedPermissionSet = HttpRuntime.NamedPermissionSet; 
     if (namedPermissionSet != null) 
     { 
      namedPermissionSet.PermitOnly(); 
     } 
     IDictionary<string, object> rawParams = GetRawParams(methodData, context); 
     InvokeMethod(context, methodData, rawParams); 
    } 
    catch (Exception exception) 
    { 
     WriteExceptionJsonString(context, exception); 
    } 
} 

Para empeorar las cosas, no hay punto de extensión limpia (que pude encontrar) donde se puede cambiar/extender el comportamiento. Si quiere ir por el camino de escribir su propio IHttpHandler, creo que tendrá que volver a implementar el RestHandler (o RestHandlerWithSession); independientemente Reflector será tu amigo.

Para aquellos que pueden optar por modificar sus webMethods

Si está utilizando Visual Studio 2008 o posterior, utilizando expresiones lambda hace que las cosas no es tan malo (aunque no es solución global/genérico) en términos o extracción de duplicados código.

[WebMethod] 
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)] 
public String GetServerTime() 
{ 
    return Execute(() => DateTime.Now.ToString()); 
} 

public T Execute<T>(Func<T> action) 
{ 
    if (action == null) 
    throw new ArgumentNullException("action"); 

    try 
    { 
    return action.Invoke(); 
    } 
    catch (Exception ex) 
    { 
    throw; // Do meaningful error handling/logging... 
    } 
} 

Dónde Ejecutar puede implementarse en una subclase de WebService o como un método de extensión.

ACTUALIZACIÓN: Reflexión mal

Como se ha mencionado en mi respuesta original, se puede abusar de reflexión para obtener lo que desea ... específicamente puede crear su propia HttpHandler que hace uso de la parte interna de la RestHandler a proporcionar un punto de interceptación para capturar detalles de excepción. He incluido un ejemplo de código "inseguro" a continuación para que comiences.

Personalmente, NO usaría este código; pero funciona.

namespace WebHackery 
{ 
    public class AjaxServiceHandler : IHttpHandler 
    { 
    private readonly Type _restHandlerType; 
    private readonly MethodInfo _createHandler; 
    private readonly MethodInfo _getRawParams; 
    private readonly MethodInfo _invokeMethod; 
    private readonly MethodInfo _writeExceptionJsonString; 
    private readonly FieldInfo _webServiceMethodData; 

    public AjaxServiceHandler() 
    { 
     _restHandlerType = typeof(ScriptMethodAttribute).Assembly.GetType("System.Web.Script.Services.RestHandler"); 

     _createHandler = _restHandlerType.GetMethod("CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext) }, null); 
     _getRawParams = _restHandlerType.GetMethod("GetRawParams", BindingFlags.NonPublic | BindingFlags.Static); 
     _invokeMethod = _restHandlerType.GetMethod("InvokeMethod", BindingFlags.NonPublic | BindingFlags.Static); 
     _writeExceptionJsonString = _restHandlerType.GetMethod("WriteExceptionJsonString", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext), typeof(Exception) }, null); 

     _webServiceMethodData = _restHandlerType.GetField("_webServiceMethodData", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField); 
    } 

    public bool IsReusable 
    { 
     get { return true; } 
    } 

    public void ProcessRequest(HttpContext context) 
    { 
     var restHandler = _createHandler.Invoke(null, new Object[] { context }); 
     var methodData = _webServiceMethodData.GetValue(restHandler); 
     var rawParams = _getRawParams.Invoke(null, new[] { methodData, context }); 

     try 
     { 
     _invokeMethod.Invoke(null, new[] { context, methodData, rawParams }); 
     } 
     catch (Exception ex) 
     { 
     while (ex is TargetInvocationException) 
      ex = ex.InnerException; 

     // Insert Custom Error Handling HERE... 

     _writeExceptionJsonString.Invoke(null, new Object[] { context, ex}); 
     } 
    } 
    } 
} 
+0

Sí, creo que podría ver el código y ver si puedo escribir un HttpHandler que "envuelva" el controlador existente de alguna manera. Aparte de eso, creo que no hay una solución limpia y debería agregar el manejo de errores a los métodos web más complicados. +1 para su respuesta detallada! –

+0

Debido a la forma en que se ha implementado el RestHandler, su única opción para la extensión probablemente será el uso indebido de Reflection (suponiendo plena confianza) ya que todo en RestHandler es interno o privado. –

+0

Eh go figure ... Me pregunto si puede escribir algún tipo de HttpFilter que detecte excepciones SOAP en el flujo de respuesta y las registre en una base de datos. Realmente, todo lo que quiero hacer aquí es el registro de errores. Ahora atrapo las excepciones en Javascript y luego vuelvo a llamar a otro webmethod para registrar el error. –

4

Esta es una buena pregunta. He tenido este problema en la aplicación web en la que trabajo. Me temo que mi solución no fue inteligente de ninguna manera. Simplemente envolví el código en cada método de servicio web en un try/catch y devolví un objeto que indicaba si el método se había ejecutado correctamente o no. Afortunadamente, mi aplicación no tiene una gran cantidad de llamadas al servicio web, por lo que puedo escapar de esto. Aprecio que no sea una buena solución si estás haciendo un montón de llamadas al servicio web.

+0

Hago el mismo tipo de cosas en la última aplicación web en la que estoy trabajando. Tengo muchas llamadas ajax a través de jquery y cuando el método del servidor arroja una excepción, lo veo, visten el JSON y tengo un método global del lado del cliente que notifica al usuario que hay un problema y cuáles son los detalles de ese problema. –

2

¿Ha echado un vistazo a WCF Web API? Actualmente se encuentra en versión beta, pero ya se está utilizando en varios sitios en vivo. Exponer json/xml rest api es muy fácil. En él puede crear su propia clase derivada HttpErrorHandler para manejar los errores. Es una solución muy limpia. Puede valer la pena echarle un vistazo más de cerca. La migración de cualquier servicio existente también debería ser directa. ¡Espero eso ayude!

+0

¡Esto es muy interesante! Todavía no creo que lo use en producción, pero definitivamente voy a estar pendiente de esto. +1 para el enlace! –

+1

Voy a decir que esto es drásticamente más fácil en el mundo de WCF; simplemente cree un IErrorHandler (http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.85).aspx) y el comportamiento del servicio asociado y listo. –

+0

Sí, parece que mi mejor apuesta a largo plazo es transferir mi código a WCF. Me pregunto qué tan difícil será ... –

2

Editar

¿trató de convertir el manejo del error de cierre en web.config y luego añadiendo un módulo de error?

<location path="Webservices" > 
    <system.web> 
    <customErrors mode="Off" /> 
    </system.web> 
</location> 

manejo de errores normal ya que creo que debe ser uniforme para el RestHandler

Se puede crear un HttpModule para atrapar todos los errores y volver resultado personalizado. Tenga en cuenta que este mensaje de error será la respuesta al cliente en lugar del resultado esperado del servicio web. También puede lanzar cualquier excepción personalizada en la aplicación y dejar que el módulo la maneje si el cliente debe responder o notificar al usuario al mismo.

resultado Ejemplo de ejecución correcta: resultado

{ 
    success: true, 
    data: [ .... ], 
    error: null 
} 

Ejemplo de ejecución fallida:

{ 
    success: false, 
    data: null, 
    error: { 
    message: 'error message', 
    stackTrace: 'url encoded stack trace' 
    } 
} 

Esto, unido al web.config:

<modules> 
    <add name="UnhandledException" type="Modules.Log.UnhandledException"/> 
</modules> 

Usted puede agregar fácilmente el código hacer que el módulo se autoregistre implementando la bonita indocumentación PreApplicationStartMethod funciona en el módulo y crea y usa su clase HttpApplication personalizada para usar en lugar de la estándar.Al hacerlo, puede agregar cualquier módulo a la aplicación simplemente agregándolo a la carpeta bin de la aplicación. Es la forma en que hago todas mis aplicaciones actualmente y debo decir que funciona perfectamente.

A continuación código se captura todos los errores de aplicación y devolver una respuesta HTML con el mensaje de error (que tendrá que reemplazar el material html con su propia implementación mensaje de error JSON):

using System.Web; 

namespace Modules.Log 
{ 
    using System; 
    using System.Text; 

    public class LogModule : IHttpModule 
    { 
     private bool initialized = false; 
     private object initLock = new Object(); 
     private static string applicationName = string.Format("Server: {0}; [LogModule_AppName] installation name is not set", System.Environment.MachineName); 

     public void Dispose() 
     { 
     } 

     public void Init(HttpApplication context) 
     { 
      // Do this one time for each AppDomain. 
      if (!initialized) 
      { 
       lock (initLock) 
       { 
        if (!initialized) 
        { 
         context.Error += new EventHandler(this.OnUnhandledApplicationException); 

         initialized = true; 
        } 
       } 
      } 
     } 

     private void OnUnhandledApplicationException(object sender, EventArgs e) 
     { 
      StringBuilder message = new StringBuilder("<html><head><style>" + 
       "body, table { font-size: 12px; font-family: Arial, sans-serif; }\r\n" + 
       "table tr td { padding: 4px; }\r\n" + 
       ".header { font-weight: 900; font-size: 14px; color: #fff; background-color: #2b4e74; }\r\n" + 
       ".header2 { font-weight: 900; background-color: #c0c0c0; }\r\n" + 
       "</style></head><body><table><tr><td class=\"header\">\r\n\r\nUnhandled Exception logged by LogModule.dll:\r\n\r\nappId="); 

      string appId = (string)AppDomain.CurrentDomain.GetData(".appId"); 
      if (appId != null) 
      { 
       message.Append(appId); 
      } 

      message.Append("</td></tr>"); 

      HttpServerUtility server = HttpContext.Current.Server; 
      Exception currentException = server.GetLastError(); 

      if (currentException != null) 
      { 
       message.AppendFormat(
         "<tr><td class=\"header2\">TYPE</td></tr><tr><td>{0}</td></tr><tr><td class=\"header2\">REQUEST</td></tr><tr><td>{3}</td></tr><tr><td class=\"header2\">MESSAGE</td></tr><tr><td>{1}</td></tr><tr><td class=\"header2\">STACK TRACE</td></tr><tr><td>{2}</td></tr>", 
         currentException.GetType().FullName, 
         currentException.Message, 
         currentException.StackTrace, 
         HttpContext.Current != null ? HttpContext.Current.Request.FilePath : "n/a"); 
       server.ClearError(); 
      } 

      message.Append("</table></body></html>"); 

      HttpContext.Current.Response.Write(message.ToString()); 
      server.ClearError(); 
     } 
    } 
} 
+0

Las llamadas de Ajax a los métodos web no arrojan excepciones no controladas (vea el primer fragmento de código en mi respuesta).Este enfoque funciona para llamadas regulares, pero el RestHandler lo elimina como una opción ya que la excepción es "manejada". –

+0

Ah, eché de menos ... actualicé la publicación con otra cosa que podría funcionar ... – Asken

+0

Creo que @CalgaryCoder lo resume bastante bien: se detectan excepciones y luego se llama a 'WriteExceptionJsonString' para escribir información de excepción serializada como JSON cuerda. Tendrás que interceptar esto para tener algo de suerte. Es lamentable que no hayan hecho que estas clases sean extensibles, sería un cambio de diseño bastante directo para agregar un mundo de más potencia. –

Cuestiones relacionadas