使用格式錯誤的 Json 呼叫 ASP.NET WebMethod 時擷取錯誤
我們有一個較舊的 ASP.NET WebForms 應用程序,它通過在客戶端使用 jQuery
$.ajax()呼叫來執行 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。
在我的情況下是否有一些類似的方法可以附加自定義處理程序?
根據參考資料,內部
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); } } }