【问题标题】:Is there a way to trap all errors in a AJAX-web service?有没有办法捕获 AJAX-web 服务中的所有错误?
【发布时间】:2010-08-28 01:37:32
【问题描述】:

我想捕获在 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 服务中的每个方法。

【问题讨论】:

  • 不能修改web方法的要求有什么弹性吗?
  • 嗯,是的 - 只要我不必将所有这些都包围在巨大的 try/catch 块中。如果解决方案是干净的,那么我愿意接受它。
  • 我有一个需要 Try 块和单行 Catch 的解决方案。你也许可以改进它以完全取消 try/catch,我还没有深入挖掘。想要发布吗?
  • 只要try 块不是围绕每个网络方法中的所有代码(已经发布),那么一定要继续发布它。
  • 确实如此,所以我能提供的最好帮助就是向您指出这个问题:stackoverflow.com/questions/2180228/…

标签: c# asp.net .net ajax json


【解决方案1】:

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

您无法捕获 HttpApplication.Error 等的原因与 Microsoft 的好人如何实现 RestHandler 有关。具体来说,RestHandler 显式捕获(处理)异常并将异常详细信息写入 Response:

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 会是你的朋友。

对于那些可能选择修改其 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...
  }
}

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

【讨论】:

  • 是的,我想我可能会查看代码,看看我是否可以编写一个以某种方式“包装”现有处理程序的 HttpHandler。除此之外,我认为没有干净的解决方案,我应该将错误处理添加到更复杂的 Web 方法中。为您的详细答案 +1!
  • 由于 RestHandler 的实现方式,您唯一的扩展选择可能是反射滥用(假设完全信任),因为 RestHandler 中的所有内容都是内部或私有的。
  • Eh go figure.. 想知道您是否可以编写某种 HttpFilter 来检测响应流中的 SOAP 异常并将它们记录到数据库中。我真正想做的就是错误记录。现在我在 Javascript 中捕获异常,然后重新调用另一个 web 方法来记录错误。
  • 自定义 HttpModule 将能够拦截任何可以过滤为仅记录状态代码 = 500 的响应;缺点是在大多数情况下您无法获得很多细节。
  • 是的,如果我能获得异常信息,比如完整的堆栈跟踪,也许是小型转储,这才是真正值得的。我真的不想返回 HTTP 响应。
【解决方案2】:

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

【讨论】:

  • 我在我正在开发的最新网络应用程序中做同样的事情。我有很多通过 jquery 进行的 ajax 调用,当服务器端方法抛出异常时,我会捕获它,修饰 JSON 并有一个全局客户端方法来通知用户存在问题以及该问题的详细信息是什么。
【解决方案3】:

你看过WCF Web API吗? 这目前处于测试阶段,但已在多个现场站点中使用。 公开 json/xml rest api 非常容易。 在其中,您可以创建自己的 HttpErrorHandler 派生类来处理错误。这是一个非常干净的解决方案。可能值得你仔细看看。迁移任何现有服务也应该很简单。希望对您有所帮助!

【讨论】:

  • 这很有趣!我不认为我会在生产中使用它,但我肯定会密切关注这一点。 +1 链接!
  • 我认为这在 WCF 世界中要容易得多;只需创建一个 IErrorHandler (msdn.microsoft.com/en-us/library/…) 和相关的服务行为,你就完成了。
  • 是的,从长远来看,我最好的选择是将我的代码移植到 WCF。想知道那会有多难..
【解决方案4】:

编辑

您是否尝试在 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>

您可以通过在模块中实现相当未记录的 PreApplicationStartMethod 函数轻松添加代码以使模块自注册,并创建和使用您的自定义 HttpApplication 类来代替标准类。通过这样做,您可以将任何模块添加到应用程序中,只需将其添加到应用程序的 bin 文件夹即可。这是我目前所有应用程序的方式,我必须说它完美无缺。

以下代码将捕获所有应用程序错误并返回带有错误消息的 html 响应(您需要将 html 内容替换为您自己的 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();
        }
    }
}

【讨论】:

  • 对 Web 方法的 Ajax 调用不会引发未处理的异常(请参阅我的回答中的第一个代码 sn-p)。此方法适用于常规调用,但 RestHandler 将其作为选项移除,因为异常已“处理”。
  • 我认为@CalgaryCoder 总结得很好 - 异常被捕获,然后WriteExceptionJsonString 被调用以将序列化的异常信息写为 JSON 字符串。你必须拦截它才能有任何运气。可惜他们没有让这些类可扩展,这将是一些相当直接的设计更改来增加一个更强大的世界。
猜你喜欢
  • 2013-05-21
  • 1970-01-01
  • 2015-11-09
  • 1970-01-01
  • 1970-01-01
  • 2021-06-04
  • 1970-01-01
  • 2021-03-12
  • 1970-01-01
相关资源
最近更新 更多