Asp.net

使用格式錯誤的 Json 呼叫 ASP.NET WebMethod 時擷取錯誤

  • September 26, 2019

我們有一個較舊的 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);
       }
   }
}

引用自:https://stackoverflow.com/questions/24054573