ASP.NET [ScriptService]服务中全局记录异常

14
我正在使用[System.Web.Script.Services.ScriptService]标签,以便使用可从客户端JavaScript调用的Web服务。我需要的是一种全局记录这些方法中任何未处理异常的方法。在客户端,我会得到错误回调并可以从那里继续,但我需要服务器端捕获以记录异常。
这个URL上的人:http://ayende.com/Blog/archive/2008/01/06/ASP.Net-Ajax-Error-Handling-and-WTF.aspx 建议说这是无法实现的。
这是准确的吗? 我真的必须去尝试/捕获整个系统中每个Web方法吗?
4个回答

15
您可以使用HTTP模块捕获Web服务方法抛出的异常消息、堆栈跟踪和异常类型。
首先让我们了解一些背景知识...
- 如果Web服务方法引发异常,则HTTP响应的状态代码为500。 - 如果关闭自定义错误,则Web服务将返回JSON格式的异常消息和堆栈跟踪给客户端。例如:
{"Message":"Exception message","StackTrace":" at WebApplication.HelloService.HelloWorld() in C:\Projects\Stackoverflow Examples\WebApplication\WebApplication\HelloService.asmx.cs:line 22","ExceptionType":"System.ApplicationException"} - 当打开自定义错误时,Web服务向客户端返回默认消息,并删除堆栈跟踪和异常类型:
{"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""} 因此,我们需要关闭Web服务的自定义错误,并插入一个HTTP模块,它可以:
1. 检查请求是否针对Web服务方法 2. 检查是否抛出异常 - 即返回状态代码为500 3. 如果条件1和条件2成立,则获取将发送给客户端的原始JSON,并将其替换为默认JSON
以下代码是执行此操作的HTTP模块示例:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

public class ErrorHandlerModule : IHttpModule {

  public void Init(HttpApplication context) {
    context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
    context.EndRequest += OnEndRequest;
  }

  static void OnPostRequestHandlerExecute(object sender, EventArgs e) {
    HttpApplication context = (HttpApplication) sender;
    // TODO: Update with the correct check for your application
    if (context.Request.Path.StartsWith("/HelloService.asmx") 
        && context.Response.StatusCode == 500) {
      context.Response.Filter = 
        new ErrorHandlerFilter(context.Response.Filter);
      context.EndRequest += OnEndRequest;
    }
  }

  static void OnEndRequest(object sender, EventArgs e) {
    HttpApplication context = (HttpApplication) sender;
    ErrorHandlerFilter errorHandlerFilter = 
      context.Response.Filter as ErrorHandlerFilter;
    if (errorHandlerFilter == null) {
      return;
    }

    string originalContent =
      Encoding.UTF8.GetString(
        errorHandlerFilter.OriginalBytesWritten.ToArray());

    // If customErrors are Off then originalContent will contain JSON with
    // the original exception message, stack trace and exception type.

    // TODO: log the exception
  }

  public void Dispose() { }
}

这个模块使用以下过滤器来覆盖发送给客户端的内容并存储原始字节(其中包括异常消息、堆栈跟踪和异常类型):

public class ErrorHandlerFilter : Stream {

  private readonly Stream _responseFilter;

  public List OriginalBytesWritten { get; private set; }

  private const string Content = 
    "{\"Message\":\"There was an error processing the request.\"" +
    ",\"StackTrace\":\"\",\"ExceptionType\":\"\"}";

  public ErrorHandlerFilter(Stream responseFilter) {
    _responseFilter = responseFilter;
    OriginalBytesWritten = new List();
  }

  public override void Flush() {
    byte[] bytes = Encoding.UTF8.GetBytes(Content);
    _responseFilter.Write(bytes, 0, bytes.Length);
    _responseFilter.Flush();
  }

  public override long Seek(long offset, SeekOrigin origin) {
    return _responseFilter.Seek(offset, origin);
  }

  public override void SetLength(long value) {
    _responseFilter.SetLength(value);
  }

  public override int Read(byte[] buffer, int offset, int count) {
    return _responseFilter.Read(buffer, offset, count);
  }

  public override void Write(byte[] buffer, int offset, int count) {
    for (int i = offset; i < offset + count; i++) {
      OriginalBytesWritten.Add(buffer[i]);
    }
  }

  public override bool CanRead {
    get { return _responseFilter.CanRead; }
  }

  public override bool CanSeek {
    get { return _responseFilter.CanSeek; }
  }

  public override bool CanWrite {
    get { return _responseFilter.CanWrite; }
  }

  public override long Length {
    get { return _responseFilter.Length; }
  }

  public override long Position {
    get { return _responseFilter.Position; }
    set { _responseFilter.Position = value; }
  }
}

使用这种方法需要关闭Web服务的自定义错误。您可能希望在应用程序的其余部分保持自定义错误打开,因此Web服务应该放置在子目录中。可以使用覆盖父设置的web.config仅在该目录中关闭自定义错误。


尽管我在当前项目中没有跟进这个问题,但这可能是可靠地捕获所有这些错误的唯一方法。 - Clyde
我尝试了这个方法,但是不知何故我的context.Response.Filter.length抛出了一个'System.NotSupportedException'类型的异常。它就是不起作用。我认为它非常接近,我的输出与你说的完全一样。而且我得到了500状态码。 - maxisam

3

您在后台运行存储过程。然后,对于一个单变量,它返回了多个值。由此产生了冲突,并且抛出了这个错误。


1

我知道这并不完全回答了问题,但我曾经自己去寻找过这个答案,最终却一无所获。最后只好在每个 Web 服务调用中加入 try/catch,并在 catch 中调用我们的错误日志记录器。虽然有些麻烦,但至少能够解决问题。


-1
在ASP.Net中,可以使用全局错误处理程序捕获所有运行时未处理的异常,尽管博客文章建议这样做可能不起作用,但您可以尝试以某种方式重新抛出错误来尝试这种方法?
另一个想法是查看开源elmah(错误日志记录模块和处理程序)适用于ASP.Net,这可能有所帮助,或者该社区中的某个人可能会有想法。

1
是的,我不确定你是否真正理解了这个问题。异常确实被 ASP.NET 脚本服务代码捕获了--在我有机会捕获它之前就已经被捕获了。因此全局错误处理程序无法起作用。该框架向客户端发送一个错误,以便我可以显示一条消息,但没有服务器钩子。 - Clyde
然而,elmah链接很有趣...我一定会去查看的。 - Clyde
我也推荐使用ELMAH。我自己就是为了这个目的而使用它。 - Dave Ward
ELMAH无法工作,因为它使用了[ScriptServer]属性抑制的全局错误处理程序。 - WiseGuyEh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接