从调用ASP中捕获错误。NET WebMethod,格式错误的Json

时间:2022-08-22 11:11:28

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.

我们有一个更老的ASP。NET WebForms应用程序,通过在客户端使用jQuery $. AJAX()调用来执行AJAX请求,调用页面中的静态方法,这些方法在代码后面加上[WebMethod]属性。

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.

如果WebMethod中出现未处理的异常,则不会触发Application_Error事件,因此不会被我们的错误日志记录器(ELMAH)接收。这是众所周知的,并不是问题——我们将所有WebMethod代码封装在try-catch块中,并将异常手工登录到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.

然而,有一件事让我难住了。如果错误的Json被发布到WebMethod URL,它会在输入我们的代码之前抛出一个异常,我找不到任何方法来捕获它。

e.g. this WebMethod signature

如这WebMethod签名

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

Normally called with a Json payload like:

通常使用Json有效负载调用:

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

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

我尝试使用Fiddler进行测试,将有效负载编辑为畸形的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):

并从javascript tobjectdeserializer发送到客户端的ArgumentException错误响应(这是在一个本地运行的简单测试应用程序中,没有自定义错误):

{"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"}

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

它仍然没有触发Application_Error事件,而且它从未进入我们的代码,因此我们无法自己记录错误。

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.

我发现了一个类似的问题,它有一个指向博客文章“如何为Web服务创建全局异常处理程序”的指针,但这似乎只对SOAP Web服务有效,而不是AJAX get /POSTs有效。

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

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

5 个解决方案

#1


14  

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:

根据参考资料来源,内部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);
    }
}

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:

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

#2


1  

This article suggests that there are two ways to extend WebMethods of which the SoapExtension is the easier. This other one shows an example how to write a SoapExtension. It looks like the place where you can do message validation.

本文建议有两种方法可以扩展WebMethods,其中SoapExtension更简单。另一个示例展示了如何编写SoapExtension。它看起来像是您可以进行消息验证的地方。

#3


1  

When you say that you have static methods on the page code-behind marked with WebMethod and you say that you use $.ajax, that sounds just wrong. But I'll give the benefit of the doubt, as I don't know the particularities of you system.

当您说在页面代码后面有一个用WebMethod标记的静态方法时,您说您使用$。ajax,听起来是错误的。但是,我还是不相信,因为我不知道你们系统的特殊性。

Anyway, please test this:

无论如何,请测试:

  • You should have a ScriptManager on your page looking like this: (**1)

    页面上应该有这样的ScriptManager: (**1)

  • Then in that place where you have your $.ajax call, call you Page Method like this: (**2)

    然后在你有钱的地方。ajax调用,像这样调用Page方法:(**2)

(**1)

(* * 1)

<asp:ScriptManager ID="smPageManager"
        runat="server"
        EnablePageMethods="true" 
        ScriptMode="Release" 
        LoadScriptsBeforeUI="true"> 
</asp:ScriptManager>

(**2)

(* * 2)

PageMethods.LeWebMethod("hero", 1024, function(response){
    alert(response);
}, function(error){
    alert(error);
});

Know using ASP.NET Ajax Library the proper way, give it a test, and see if the error reports back to you properly.

知道使用ASP。NET Ajax库的正确方法是,对它进行测试,看看是否正确地报告了错误。

P.S: Sorry for the bookmark style notation, but SO, seems be experiencing some malfunction right now.

P。店员:对不起书签样式表示法,但是,看来现在出现了一些故障。

UPDATE

更新

Reading this post, seems to explain the problem you are facing:

阅读这篇文章,似乎可以解释你所面临的问题:

(...) If the request is for a class that implements System.Web.UI.Page and it is a rest method call, the WebServiceData class (that was explained in a previous post) is used to call the requested method from the Page. After the method has been called, the CompleteRequest method is called, bypassing all pipeline events and executing the EndRequest method. This allows MS AJAX to be able to call a method on a page instead of having to create a web service to call a method. (...)

(…)如果请求是用于实现System.Web.UI的类。Page是一个rest方法调用,WebServiceData类(在前面的文章中解释过)用于从页面调用所请求的方法。调用该方法之后,调用CompleteRequest方法,绕过所有管道事件并执行EndRequest方法。这使MS AJAX能够在页面上调用方法,而不必创建web服务来调用方法。(…)

Try to use the ASP.NET JavaScript Proxies, to check if you can capture the error using Microsoft Generated Code.

尝试使用ASP。NET JavaScript代理,检查是否可以使用Microsoft生成的代码捕获错误。

#4


0  

These links might help you to handle the error on the client side,

这些链接可以帮助您处理客户端的错误,

*

*

