Catching errors from calling ASP.NET WebMethod with malformed Json
我们有一个旧的ASP.NET webforms应用程序,它通过在客户端使用jquery
如果webmethod中发生未处理的异常,它不会激发
然而,有一件事让我很为难。如果将格式不正确的JSON发布到webmethod url,它会在输入代码之前引发异常,我找不到任何方法来捕获它。
例如,此WebMethod签名
1 2 | [WebMethod] public static string LeWebMethod(string stringParam, int intParam) |
通常使用json负载调用,比如:
1 | {"stringParam":"oh hai","intParam":37} |
我尝试使用fiddler将有效负载编辑到格式错误的json:
1 | {"stringParam":"oh hai","intPara |
并从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | {"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"} |
它仍然没有触发
我发现了一个类似的问题,其中有一个指向博客文章"如何为Web服务创建全局异常处理程序"的指针,但它似乎只对SOAP Web服务有效,而不是Ajax获取/发布。
在我的情况下,是否有类似的方法来附加自定义处理程序?
根据参考源,内部
1 2 3 4 5 6 7 8 9 10 | 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); } } |
我唯一能想到的解决方法是创建一个输出过滤器来截取和记录输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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模块)中,在
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } } } |
本文建议有两种方法来扩展WebMethods,其中soapextension更容易实现。另一个示例演示如何编写SoapExtension。它看起来像是您可以进行消息验证的地方。
当您说页面代码后面有标记为
无论如何,请测试:
页面上应该有一个这样的脚本管理器:(**1)
然后在您有
$.ajax 调用的地方,像这样调用page方法:(**2)
(** 1)
1 2 3 4 5 6 | <asp:ScriptManager ID="smPageManager" runat="server" EnablePageMethods="true" ScriptMode="Release" LoadScriptsBeforeUI="true"> </asp:ScriptManager> |
(** 2)
1 2 3 4 5 | PageMethods.LeWebMethod("hero", 1024, function(response){ alert(response); }, function(error){ alert(error); }); |
了解使用ASP.NET Ajax库的正确方法,对其进行测试,并查看错误是否正确地报告给您。
P.S:对不起,书签样式的符号,但是,现在似乎出现了一些故障。
更新
阅读这篇文章,似乎可以解释你面临的问题:
(…)如果请求是用于实现System.Web.UI.Page的类,并且它是一个REST方法调用,则使用WebServiceData类(在上一篇文章中解释过)从该页调用请求的方法。调用该方法后,将调用CompleteRequest方法,绕过所有管道事件并执行EndRequest方法。这使得MS Ajax能够调用页面上的方法,而不必创建调用方法的Web服务。(…)
尝试使用ASP.NET JavaScript代理,检查是否可以使用Microsoft生成的代码捕获错误。
这里有一个用我自己的版本替换内部resthandler实现的解决方案。您可以将异常记录在WriteExceptionJSonString方法中。这将使用动态替换C方法内容时提供的答案?换掉方法。我已经确认,如果在global.asax应用程序的start方法中添加一个对replaceRestHandler的调用,它将对我起作用。没有运行很长时间或在生产中,所以使用风险自理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | 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://stackoverflow.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; } } } } } |
这些链接可能会帮助您处理客户端的错误,
栈溢出
非封闭进化
ASP.NET
痴迷
然后您可以从客户端触发一个控制事件,通过服务器传递错误并进行日志记录。