2010-08-28 4 views
15

Je voudrais piéger toute exception non gérée levée dans un service Web ASP.NET, mais rien de ce que j'ai essayé n'a fonctionné jusqu'à présent.Existe-t-il un moyen de piéger toutes les erreurs dans un service Web AJAX?

Tout d'abord, l'événement HttpApplication.Error ne se déclenche pas sur les services Web, de sorte que c'est à ..

La prochaine approche a consisté à mettre en œuvre une extension de savon, et l'ajouter à web.config avec:

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

Cependant, cela ne fonctionne pas si vous appelez la méthode web sur JSON (que mon site web fait exclusivement) ..

ma prochaine idée serait d'écrire mon propre HttpHandler pour .asmx, qui serait j'espère dériver de System.Web.Script.Services.ScriptHandlerFac tory et faire quelque chose d'intelligent. Je n'ai pas encore essayé.

Y a-t-il une approche qui me manque? Merci!

Mike

MISE À JOUR:

Je vais résumer les solutions éventuellement ici:

1) Mise à WCF qui fait beaucoup cette chose, beaucoup plus facile. 2) Comme vous ne pouvez pas sous-classer ou surcharger la classe RestHandler, vous devrez réimplémenter le tout comme votre IHttpHandler ou utiliser la réflexion pour appeler manuellement ses méthodes. Comme la source de RestHandler est publique et ne compte que 500 lignes environ, il se peut que votre propre version ne soit pas une énorme quantité de travail, mais vous seriez alors responsable de sa maintenance. Je ne suis pas non plus au courant des restrictions de licence liées à ce code.

3) Vous pouvez envelopper vos méthodes dans des blocs try/catch, ou peut-être utiliser des expressions LAMBDA pour rendre ce code un peu plus propre. Cela nécessiterait toujours que vous modifiez chaque méthode dans votre service Web.

+0

est-il une certaine flexibilité dans l'exigence que vous ne pouvez pas modifier les méthodes Web? –

+0

Eh bien, oui - tant que je n'ai pas à les entourer tous de blocs d'essai géants/catch .. Si la solution est propre, alors je suis ouvert à cela. –

+0

J'ai une solution qui nécessite un bloc Try et une seule ligne Catch. Vous pourriez être en mesure de l'améliorer pour faire disparaître complètement le try/catch, je n'ai pas creusé aussi profondément. Voulez-vous posté? –

Répondre

11

Comme décrit dans Capture all unhandled exceptions automatically with WebService il n'y a vraiment pas de bonne solution.

La raison pour laquelle vous ne pouvez pas capturer le HttpApplication.Error etc a à voir avec la façon dont le RestHandler a été implémenté par les bonnes personnes chez Microsoft. Plus précisément, le RestHandler attire explicitement (poignées) l'exception et écrit les détails de l'exception à la réponse:

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); 
    } 
} 

Pour aggraver les choses, il n'y a pas de point d'extension propre (que je pourrais trouver) où vous pouvez modifier/étendre le comportement. Si vous voulez suivre le chemin de l'écriture de votre propre IHttpHandler, je crois que vous aurez à ré-implémenter le RestHandler (ou RestHandlerWithSession); quel que soit Reflector sera votre ami.

Pour ceux qui peuvent choisir de modifier leurs WebMethods

Si vous utilisez Visual Studio 2008 ou plus tard, en utilisant des expressions Lambda rend les choses pas trop mal (mais pas solution globale/générique) en termes ou suppression d'dupliqués code.

[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... 
    } 
} 

Où Execute peut être implémenté dans une sous-classe de WebService ou en tant que méthode d'extension.

MISE À JOUR: Réflexion mal

Comme mentionné dans ma réponse d'origine, vous pouvez abuser de réflexion pour obtenir ce que vous voulez ... vous pouvez spécifiquement créer votre propre HttpHandler qui utilise des composants internes du RestHandler à fournir un point d'interception pour capturer les détails de l'exception. J'ai inclus un exemple de code «dangereux» ci-dessous pour vous aider à démarrer.

Personnellement, je n'utiliserais PAS ce code; mais ça marche.

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

Oui Je pense que je pourrais regarder dans le code et voir si je peux écrire un HttpHandler qui "enveloppe" le gestionnaire existant d'une certaine manière. Autre que cela, je pense qu'il n'y a pas de solution propre et je devrais juste ajouter la gestion des erreurs aux méthodes web plus compliquées. +1 pour votre réponse détaillée! –

+0