unseenrevolution

unseenrevolution

asp.net

asp.net

encosia

encosia

then you could trigger a control event from client side to pass the error through the server and do the logging.

然后,您可以从客户端触发一个控制事件,将错误传递到服务器并进行日志记录。

#5


0  

Here is a solution that replaces the internal RestHandler implementation with my own version. You can log the exception in the WriteExceptionJsonString method. This uses an answer provided on Dynamically replace the contents of a C# method? to swap out the method. I've confirmed it works for me if I add a call to ReplaceRestHandler in my Global.asax Application_Start method. Haven't run this very long or in production so use at your own risk.

这里有一个解决方案,用我自己的版本替换内部的RestHandler实现。您可以在WriteExceptionJsonString方法中记录异常。这使用了一个关于动态替换c#方法内容的答案?换掉方法。我已经确认,如果我在全局中添加对ReplaceRestHandler的调用,它对我有效。asax Application_Start方法。没有运行这么长时间或在生产,所以使用在你自己的风险。

using System;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Web;
using Newtonsoft.Json;

namespace Royal.Common.WebStuff
{
    public static class RestHandlerUtils
    {
        internal static void WriteExceptionJsonString(HttpContext context, Exception ex, int statusCode)
        {
            string charset = context.Response.Charset;
            context.Response.ClearHeaders();
            context.Response.ClearContent();
            context.Response.Clear();
            context.Response.StatusCode = statusCode;
            context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(statusCode);
            context.Response.ContentType = "application/json";
            context.Response.AddHeader("jsonerror", "true");
            context.Response.Charset = charset;
            context.Response.TrySkipIisCustomErrors = true;
            using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))
            {
                if (ex is TargetInvocationException)
                    ex = ex.InnerException;
                var error = new OrderedDictionary();
                error["Message"] = ex.Message;
                error["StackTrace"] = ex.StackTrace;
                error["ExceptionType"] = ex.GetType().FullName;
                streamWriter.Write(JsonConvert.SerializeObject(error));
                streamWriter.Flush();
            }
        }

        public static void ReplaceRestHandler()
        {
            //https://*.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method
            var methodToInject = typeof(RestHandlerUtils).GetMethod("WriteExceptionJsonString",
                BindingFlags.NonPublic | BindingFlags.Static);
            var asm = typeof(System.Web.Script.Services.ScriptMethodAttribute).Assembly;
            var rhtype = asm.GetType("System.Web.Script.Services.RestHandler");
            var methodToReplace = rhtype
                .GetMethod("WriteExceptionJsonString", BindingFlags.NonPublic | BindingFlags.Static, null,
                    new Type[] {typeof(HttpContext), typeof(Exception), typeof(int)}, null);

            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*) methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*) methodToReplace.MethodHandle.Value.ToPointer() + 2;
                    *tar = *inj;
                }
                else
                {
                    long* inj = (long*) methodToInject.MethodHandle.Value.ToPointer() + 1;
                    long* tar = (long*) methodToReplace.MethodHandle.Value.ToPointer() + 1;
                    *tar = *inj;
                }
            }
        }
    }
}

#1


14  

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:

根据参考资料来源,内部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);
    }
}

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:

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

#2


1  

This article suggests that there are two ways to extend WebMethods of which the SoapExtension is the easier. This other one shows an example how to write a SoapExtension. It looks like the place where you can do message validation.

本文建议有两种方法可以扩展WebMethods,其中SoapExtension更简单。另一个示例展示了如何编写SoapExtension。它看起来像是您可以进行消息验证的地方。

#3


1  

When you say that you have static methods on the page code-behind marked with WebMethod and you say that you use $.ajax, that sounds just wrong. But I'll give the benefit of the doubt, as I don't know the particularities of you system.

当您说在页面代码后面有一个用WebMethod标记的静态方法时,您说您使用$。ajax,听起来是错误的。但是,我还是不相信,因为我不知道你们系统的特殊性。

Anyway, please test this:

无论如何,请测试:

  • You should have a ScriptManager on your page looking like this: (**1)

    页面上应该有这样的ScriptManager: (**1)

  • Then in that place where you have your $.ajax call, call you Page Method like this: (**2)

    然后在你有钱的地方。ajax调用,像这样调用Page方法:(**2)

(**1)

(* * 1)

<asp:ScriptManager ID="smPageManager"
        runat="server"
        EnablePageMethods="true" 
        ScriptMode="Release" 
        LoadScriptsBeforeUI="true"> 
</asp:ScriptManager>

(**2)

(* * 2)

PageMethods.LeWebMethod("hero", 1024, function(response){
    alert(response);
}, function(error){
    alert(error);
});

