[Wcf] 發生"ServiceChannel 處於 Faulted 狀態,因此無法用來通訊"的解決辦法

[Wcf] 發生"ServiceChannel 處於 Faulted 狀態,因此無法用來通訊"的解決辦法


前言


最近專案在使用Wcf來當作網路通訊的基礎架構,

但在開發時期常常出現下面這個問題,

ServiceChannel 處於 Faulted 狀態,因此無法用來通訊

這時候Client無法Dispose,也無法呼叫任何Service Function,

只能把Client Abort掉再重新建立,

現在讓我們來了解一下問題發生的原因吧!

 

實際演練


首先,讓我們來重建問題發生的場景,

一開始先建立一個Wcf的Service

IDivideService


    public interface IDivideService
    {
        [OperationContract]
        int Divide(int a, int b);
    }

DevideService


{
    #region IDivideService Members

    public int Divide(int a, int b)
    {
        return a / b;
    }

    #endregion
}

再來建立Wcf的Client


    {
        public DivideServiceProxy()          
        {           
        }        

        #region IDivideService Members

        public int Divide(int a, int b)
        {
            return base.Channel.Divide(a, b);
        }

        #endregion
    }

我們在程式中使用Client的片段如下


            {
                try
                {
                    mProxy.Divide(1, 0);                
                }
                catch(Exception e)
                {                 
                       //Do Error Handling
                }
            }

執行之後我們可以看到,程式抓到錯誤訊息如下:

49

這是很正常的,因為我們嘗試除以零,所以我們可以接收到Server所傳出的Exception,

但接下來跑完Using區段之後,卻發現在mProxy Dispose階段出現下列Exception而無法Dispose

50

我們雖然有用Try Catch包住了,卻還是造成Channel變成Faulted的狀況, 這是因為在Wcf中,

若Service擲出任何未定義在FaultContract中的Exception,Channel就會切換為Faulted的狀態!

解決的方法很簡單,我們只要在Service Contract中定義我們預期會發生並允許Client接收的FaultContract即可解決,

首先在Interface中定義FaultContract


    public interface IDevideService
    {
        [OperationContract]
        [FaultContract(typeof(CalculateError))]
        int Divide(int a, int b);
    }

    [DataContract]
    public class CalculateError
    {
        public CalculateError(string errorMessage)
        {
            this.ErrorMessage = errorMessage;
        }

        [DataMember]
        public string ErrorMessage { get; set; }
    }

改寫Service,當DivideByZero時丟出定義好的FaultException


{

    #region IDevideService Members

    public int Divide(int a, int b)
    {
        int result;

        try
        {
            result = a / b;
        }
        catch (DivideByZeroException ex)
        {
            throw new FaultException<CalculateError>(new CalculateError(ex.Message), new FaultReason("Can't Divide by Zero!"));
        }
        catch
        {
            throw;
        }

        return result;
    }

    #endregion
}

再次重新執行我們的Client程式,我們可以發現能夠正確地接受到Exception,Channel也不會變成Faulted的狀態了

當然在開發過程中,我們可能並不會在開發初期就將所有預期發生的Exception完全定義在FaultContract中,

在Client開發時也可以使用一種比較Tricky的解決方法,但建議在開發後期FaultContract定義明確之後,

要把這段程式碼拿掉,使用上面介紹的方法來避免這個問題的發生!使用正確的錯誤處理機制

我們可以在Client的Proxy Class中註冊Channel的Faulted Event,並即時的建立一個新的頻道


        {
            (base.Channel as ICommunicationObject).Faulted += new EventHandler(DivideServiceProxy_Faulted);
        }

        void DivideServiceProxy_Faulted(object sender, EventArgs e)
        {
            (sender as ICommunicationObject).Abort();
            if (sender is IDivideService)
            {
                sender = base.ChannelFactory.CreateChannel();
            }
        }   

當Channel變成Faulted的時候,我們就馬上Dispose並創建一個新的Channel來替代,

就可以讓Client在Debug的過程不會一直被中斷囉!

 

結語


在Wcf的開發之中,所有Client會收到的Exception應該都是明確定義在FaultContract之中的,

若有任何未被定義在FaultContract之中的Exception被擲出,就會造成Channel變成Faulted的狀況發生,

我們應該在程式之中盡可能地考慮的各種Exception的發生情況,並定義清楚允許Client收到的FaultContract。

如果沒有處理好也可能會洩漏系統的安全漏洞,需特別小心注意才行!

如果有任何問題歡迎大家多多提出來討論 ^_^