代码之家  ›  专栏  ›  技术社区  ›  Mike Christensen

在Ajax Web服务中是否有一种方法可以捕获所有错误?

  •  15
  • Mike Christensen  · 技术社区  · 15 年前

    我想捕获在ASP.NET Web服务中抛出的任何未处理的异常,但到目前为止,我尝试过的任何方法都没有奏效。

    首先,httpapplication.error事件不会在Web服务上触发,所以这是不可能的。

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

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

    但是,如果您通过JSON调用Web方法(我的网站专门做这个),这就不起作用。

    我的下一个想法是为.asmx编写自己的httphandler,希望它能从system.web.script.services.scripthandlerFactory派生并做一些聪明的事情。我还没试过。

    有没有我错过的方法?谢谢!

    迈克

    更新:

    我将在这里总结可能的解决方案:

    1)升级到 WCF 这使得整个事情变得更加容易。

    2)由于不能对resthandler类进行子类或重写,因此必须将整个过程重新实现为自己的IHttphandler,或者使用反射手动调用其方法。由于resthandler的源代码是公共的,而且只有500行长,因此生成您自己的版本可能不是一项巨大的工作,但是您将负责维护它。我也不知道与此代码有关的任何许可限制。

    3)您可以将方法包装在try/catch块中,或者使用lambda表达式使代码更清晰。它仍然需要您修改Web服务中的每个方法。

    4 回复  |  直到 14 年前
        1
  •  11
  •   Community Mohan Dere    9 年前

    如上所述 Capture all unhandled exceptions automatically with WebService 真的没有好的解决办法。

    无法捕获httpapplication.error等的原因与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);
        }
    }
    

    更糟糕的是,没有一个清晰的扩展点(我可以找到),您可以在这里更改/扩展行为。如果你想写自己的ihtphandler,我相信你将不得不重新实现resthandler(或resthandlerWithSession);不管reflector是什么,它都是你的朋友。

    对于可能选择修改其WebMethods的用户

    如果您使用的是Visual Studio 2008或更高版本,则使用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的子类中实现,也可以作为扩展方法实现。

    更新:反射邪恶

    正如我最初的回答中提到的,你可以滥用反射来得到你想要的…具体地说,您可以创建自己的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});
          }
        }
      }
    }
    
        2
  •  4
  •   Phil Hale    15 年前

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

        3
  •  2
  •   santiagoIT    14 年前

    你看了吗 WCF Web API ? 这是目前的测试版,但它已经在几个现场使用。 公开JSON/XML REST API非常容易。 在其中,您可以创建自己的httpErrorHandler派生类来处理错误。这是一种非常干净的溶液。也许值得你仔细看看。迁移任何现有的服务也应该很简单。希望它有帮助!

        4
  •  2
  •   Asken    14 年前

    编辑

    是否尝试在web.config中关闭错误处理,然后添加错误模块?

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

    正常的错误处理,正如我认为的那样,它甚至应该用于resthandler

    您可以创建一个httpmodule来捕获所有错误并返回自定义结果。请注意,这将是一条错误消息,它将是对客户端的响应,而不是来自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>
    

    您可以通过实现非常没有文档的 预应用启动方法 在模块中运行,创建并使用自定义的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();
            }
        }
    }
    
    推荐文章