Know using ASP.NET Ajax Library the proper way, give it a test, and see if the error reports back to you properly.

知道使用ASP。NET Ajax库的正确方法是,对它进行测试,看看是否正确地报告了错误。

P.S: Sorry for the bookmark style notation, but SO, seems be experiencing some malfunction right now.

P。店员:对不起书签样式表示法,但是,看来现在出现了一些故障。

UPDATE

更新

Reading this post, seems to explain the problem you are facing:

阅读这篇文章,似乎可以解释你所面临的问题:

(...) If the request is for a class that implements System.Web.UI.Page and it is a rest method call, the WebServiceData class (that was explained in a previous post) is used to call the requested method from the Page. After the method has been called, the CompleteRequest method is called, bypassing all pipeline events and executing the EndRequest method. This allows MS AJAX to be able to call a method on a page instead of having to create a web service to call a method. (...)

(…)如果请求是用于实现System.Web.UI的类。Page是一个rest方法调用,WebServiceData类(在前面的文章中解释过)用于从页面调用所请求的方法。调用该方法之后,调用CompleteRequest方法,绕过所有管道事件并执行EndRequest方法。这使MS AJAX能够在页面上调用方法,而不必创建web服务来调用方法。(…)

Try to use the ASP.NET JavaScript Proxies, to check if you can capture the error using Microsoft Generated Code.

尝试使用ASP。NET JavaScript代理,检查是否可以使用Microsoft生成的代码捕获错误。

#4


0  

These links might help you to handle the error on the client side,

这些链接可以帮助您处理客户端的错误,

*

*

unseenrevolution

unseenrevolution

asp.net

asp.net

encosia

encosia

then you could trigger a control event from client side to pass the error through the server and do the logging.

然后,您可以从客户端触发一个控制事件,将错误传递到服务器并进行日志记录。

#5


0  

Here is a solution that replaces the internal RestHandler implementation with my own version. You can log the exception in the WriteExceptionJsonString method. This uses an answer provided on Dynamically replace the contents of a C# method? to swap out the method. I've confirmed it works for me if I add a call to ReplaceRestHandler in my Global.asax Application_Start method. Haven't run this very long or in production so use at your own risk.

这里有一个解决方案,用我自己的版本替换内部的RestHandler实现。您可以在WriteExceptionJsonString方法中记录异常。这使用了一个关于动态替换c#方法内容的答案?换掉方法。我已经确认,如果我在全局中添加对ReplaceRestHandler的调用,它对我有效。asax Application_Start方法。没有运行这么长时间或在生产,所以使用在你自己的风险。

using System;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Web;
using Newtonsoft.Json;

namespace Royal.Common.WebStuff
{
    public static class RestHandlerUtils
    {
        internal static void WriteExceptionJsonString(HttpContext context, Exception ex, int statusCode)
        {
            string charset = context.Response.Charset;
            context.Response.ClearHeaders();
            context.Response.ClearContent();
            context.Response.Clear();
            context.Response.StatusCode = statusCode;
            context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(statusCode);
            context.Response.ContentType = "application/json";
            context.Response.AddHeader("jsonerror", "true");
            context.Response.Charset = charset;
            context.Response.TrySkipIisCustomErrors = true;
            using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))
            {
                if (ex is TargetInvocationException)
                    ex = ex.InnerException;
                var error = new OrderedDictionary();
                error["Message"] = ex.Message;
                error["StackTrace"] = ex.StackTrace;
                error["ExceptionType"] = ex.GetType().FullName;
                streamWriter.Write(JsonConvert.SerializeObject(error));
                streamWriter.Flush();
            }
        }

        public static void ReplaceRestHandler()
        {
            //https://*.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method
            var methodToInject = typeof(RestHandlerUtils).GetMethod("WriteExceptionJsonString",
                BindingFlags.NonPublic | BindingFlags.Static);
            var asm = typeof(System.Web.Script.Services.ScriptMethodAttribute).Assembly;
            var rhtype = asm.GetType("System.Web.Script.Services.RestHandler");
            var methodToReplace = rhtype
                .GetMethod("WriteExceptionJsonString", BindingFlags.NonPublic | BindingFlags.Static, null,
                    new Type[] {typeof(HttpContext), typeof(Exception), typeof(int)}, null);

            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*) methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*) methodToReplace.MethodHandle.Value.ToPointer() + 2;
                    *tar = *inj;
                }
                else
                {
                    long* inj = (long*) methodToInject.MethodHandle.Value.ToPointer() + 1;
                    long* tar = (long*) methodToReplace.MethodHandle.Value.ToPointer() + 1;
                    *tar = *inj;
                }
            }
        }
    }
}