2010-08-28 36 views
15

我想捕捉在ASP.NET Web服務中引發的任何未處理的異常,但沒有任何我嘗試過的工作到目前爲止。有沒有辦法在AJAX-web服務中捕獲所有錯誤?

首先,在HttpApplication.Error不會觸發事件上的Web服務,所以這是了..

下一個方法是實現SOAP擴展,並將其添加到與web.config中:

<soapExtensionTypes> 
    <add type="Foo" priority="1" group="0" /> 
</soapExtensionTypes> 

然而,如果你打電話超過JSON網絡方法(我的網站不完全),這並不工作..

我的下一個想法是寫我自己的HttpHandler爲的.asmx,這將希望從System.Web.Script.Services.ScriptHandlerFac派生並且做一些聰明的事情。我還沒有嘗試過。

有沒有我失蹤的方法?謝謝!

邁克

UPDATE:

我可能會在這裏總結了解決方案:

1)升級到WCF這使得這整個事情非常非常容易。

2)由於你不能子類或重寫RestHandler類,你將不得不重新實現整個東西作爲你自己的IHttpHandler或使用反射手動調用它的方法。由於RestHandler的源代碼是公共的,只有大約500行的長度,所以製作自己的版本可能不是一項大量的工作,但是您將負責維護它。我也不知道這個代碼涉及任何許可限制。 3)你可以將你的方法包裝在try/catch塊中,或者使用LAMBDA表達式來使這些代碼更清晰一些。它仍然會要求您修改Web服務中的每個方法。

+0

是否有任何靈活性您無法修改Web方法的要求? – 2011-11-11 16:57:34

+0

嗯,是的 - 只要我不必在巨大的try/catch塊中包圍它們所有的東西。如果解決方案很乾淨,那麼我對它開放。 – 2011-11-11 16:58:39

+0

我有一個解決方案,它需要一個Try塊和一行Catch。你或許可以改進它,完全消除try/catch,我還沒有深入挖掘。要發佈嗎? – 2011-11-11 17:01:14

回答

11

Capture all unhandled exceptions automatically with WebService所述,確實沒有好的解決方案。

您無法捕獲HttpApplication.Error等的原因與Microsoft的好人如何實施RestHandler有關。具體來說,RestHandler明確捕獲(句柄)異常並寫出異常細節的響應:

internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData) 
{ 
    try 
    { 
     NamedPermissionSet namedPermissionSet = HttpRuntime.NamedPermissionSet; 
     if (namedPermissionSet != null) 
     { 
      namedPermissionSet.PermitOnly(); 
     } 
     IDictionary<string, object> rawParams = GetRawParams(methodData, context); 
     InvokeMethod(context, methodData, rawParams); 
    } 
    catch (Exception exception) 
    { 
     WriteExceptionJsonString(context, exception); 
    } 
} 

更糟糕的是,沒有乾淨的擴展點(我能找到),您可以更改/延長行爲。如果你想要寫自己的IHttpHandler,我相信你幾乎不得不重新實現RestHandler(或者RestHandlerWithSession)。無論Reflector會成爲你的朋友。

對於那些可能選擇。如果您正在使用Visual Studio 2008或更高版本來修改他們的WebMethods

,使用Lambda表達式使事情不是太糟糕了(雖然不是全球/通用的解決方案)的條款或刪除複製碼。

[WebMethod] 
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)] 
public String GetServerTime() 
{ 
    return Execute(() => DateTime.Now.ToString()); 
} 

public T Execute<T>(Func<T> action) 
{ 
    if (action == null) 
    throw new ArgumentNullException("action"); 

    try 
    { 
    return action.Invoke(); 
    } 
    catch (Exception ex) 
    { 
    throw; // Do meaningful error handling/logging... 
    } 
} 

其中Execute可以在WebService的子類中實現或作爲擴展方法實現。

UPDATE:反思危機

正如我在原始答覆中提到,你可以濫用反射來得到你想要的......具體情況,你可以創建自己的HttpHandler,它利用了RestHandler的內部來提供捕獲異常細節的攔截點。我在下面列出了一個「不安全」的代碼示例,以幫助您入門。

就個人而言,我不會使用此代碼;但它的工作原理。

namespace WebHackery 
{ 
    public class AjaxServiceHandler : IHttpHandler 
    { 
    private readonly Type _restHandlerType; 
    private readonly MethodInfo _createHandler; 
    private readonly MethodInfo _getRawParams; 
    private readonly MethodInfo _invokeMethod; 
    private readonly MethodInfo _writeExceptionJsonString; 
    private readonly FieldInfo _webServiceMethodData; 

    public AjaxServiceHandler() 
    { 
     _restHandlerType = typeof(ScriptMethodAttribute).Assembly.GetType("System.Web.Script.Services.RestHandler"); 

     _createHandler = _restHandlerType.GetMethod("CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext) }, null); 
     _getRawParams = _restHandlerType.GetMethod("GetRawParams", BindingFlags.NonPublic | BindingFlags.Static); 
     _invokeMethod = _restHandlerType.GetMethod("InvokeMethod", BindingFlags.NonPublic | BindingFlags.Static); 
     _writeExceptionJsonString = _restHandlerType.GetMethod("WriteExceptionJsonString", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext), typeof(Exception) }, null); 

     _webServiceMethodData = _restHandlerType.GetField("_webServiceMethodData", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField); 
    } 

    public bool IsReusable 
    { 
     get { return true; } 
    } 

    public void ProcessRequest(HttpContext context) 
    { 
     var restHandler = _createHandler.Invoke(null, new Object[] { context }); 
     var methodData = _webServiceMethodData.GetValue(restHandler); 
     var rawParams = _getRawParams.Invoke(null, new[] { methodData, context }); 

     try 
     { 
     _invokeMethod.Invoke(null, new[] { context, methodData, rawParams }); 
     } 
     catch (Exception ex) 
     { 
     while (ex is TargetInvocationException) 
      ex = ex.InnerException; 

     // Insert Custom Error Handling HERE... 

     _writeExceptionJsonString.Invoke(null, new Object[] { context, ex}); 
     } 
    } 
    } 
} 
+0

是的,我想我可能會查看代碼,看看我是否可以編寫一個HttpHandler以某種方式「包裝」現有的處理程序。除此之外,我認爲沒有乾淨的解決方案,我應該將錯誤處理添加到更復雜的Web方法中。 +1爲您的詳細答案! – 2011-11-16 16:42:06

+0

由於RestHandler的實現方式,您的唯一擴展選項可能是反射濫用(假設完全信任),因爲RestHandler中的所有內容都是內部的或私有的。 – 2011-11-16 16:57:24

+0

呃去圖..不知道你是否可以寫一些HttpFilter來檢測響應流中的SOAP異常並將它們記錄到數據庫中。真的,我想在這裏做的是錯誤日誌記錄。現在我用Javascript捕捉異常,然後重新調用另一個webmethod來記錄錯誤。 – 2011-11-16 17:03:00

4

這是一個很好的問題。我在我工作的網絡應用程序中遇到過這個問題。恐怕我的解決方案在任何方面都不聰明。我簡單地用try/catch封裝了每個Web服務方法中的代碼,並返回了一個表示方法是否已成功運行的對象。幸運的是,我的應用沒有大量的Web服務調用,所以我可以逃脫這一點。如果您正在執行大量的Web服務調用,我很感激這不是一個好的解決方案。

+0

我在我正在開發的最新的Web應用程序中做同樣的事情。我有很多通過jQuery調用Ajax調用,當服務器端方法拋出一個異常時,我會修正JSON並使用全局客戶端方法通知用戶存在問題以及該問題的詳細信息。 – 2011-11-17 16:10:39

2

你已經採取了看看WCF Web API? 這是目前處於測試階段,但它已在幾個活網站中使用。暴露json/xml休息API是很容易的。 在這裏你可以創建你自己的HttpErrorHandler派生類來處理錯誤。這是一個非常乾淨的解決方案。值得仔細看看你。遷移任何現有的服務也應該是直截了當的。希望能幫助到你!

+0

這很有趣!我認爲我還沒有將它用於製作,但我一定會留意這一點。鏈接+1! – 2011-11-16 16:37:32

+1

我會第二次在WCF世界中這是非常容易的;只需創建一個IErrorHandler(http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.85).aspx)和相關的服務行爲即可完成。 – 2011-11-16 16:59:51

+0

對我來說,聽起來像我最好的選擇就是將我的代碼移植到WCF。不知道這將是多麼艱難.. – 2011-11-16 17:06:03

2

編輯

你嘗試把web.config中的錯誤處理掉,然後加上一個誤差模塊?

<location path="Webservices" > 
    <system.web> 
    <customErrors mode="Off" /> 
    </system.web> 
</location> 

正常的錯誤處理,因爲我認爲它應該是,即使是RestHandler

您可以創建一個HTTP模塊來捕獲所有的錯誤,並返回定製的結果。請注意,這將是對客戶端的響應,而不是Web服務的預期結果。您也可以在應用程序中拋出任何自定義異常,並讓該模塊在客戶端響應或通知用戶時處理它。正確執行的

結果舉例:未能執行

{ 
    success: true, 
    data: [ .... ], 
    error: null 
} 

結果舉例:

{ 
    success: false, 
    data: null, 
    error: { 
    message: 'error message', 
    stackTrace: 'url encoded stack trace' 
    } 
} 

這個添加到Web.config:

<modules> 
    <add name="UnhandledException" type="Modules.Log.UnhandledException"/> 
</modules> 

您可以輕鬆地添加代碼通過實現漂亮的無證書來使模塊自行註冊PreApplicationStartMethod函數在模塊中創建並使用您的自定義HttpApplication類來使用,而不是使用標準的。通過這樣做,您只需將其添加到應用程序的bin文件夾即可將任何模塊添加到應用程序中。這是我現在完成所有應用程序的方式,我必須說它完美地工作。

下面的代碼將捕捉所有應用程序錯誤,並返回該錯誤消息的HTML響應(你需要用自己的JSON的錯誤信息執行,以取代HTML的東西):

using System.Web; 

namespace Modules.Log 
{ 
    using System; 
    using System.Text; 

    public class LogModule : IHttpModule 
    { 
     private bool initialized = false; 
     private object initLock = new Object(); 
     private static string applicationName = string.Format("Server: {0}; [LogModule_AppName] installation name is not set", System.Environment.MachineName); 

     public void Dispose() 
     { 
     } 

     public void Init(HttpApplication context) 
     { 
      // Do this one time for each AppDomain. 
      if (!initialized) 
      { 
       lock (initLock) 
       { 
        if (!initialized) 
        { 
         context.Error += new EventHandler(this.OnUnhandledApplicationException); 

         initialized = true; 
        } 
       } 
      } 
     } 

     private void OnUnhandledApplicationException(object sender, EventArgs e) 
     { 
      StringBuilder message = new StringBuilder("<html><head><style>" + 
       "body, table { font-size: 12px; font-family: Arial, sans-serif; }\r\n" + 
       "table tr td { padding: 4px; }\r\n" + 
       ".header { font-weight: 900; font-size: 14px; color: #fff; background-color: #2b4e74; }\r\n" + 
       ".header2 { font-weight: 900; background-color: #c0c0c0; }\r\n" + 
       "</style></head><body><table><tr><td class=\"header\">\r\n\r\nUnhandled Exception logged by LogModule.dll:\r\n\r\nappId="); 

      string appId = (string)AppDomain.CurrentDomain.GetData(".appId"); 
      if (appId != null) 
      { 
       message.Append(appId); 
      } 

      message.Append("</td></tr>"); 

      HttpServerUtility server = HttpContext.Current.Server; 
      Exception currentException = server.GetLastError(); 

      if (currentException != null) 
      { 
       message.AppendFormat(
         "<tr><td class=\"header2\">TYPE</td></tr><tr><td>{0}</td></tr><tr><td class=\"header2\">REQUEST</td></tr><tr><td>{3}</td></tr><tr><td class=\"header2\">MESSAGE</td></tr><tr><td>{1}</td></tr><tr><td class=\"header2\">STACK TRACE</td></tr><tr><td>{2}</td></tr>", 
         currentException.GetType().FullName, 
         currentException.Message, 
         currentException.StackTrace, 
         HttpContext.Current != null ? HttpContext.Current.Request.FilePath : "n/a"); 
       server.ClearError(); 
      } 

      message.Append("</table></body></html>"); 

      HttpContext.Current.Response.Write(message.ToString()); 
      server.ClearError(); 
     } 
    } 
} 
+0

Ajax調用Web方法不會拋出未處理的異常(請參閱我的答案中的第一個代碼片段)。這種方法適用於常規調用,但RestHandler將此作爲選項移除,因爲異常是「已處理」的。 – 2011-11-17 13:45:05

+0

啊,錯過了......用另一個可能工作的東西更新了帖子... – Asken 2011-11-17 13:53:42

+0

我認爲@CalgaryCoder總結得非常好 - 異常被捕獲,然後調用WriteExceptionJsonString來將序列化的異常信息寫爲JSON串。你不得不攔截這個有運氣。蹩腳的是,他們並沒有將這些類擴展出來,而是爲了增加一個更強大的世界而進行的一些相當直接的設計變更。 – 2011-11-17 16:37:00

相關問題