Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
599 views
in Technique[技术] by (71.8m points)

ajax - Catching errors from calling ASP.NET WebMethod with malformed Json

We have an older ASP.NET WebForms application which performs AJAX request by using jQuery $.ajax() calls on the client side, calling static methods in the page code-behind decorated with [WebMethod] attributes.

If an unhandled exception occurs within the WebMethod, it does not fire the Application_Error event and is thus not picked up by our error logger (ELMAH). This is well known and not a problem - we have all WebMethod code wrapped in try-catch blocks with exceptions being manually logged to ELMAH.

However, there is one case that has me stumped. If malformed Json is posted to the WebMethod URL, it throws an exception before entering our code, and I can't find any way to trap this.

e.g. this WebMethod signature

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

Normally called with a Json payload like:

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

I tried a test using Fiddler to edit the payload to the malformed Json:

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

And got the following ArgumentException error response from JavaScriptObjectDeserializer sent to the client (this is in a simple test app running locally with no custom errors):

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

It's still not firing the Application_Error event, and it never enters our code so we can't log the error ourselves.

I found a similar question which got a pointer to the blog post "How to create a global exception handler for a Web Service" but that appears to only be valid for SOAP webservices, not AJAX GETs/POSTs.

Is there some similar way to attach a custom handler in my situation?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

According to the reference source, the internal RestHandler.ExecuteWebServiceCall method catches all exceptions thrown by GetRawParams and simply writes them to the response stream, which is why Application_Error isn't invoked:

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

The only workaround I can think of is to create an output filter that intercepts and logs the output:

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

In Global.asax.cs (or in an HTTP module), install the filter in 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);
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...