En raison de la façon dont le RestHandler a été implémenté, votre seule option pour l'extension sera probablement l'abus de Reflection (en supposant une confiance totale) car tout dans RestHandler est interne ou privé. –

+0

Eh aller figure .. Je me demande si vous pouvez écrire un genre de HttpFilter qui détecte les exceptions SOAP dans le flux de réponse et les enregistre dans une base de données. Vraiment tout ce que je veux faire ici est la journalisation des erreurs. À l'heure actuelle, je piège les exceptions en Javascript, puis je rappelle une autre méthode Web pour consigner l'erreur. –

4

C'est une bonne question. J'ai rencontré ce problème dans l'application Web sur laquelle je travaille. J'ai peur que ma solution ne soit pas intelligente d'aucune façon. J'ai simplement enveloppé dans le code dans chaque méthode de service Web dans un try/catch et renvoyé un objet qui indiquait si la méthode s'était exécutée avec succès ou non. Heureusement, mon application n'a pas un nombre énorme d'appels de service Web, donc je peux m'en sortir. J'apprécie que ce ne soit pas une bonne solution si vous faites beaucoup d'appels de service Web.

+0

Je fais le même genre de chose dans la dernière application web sur laquelle je travaille. J'ai beaucoup d'appels ajax via jquery et quand la méthode côté serveur lève une exception je l'attrape, habille le JSON et ai une méthode côté client global qui informe l'utilisateur qu'il y avait un problème et quels étaient les détails de ce problème. –

2

Avez-vous pris un coup d'oeil à WCF Web API? Ceci est actuellement en version bêta, mais il est déjà utilisé sur plusieurs sites en ligne. Exposer des api de repos json/xml est très facile. Dans ce cas, vous pouvez créer votre propre classe dérivée HttpErrorHandler pour gérer les erreurs. C'est une solution très propre. Cela pourrait valoir la peine de regarder de plus près pour vous. La migration de tout service existant devrait également être simple. J'espère que cela aide!

+0

C'est très intéressant! Je ne pense pas que je l'utiliserais encore dans la production, mais je vais certainement garder un oeil sur cela. +1 pour le lien! –

+1

Je vais soutenir que c'est beaucoup plus facile dans le monde de la WCF; créez simplement un IErrorHandler (http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.85).aspx) et le comportement de service associé et vous avez terminé. –

+0

Ouais semble que mon meilleur pari à long terme est de porter mon code à la WCF. Je me demande comment cela sera difficile .. –

2

Modifier

Avez-vous essayé de transformer l'erreur de manipulation hors de web.config, puis en ajoutant un module d'erreur?

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

gestion des erreurs normale car je pense qu'il devrait être même pour les RestHandler

Vous pouvez créer un HttpModule pour attraper toutes les erreurs et revenir résultat personnalisé. Notez que ce message d'erreur sera la réponse au client au lieu du résultat attendu du service Web. Vous pouvez également ajouter personnalisée exception dans l'application et laisser le module gérer si le client doit répondre ou informer l'utilisateur.

Exemple pour l'exécution correcte:

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

Exemple de résultat d'exécution a échoué:

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

Ajouter ceci au web.config:

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

Vous pouvez facilement ajouter du code pour que le module s'auto-enregistre en implémentant le joli document non documenté PreApplicationStartMethod fonctionne dans le module et crée et utilise votre classe HttpApplication personnalisée à utiliser à la place de la classe standard.En faisant cela, vous pouvez ajouter n'importe quel module à l'application en l'ajoutant simplement au dossier bin de l'application. C'est comme ça que je fais toutes mes applications actuellement et je dois dire que ça fonctionne parfaitement.

Ci-dessous le code attrapera toutes les erreurs d'application et renvoyer une réponse html avec le message d'erreur (vous devrez remplacer les trucs html avec votre propre implémentation de message d'erreur 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

Les appels Ajax aux méthodes web ne lancent pas d'exceptions non gérées (voir le premier extrait de code dans ma réponse).Cette approche fonctionne pour les appels réguliers, mais le RestHandler supprime cela comme une option que l'exception est "traitée". –

+0

Ah, manqué que ... mis à jour le poste avec une autre chose qui pourrait fonctionner ... – Asken

+0

Je pense que @CalgaryCoder résume assez bien - Les exceptions sont interceptées, puis 'WriteExceptionJsonString' est appelé pour écrire des informations d'exception sérialisé en tant que JSON chaîne. Vous devriez intercepter cela pour avoir de la chance. C'est boiteux qu'ils n'aient pas rendu ces classes extensibles, ce serait des changements de conception assez simples pour ajouter un monde de plus de puissance. –

Questions connexes