有没有办法捕获AJAX-web服务中的所有错误?

时间:2010-08-28 01:37:32

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

我想捕获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 个答案:

答案 0 :(得分:11)

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

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

答案 1 :(得分:4)

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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

修改

您是否尝试在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响应(您需要使用自己的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();
        }
    }
}