小编典典

通过格式错误的Json调用ASP.NET WebMethod捕获错误

ajax

我们有一个较旧的ASP.NET WebForms应用程序,该应用程序通过$.ajax()在客户端使用jQuery
调用来执行AJAX请求,并在用[WebMethod]属性修饰的代码背后的页面中调用静态方法。

如果WebMethod中发生未处理的异常,它将不会触发该Application_Error事件,因此不会被我们的错误记录器(ELMAH)接收。这是众所周知的,不是问题-
我们将所有WebMethod代码包装在try-catch块中,但例外情况已手动记录到ELMAH中。

但是,有一种情况令我难过。如果格式错误的Json发布到WebMethod URL,则在输入我们的代码之前它将引发异常,并且我找不到任何方法来捕获此错误。

例如此WebMethod签名

[WebMethod]
public static string LeWebMethod(string stringParam, int intParam)

通常使用Json有效负载调用,例如:

{"stringParam":"oh hai","intParam":37}

我尝试使用Fiddler进行测试以将有效载荷编辑为格式错误的Json:

{"stringParam":"oh hai","intPara

并将以下ArgumentException错误响应从JavaScriptObjectDeserializer发送到客户端(这是在本地运行的没有自定义错误的简单测试应用中):

{"Message":"Unterminated string passed in. (32): {\"stringParam\":\"oh hai\",\"intPara","StackTrace":"   at
System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeString()\r\n   at
System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeMemberName()\r\n   at
System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeDictionary(Int32 depth)\r\n   at 
System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializeInternal(Int32 depth)\r\n   at 
System.Web.Script.Serialization.JavaScriptObjectDeserializer.BasicDeserialize(String input, Int32 depthLimit, JavaScriptSerializer serializer)\r\n   at 
System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)\r\n   at 
System.Web.Script.Serialization.JavaScriptSerializer.Deserialize[T](String input)\r\n   at 
System.Web.Script.Services.RestHandler.GetRawParamsFromPostRequest(HttpContext context, JavaScriptSerializer serializer)\r\n   at 
System.Web.Script.Services.RestHandler.GetRawParams(WebServiceMethodData methodData, HttpContext context)\r\n   at 
System.Web.Script.Services.RestHandler.ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)","ExceptionType":"System.ArgumentException"}

它仍然不会触发该Application_Error事件,并且它永远不会输入我们的代码,因此我们无法自己记录该错误。

我发现了一个类似的问题,该问题指向博客文章“
如何为Web服务创建全局异常处理程序
”,但是该问题似乎仅对SOAP
Web服务有效,而对AJAX
GET / POST无效。

在我的情况下,是否有一些类似的方法来附加自定义处理程序?


阅读 193

收藏
2020-07-26

共1个答案

小编典典

根据参考资料,内部RestHandler.ExecuteWebServiceCall方法捕获所有抛出的异常,GetRawParams并将它们简单地写入响应流,这就是为什么Application_Error不对其进行调用的原因:

internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData) {
    try {
        ...
        IDictionary<string, object> rawParams = GetRawParams(methodData, context);
        InvokeMethod(context, methodData, rawParams);
    }
    catch (Exception ex) {
        WriteExceptionJsonString(context, ex);
    }
}

我能想到的唯一解决方法是创建一个输出过滤器来拦截并记录输出:

public class PageMethodExceptionLogger : Stream
{
    private readonly HttpResponse _response;
    private readonly Stream _baseStream;
    private readonly MemoryStream _capturedStream = new MemoryStream();

    public PageMethodExceptionLogger(HttpResponse response)
    {
        _response = response;
        _baseStream = response.Filter;
    }

    public override void Close()
    {
        if (_response.StatusCode == 500 && _response.Headers["jsonerror"] == "true")
        {
            _capturedStream.Position = 0;
            string responseJson = new StreamReader(_capturedStream).ReadToEnd();
            // TODO: Do the actual logging.
        }

        _baseStream.Close();
        base.Close();
    }

    public override void Flush()
    {
        _baseStream.Flush();
    }

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

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

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

    public override void Write(byte[] buffer, int offset, int count)
    {
        _baseStream.Write(buffer, offset, count);
        _capturedStream.Write(buffer, offset, count);
    }

    public override bool CanRead { get { return _baseStream.CanRead; } }
    public override bool CanSeek { get { return _baseStream.CanSeek; } }
    public override bool CanWrite { get { return _baseStream.CanWrite; } }
    public override long Length { get { return _baseStream.Length; } }

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

在Global.asax.cs(或HTTP模块)中,将过滤器安装在Application_PostMapRequestHandler

protected void Application_PostMapRequestHandler(object sender, EventArgs e)
{
    HttpContext context = HttpContext.Current;
    if (context.Handler is Page && !string.IsNullOrEmpty(context.Request.PathInfo))
    {
        string contentType = context.Request.ContentType.Split(';')[0];
        if (contentType.Equals("application/json", StringComparison.OrdinalIgnoreCase))
        {
            context.Response.Filter = new PageMethodExceptionLogger(context.Response);
        }
    }
}
2020-07-26