首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 實戰教學 (4):例外處理機制全解析 — Exception、FaultException、FaultException 與 IErrorHandler

     編輯:關於.NET

前言

WCF 服務端拋出的 CLR 例外 (Exception) 無法直接跨越服務邊界傳遞給用戶端,預設會被轉換為通用的 FaultException 並導致通道進入 Faulted 狀態。為了實現型別安全、跨平台相容的錯誤傳遞,WCF 提供了 FaultException、FaultException<TDetail> (錯誤契約) 以及 IErrorHandler 全域錯誤攔截機制。本篇完整示範四種錯誤處理模式的實作與用戶端捕獲差異。

四種錯誤處理模式比較

模式服務端拋出用戶端捕獲型別安全通道狀態適用場景
原始 Exceptionthrow new Exception()CommunicationException / FaultException❌Faulted (需重建 Proxy)除錯期、非預期錯誤
FaultExceptionthrow new FaultException()FaultException❌ (僅字串)正常 (可繼續使用)簡單錯誤通知
FaultException<T>throw new FaultException<TDetail>()FaultException<TDetail>✅ (強型別)正常正式環境、跨語言互通
IErrorHandler統一攔截未處理例外依 ProvideFault 邏輯決定可控制可控制全域日誌、統一錯誤格式、防止通道 Faulted

完整程式碼範例

1. 定義錯誤資料契約 (供 FaultException<T> 使用)

using System.Runtime.Serialization;

namespace WCF.ServiceLib.Exception
{
    [DataContract]
    public class FaultMessage
    {
        [DataMember] public string Message { get; set; }
        [DataMember] public int ErrorCode { get; set; }
    }
}

2. 服務契約 — 宣告 FaultContract

using System.ServiceModel;

namespace WCF.ServiceLib.Exception
{
    [ServiceContract]
    public interface IHello
    {
        [OperationContract]
        void HelloException();                    // 原始 Exception

        [OperationContract]
        void HelloFaultException();               // 非泛型 FaultException

        [OperationContract]
        [FaultContract(typeof(FaultMessage))]     // 關鍵:宣告錯誤契約
        void HelloFaultExceptionGeneric();        // 強型別 FaultException

        [OperationContract]
        void HelloIErrorHandler();                // 由 IErrorHandler 處理
    }
}

3. 服務實作 — 多種拋出方式

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WCF.ServiceLib.Exception
{
    public class Hello : IHello, IServiceBehavior, IDisposable
    {
        // 1. 原始 Exception - 會導致通道 Faulted
        public void HelloException() => throw new Exception("拋出 Exception 異常");

        // 2. 非泛型 FaultException - 可攜帶 FaultCode、Reason
        public void HelloFaultException()
            => throw new FaultException("拋出 FaultException 異常", new FaultCode("服務"));

        // 3. 強型別 FaultException - 需搭配 [FaultContract]
        public void HelloFaultExceptionGeneric()
            => throw new FaultException(
                new FaultMessage { Message = "拋出 FaultException 異常", ErrorCode = -1 },
                new FaultReason("為了測試 FaultException 用的"));

        // 4. 觸發 IErrorHandler 攔截
        public void HelloIErrorHandler() => throw new IOException("拋出異常,由 IErrorHandler 處理");

        // --- IServiceBehavior 實作:註冊 IErrorHandler ---
        public void AddBindingParameters(ServiceDescription sd, ServiceHostBase shb, 
            System.Collections.ObjectModel.Collection ep, 
            BindingParameterCollection bp) { }

        public void ApplyDispatchBehavior(ServiceDescription sd, ServiceHostBase shb)
        {
            var handler = new FaultErrorHandler();
            foreach (ChannelDispatcher cd in shb.ChannelDispatchers)
                cd.ErrorHandlers.Add(handler);
        }

        public void Validate(ServiceDescription sd, ServiceHostBase shb) { }

        public void Dispose() { }
    }
}

4. 自訂 IErrorHandler — 統一錯誤處理與日誌

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace WCF.ServiceLib.Exception
{
    public class FaultErrorHandler : IErrorHandler
    {
        // 例外返回給用戶端「之前」被呼叫:可轉換為 FaultException 避免通道 Faulted
        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            if (error is IOException)
            {
                var fe = new FaultException("IErrorHandler - ProvideFault 測試");
                var mf = fe.CreateMessageFault();
                fault = Message.CreateMessage(version, mf, fe.Action);
            }
            // 其他例外類型可依需求轉換
        }

        // 例外返回給用戶端「之後」被呼叫:適合寫入日誌、發送警告
        public bool HandleError(Exception error)
        {
            File.AppendAllText(@"C:\WCF_Log.txt",
                $"[{DateTime.Now}] IErrorHandler - HandleError: {error.GetType().Name} - {error.Message}{Environment.NewLine}");
            return true; // true = 已處理,不再傳遞
        }
    }
}

