2010-08-28 38 views
15

我想捕捉在ASP.NET Web服务中引发的任何未处理的异常,但没有任何我尝试过的工作到目前为止。有没有办法在AJAX-web服务中捕获所有错误?

首先,在HttpApplication.Error不会触发事件上的Web服务,所以这是了..

下一个方法是实现SOAP扩展,并将其添加到与web.config中:

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

然而,如果你打电话超过JSON网络方法(我的网站不完全),这并不工作..

我的下一个想法是写我自己的HttpHandler为的.asmx,这将希望从System.Web.Script.Services.ScriptHandlerFac派生并且做一些聪明的事情。我还没有尝试过。

有没有我失踪的方法?谢谢!

迈克

UPDATE:

我可能会在这里总结了解决方案:

1)升级到WCF这使得这整个事情非常非常容易。

2)由于你不能子类或重写RestHandler类,你将不得不重新实现整个东西作为你自己的IHttpHandler或使用反射手动调用它的方法。由于RestHandler的源代码是公共的,只有大约500行的长度,所以制作自己的版本可能不是一项大量的工作,但是您将负责维护它。我也不知道这个代码涉及任何许可限制。 3)你可以将你的方法包装在try/catch块中,或者使用LAMBDA表达式来使这些代码更清晰一些。它仍然会要求您修改Web服务中的每个方法。

+0

是否有任何灵活性您无法修改Web方法的要求? – 2011-11-11 16:57:34

+0

嗯,是的 - 只要我不必在巨大的try/catch块中包围它们所有的东西。如果解决方案很干净,那么我对它开放。 – 2011-11-11 16:58:39

+0

我有一个解决方案,它需要一个Try块和一行Catch。你或许可以改进它,完全消除try/catch,我还没有深入挖掘。要发布吗? – 2011-11-11 17:01:14

回答

11

Capture all unhandled exceptions automatically with WebService所述,确实没有好的解决方案。

您无法捕获HttpApplication.Error等的原因与Microsoft的好人如何实施RestHandler有关。具体来说,RestHandler明确捕获(句柄)异常并写出异常细节的响应:

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

更糟糕的是,没有干净的扩展点(我能找到),您可以更改/延长行为。如果你想要写自己的IHttpHandler,我相信你几乎不得不重新实现RestHandler(或者RestHandlerWithSession)。无论Reflector会成为你的朋友。

对于那些可能选择。如果您正在使用Visual Studio 2008或更高版本来修改他们的WebMethods

,使用Lambda表达式使事情不是太糟糕了(虽然不是全球/通用的解决方案)的条款或删除复制码。

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

其中Execute可以在WebService的子类中实现或作为扩展方法实现。

UPDATE:反思危机

正如我在原始答复中提到,你可以滥用反射来得到你想要的......具体情况,你可以创建自己的HttpHandler,它利用了RestHandler的内部来提供捕获异常细节的拦截点。我在下面列出了一个“不安全”的代码示例,以帮助您入门。

就个人而言,我不会使用此代码;但它的工作原理。

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

是的,我想我可能会查看代码,看看我是否可以编写一个HttpHandler以某种方式“包装”现有的处理程序。除此之外,我认为没有干净的解决方案,我应该将错误处理添加到更复杂的Web方法中。 +1为您的详细答案! – 2011-11-16 16:42:06

+0

由于RestHandler的实现方式,您的唯一扩展选项可能是反射滥用(假设完全信任),因为RestHandler中的所有内容都是内部的或私有的。 – 2011-11-16 16:57:24

+0

呃去图..不知道你是否可以写一些HttpFilter来检测响应流中的SOAP异常并将它们记录到数据库中。真的,我想在这里做的是错误日志记录。现在我用Javascript捕捉异常,然后重新调用另一个webmethod来记录错误。 – 2011-11-16 17:03:00

4

这是一个很好的问题。我在我工作的网络应用程序中遇到过这个问题。恐怕我的解决方案在任何方面都不聪明。我简单地用try/catch封装了每个Web服务方法中的代码,并返回了一个表示方法是否已成功运行的对象。幸运的是,我的应用没有大量的Web服务调用,所以我可以逃脱这一点。如果您正在执行大量的Web服务调用,我很感激这不是一个好的解决方案。

+0

我在我正在开发的最新的Web应用程序中做同样的事情。我有很多通过jQuery调用Ajax调用,当服务器端方法抛出一个异常时,我会修正JSON并使用全局客户端方法通知用户存在问题以及该问题的详细信息。 – 2011-11-17 16:10:39

2

你已经采取了看看WCF Web API? 这是目前处于测试阶段,但它已在几个活网站中使用。暴露json/xml休息API是很容易的。 在这里你可以创建你自己的HttpErrorHandler派生类来处理错误。这是一个非常干净的解决方案。值得仔细看看你。迁移任何现有的服务也应该是直截了当的。希望能帮助到你!

+0

这很有趣!我认为我还没有将它用于制作,但我一定会留意这一点。链接+1! – 2011-11-16 16:37:32

+1

我会第二次在WCF世界中这是非常容易的;只需创建一个IErrorHandler(http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.85).aspx)和相关的服务行为即可完成。 – 2011-11-16 16:59:51

+0

对我来说,听起来像我最好的选择就是将我的代码移植到WCF。不知道这将是多么艰难.. – 2011-11-16 17:06:03

2

编辑

你尝试把web.config中的错误处理掉,然后加上一个误差模块?

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

正常的错误处理,因为我认为它应该是,即使是RestHandler

您可以创建一个HTTP模块来捕获所有的错误,并返回定制的结果。请注意,这将是对客户端的响应,而不是Web服务的预期结果。您也可以在应用程序中抛出任何自定义异常,并让该模块在客户端响应或通知用户时处理它。正确执行的

结果举例:未能执行

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

结果举例:

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

这个添加到Web.config:

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

您可以轻松地添加代码通过实现漂亮的无证书来使模块自行注册PreApplicationStartMethod函数在模块中创建并使用您的自定义HttpApplication类来使用,而不是使用标准的。通过这样做,您只需将其添加到应用程序的bin文件夹即可将任何模块添加到应用程序中。这是我现在完成所有应用程序的方式,我必须说它完美地工作。

下面的代码将捕捉所有应用程序错误,并返回该错误消息的HTML响应(你需要用自己的JSON的错误信息执行,以取代HTML的东西):

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

Ajax调用Web方法不会抛出未处理的异常(请参阅我的答案中的第一个代码片段)。这种方法适用于常规调用,但RestHandler将此作为选项移除,因为异常是“已处理”的。 – 2011-11-17 13:45:05

+0

啊,错过了......用另一个可能工作的东西更新了帖子... – Asken 2011-11-17 13:53:42

+0

我认为@CalgaryCoder总结得非常好 - 异常被捕获,然后调用WriteExceptionJsonString来将序列化的异常信息写为JSON串。你不得不拦截这个有运气。蹩脚的是,他们并没有将这些类扩展出来,而是为了增加一个更强大的世界而进行的一些相当直接的设计变更。 – 2011-11-17 16:37:00

相关问题