5. 宿主 Web.config (IIS 裝載)

<service name="WCF.ServiceLib.Exception.Hello" behaviorConfiguration="ExceptionBehavior">
  <endpoint address="" binding="wsHttpBinding" contract="WCF.ServiceLib.Exception.IHello" />
</service>

<behaviors>
  <serviceBehaviors>
    <behavior name="ExceptionBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true" /> <!-- 開發期開啟,正式環境建議 false -->
    </behavior>
  </serviceBehaviors>
</behaviors>

6. 用戶端捕獲差異示範 (ASP.NET Web Form)

public partial class Exception_Hello : System.Web.UI.Page
{
    // 1. 原始 Exception - 捕獲到 CommunicationException,通道 Faulted
    protected void btnHelloException_Click(object sender, EventArgs e)
    {
        var proxy = new ExceptionService.HelloClient();
        try { proxy.HelloException(); }
        catch (Exception ex) { lblMsg.Text = ex.Message; }
        finally { SafeClose(proxy); }
    }

    // 2. FaultException - 捕獲 FaultException,通道正常
    protected void btnHelloFaultException_Click(object sender, EventArgs e)
    {
        var proxy = new ExceptionService.HelloClient();
        try { proxy.HelloFaultException(); }
        catch (FaultException fe) 
        { 
            lblMsg.Text = $"錯誤編碼:{fe.Code.Name};錯誤原因:{fe.Reason}"; 
        }
        finally { proxy.Close(); }
    }

    // 3. FaultException - 強型別捕獲,取得 Detail 物件
    protected void btnHelloFaultExceptionGeneric_Click(object sender, EventArgs e)
    {
        var proxy = new ExceptionService.HelloClient();
        try { proxy.HelloFaultExceptionGeneric(); }
        catch (FaultException fe)
        {
            lblMsg.Text = $"錯誤代碼:{fe.Detail.ErrorCode};錯誤訊息:{fe.Detail.Message};原因:{fe.Reason}";
        }
        finally { proxy.Close(); }
    }

    // 4. IErrorHandler 轉換後的 FaultException
    protected void btnHelloIErrorHandler_Click(object sender, EventArgs e)
    {
        var proxy = new ExceptionService.HelloClient();
        try { proxy.HelloIErrorHandler(); }
        catch (FaultException fe)
        {
            lblMsg.Text = $"錯誤訊息:{fe.Message}";
        }
        catch (Exception ex)
        {
            lblMsg.Text = ex.ToString();
        }
        finally { proxy.Close(); }
    }

    private void SafeClose(ICommunicationObject proxy)
    {
        try { proxy.Close(); }
        catch { proxy.Abort(); } // Faulted 狀態只能 Abort
    }
}

執行結果對照

按鈕用戶端顯示通道狀態
HelloException拋出 Exception 異常 + CommunicationObjectFaultedFaulted (需 Abort)
HelloFaultException錯誤編碼:服務;錯誤原因:拋出 FaultException 異常Opened (可繼續用)
HelloFaultExceptionGeneric錯誤代碼:-1;錯誤訊息:...;原因:...Opened
HelloIErrorHandler錯誤訊息:IErrorHandler - ProvideFault 測試Opened

最佳實務總結

  1. 正式環境務必使用 FaultException<T> + FaultContract:提供強型別錯誤資訊,避免用戶端解析字串。
  2. 實作 IErrorHandler 統一處理未預期例外:記錄完整堆疊、轉換為友善 Fault、防止通道 Faulted、整合監控系統 (如 ELK、AppInsights)。
  3. 服務端設定 includeExceptionDetailInFaults="false":避免洩露堆疊細節,僅在開發/測試環境開啟。
  4. 用戶端永遠使用 try/catch/finally + SafeClose 模式:確保通道正確關閉或中止,避免連線洩漏。
  5. 定義共用錯誤碼 Enum/常數類別:服務端與用戶端共用,避免魔術數字。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved