今天學習WCF分布式開發步步為贏系列的15節:錯誤契約(FaultContract)與異常處理(ExceptionHandle)。本節內容作為 WCF分布式開發的一個重要知識點,無論在學習還是項目中都應該有所了解。此前也和多位學習愛好者討論過WCF異常處理的相關知識。這裡就系統整理一下,共大家參考。同時也是對《WCF分布式開發步步為贏》系列文章的完善和補充。
本節主要涉及的知識點就是:【1】.NET異常處理【2】WCF異常處理【3】錯誤契約【4】WCF異常處理擴展【5】示例代碼分析,最後是【6】總結部分。
首先我們來回憶一下.NET裡一個重要的概念異常處理ExceptionHandle。異常處理在JAVA平台也有自己的機制,這個不是一個WCF特有的概念,同樣要了解WCF的異常處理,我們有必要先來了解其前身.NET的異常處理相關的概念。
【1】.NET異常處理:
.NET Framework 中的托管異常是憑借 Win32 結構化異常處理機制實現的。公共語言運行庫提供了一個模型,以統一的方式通知程序發生的錯誤,這樣為設計容錯軟件提供了極大的幫助。所有的.NET Framework操作都通過引發異常來指示出現錯誤。傳統上,語言的錯誤處理模型依賴於語言檢測錯誤和查找錯誤處理程序的獨特方法,或者依賴於操作系統提供的錯誤處理機制。運行庫實現的異常處理具有下列特點:
處理異常時不用考慮生成異常的語言或處理異常的語言。
異常處理時不要求任何特定的語言語法,而是允許每種語言定義自己的語法。
允許跨進程甚至跨計算機邊界引發異常。
與其他錯誤通知方法(如返回代碼)相比,異常具有若干優點。不再有出現錯誤而不被人注意的情況。無效值不會繼續在系統中傳播。不必檢查返回代碼。可以輕松添加異常處理代碼,以增加程序的可靠性。最後,運行庫的異常處理比基於Windows 的C++錯誤處理更快。
由於執行線程例行地遍歷托管代碼塊和非托管代碼塊,因此運行庫可以在托管代碼或非托管代碼中引發或捕捉異常。非托管代碼可以同時包含C++樣式的 SEH 異常和基於COM 的HRESULT。
異常處理使用 try、catch 和 finally 關鍵字嘗試某些操作,以處理失敗情況,盡管這些操作有可能失敗,但如果您確定需要這樣做,且希望在事後清理資源,就可以嘗試這樣做。公共語言運行時 (CLR)、.NET Framework 或任何第三方庫或者應用程序代碼都可以生成異常。異常是使用 throw 關鍵字創建的。(MSDN)
【2】WCF異常處理:
現在我們來了解一下WCF的異常處理機制。前面我們介紹了.NET的異常處理機制。WCF也是.NET框架的一部分,很多一場處理方式基本相同。但是由於其跨服務平台的目標要求,導致了WCF並不支持傳統的異常處理方式。傳統方式上.NET拋出的未經處理的異常會立即終止主進程,而WCF則不會。
【2.1】WCF錯誤類型:
在進行WCF分布式應用開發的過程中,我們客戶端經常會遇到一下三種常見的錯誤。
(1):通信錯誤,可能和網絡、通道等相關的異常,客戶端表現為Communication Exception;
(2):代理和通道的State,代理已經關閉,或者通道Fault,等問題,這個比較常見。一般通道閒置時間過久,通道會出現這個狀態錯誤的問題。一般我們可以通過代理的State來判斷。安全驗證失敗也會導致這個錯誤。
(3):服務調用錯誤,服務調用時拋出的異常,這個服務內部異常會序列化傳遞給客戶端,被客戶端捕獲。
第三種是我們本節要詳細講述的類型。
【2.2】FaultException:
這裡最關鍵的問題就是,WCF服務拋出的異常信息往往是基於.NET的內部異常信息,這些信息不能被序列化,也就不能在客戶端和服務端實現共享錯誤信息。
因此如果在WCF服務中采用傳統的方式處理異常,由於異常消息不能被序列化,因而客戶端無法捕獲和處理服務拋出的異常。為了解決這個問題,WCF提供了 FaultException。這個是一個基於行業標准的SOAP異常類,WCF會將無法識別的異常處理為統一的FaultException異常對象,因此,可以把錯誤信息傳遞到客戶端,客戶端可以捕獲FaultException或者Exception。你可以通過.NET Reflector查看此類的定義,FaultException的部分代碼如下:
[Serializable, KnownType(typeof(FaultReasonData[])), KnownType(typeof(FaultCodeData)), KnownType(typeof(FaultCodeData[])), KnownType(typeof(FaultReasonData))] public class FaultException : CommunicationException { // Fields private string action; private FaultCode code; private MessageFault fault; internal const string Namespace = "http://schemas.xmlsoap.org/Microsoft/WindowsCommunicationFoundation/2005/08/Faults/"; private FaultReason reason; // Methods public FaultException(); }
我們可以注意到FaultException具有序列化的標記。聲明了Serializable.
FaultException的另外一個重要的泛型定義就是FaultException<T>,這裡我們可以使用任何系統類型或者自定義類型來傳遞錯誤信息,T代表要傳遞的錯誤細節。此類也可以使用反射器查看代碼:
[Serializable] public class FaultException<TDetail> : FaultException { // Fields private TDetail detail; // Methods public FaultException(TDetail detail); public FaultException(TDetail detail, FaultReason reason); public FaultException(TDetail detail, string reason); protected FaultException(SerializationInfo info, StreamingContext context); public FaultException(TDetail detail, FaultReason reason, FaultCode code); public FaultException(TDetail detail, string reason, FaultCode code); public FaultException(TDetail detail, FaultReason reason, FaultCode code, string action); public FaultException(TDetail detail, string reason, FaultCode code, string action); public override MessageFault CreateMessageFault(); [SecurityCritical, SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter=true)] public override void GetObjectData(SerializationInfo info, StreamingContext context); public override string ToString(); // Properties public TDetail Detail { get; } }
【3】錯誤契約FaultContract:
說道WCF的異常處理,這裡就必須介紹一下一個重要的概念:錯誤契約(FaultContract)。
前面已經介紹了WCF的FaultException,那為什麼我們還要FaultContract。
默認情況,WCF會把服務拋出的異常以FaultException類型傳遞到客戶端。
但是WCF規定,任何客戶端和服務共享的異常,必須屬於服務行為的特性。也就是我們必須給特性的服務操作定義了服務契約才能夠提供錯誤類型。WCF為異常處理專門提供了FaultContract特性,它可以被應用到服務操作上,指明操作可能會拋出的異常類型。代碼如下:
[AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=false)] public sealed class FaultContractAttribute : Attribute { // Fields private string action; private bool hasProtectionLevel; private string name; .... }
錯誤契約FaultContract其實是WCF4大契約中的一種(Service Contract,DataContract,MessageContract,FaultContract ).聲明一個服務操作契約方法為FaultContract並指定了響應的類型參數,這個參數要和FaultException<T>的類型一致。
【4】WCF異常處理擴展:
WCF允許開發者定制異常報告和異常傳遞的過程。但是要實現System.ServiceModel.Dispatcher.IErrorHandler接口:
IErrorHandler的定義如下:
public interface IErrorHandler { // Methods bool HandleError(Exception error); void ProvideFault(Exception error, MessageVersion version, ref Message fault); }
服務端拋出的異常會再調用ProvideFault()方法後再返回給客戶端。ProvideFault不考慮異常的類型。
另外ProvideFault一個重要的作用,異常提升(Exception Promotion)。它可以將非FaultContract異常提升為FaultContract<T>異常,例如將 OverflowException異常提升為FaultExceptino< OverflowException>異常。而HandleError()方法,我們可以重新實現代碼,如log日志。
此外,如果要安裝自己定制的IErrorHandle,需要將它添加到對應的分發器裡即可。
我們在服務類實現System.ServiceModel.Description.IServiceBehavior接口 ApplyDispatchBehavior()方法中來實現安裝錯誤處理擴展。IServiceBehavior的主要方法 ApplyDispatchBehavior:
public interface IServiceBehavior { // Methods void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters); void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase); void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase); }
【5】示例代碼分析:
下面我們給出本節文章的簡單示例代碼。
【5.1】服務端:
這裡我們定義了兩個服務操作方法,主要是為了測試WCF的異常處理,SayHello和SayGoodBye。參數是個string 類型的name,長度大於10的時候就會拋出一個OverflowException內存溢出的異常,然後我們通過FaultException來包裝,傳遞到客戶端。這裡我們也對操作契約應用了FaultContract特性。定義代碼如下:
//1.服務契約
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFService
{
//操作契約
[OperationContract]
[FaultContract(typeof(OverflowException))]//標注以後,WCF客戶端才能區分錯誤契約,否則會作為通信異常拋出
string SayHello(string name);
//操作契約
[OperationContract]
[FaultContract(typeof(OverflowException))]
string SayGoodBye(string name);
}
//2.服務類,繼承接口。實現服務契約定義的操作
public class WCFService : IWCFService
{
//實現接口定義的方法
public string SayHello(string name)
{
if (name.Length < 10)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Hello! {0},...", name);
return "Hello! " + name;
}
else
{//泛型類FaultException,可以包含不同的異常,可以序列化。基於SOAP的錯誤消息
OverflowException oe = new OverflowException();
throw new FaultException<OverflowException>(oe, "name Length is more than 10");
}
}
//實現接口定義的方法
public string SayGoodBye(string name)
{
if (name.Length < 10)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("GoodBye! {0},...", name);
return "Hello! " + name;
}
else
{
OverflowException oe = new OverflowException();
throw new FaultException<OverflowException>(oe,"name Length is more than 10");
}
}
}
【5.2】宿主:
這裡宿主依然是Console 程序,自托管WCF服務。會打印每次的調用信息。比較簡單,代碼如下:
static void Main(string[] args)
{
//反射方式創建服務實例,
//Using方式生命實例,可以在對象生命周期結束時候,釋放非托管資源
using (ServiceHost host = new ServiceHost(typeof(WCFService.WCFService)))
{
////判斷是否以及打開連接,如果尚未打開,就打開偵聽端口
if (host.State != CommunicationState.Opening)
host.Open();
//顯示運行狀態
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Host is runing! and state is {0}", host.State);
Console.ForegroundColor = ConsoleColor.Red;
//print endpoint information
foreach (ServiceEndpoint se in host.Description.Endpoints)
{
Console.WriteLine("Host is listening at {0}", se.Address.Uri.ToString());
}
//等待輸入即停止服務
Console.Read();
}
}
【5.3】客戶端:
客戶端分別傳遞兩次參數,第一次正常調用,第二次參數長度大於10,引起服務端異常,然後跑出到客戶端,客戶端根據定義的FaultException<T>來處理異常。代碼如下:
//WSHttpBinding_IWCFService
Test.WCFServiceClient wcfServiceProxyHttp = new Test.WCFServiceClient("WSHttpBinding_IWCFService");
//通過代理調用SayHello服務
try
{
Console.WriteLine(wcfServiceProxyHttp.SayHello("Frank Xu"));
Console.WriteLine(wcfServiceProxyHttp.SayHello("Frank Xu Lei WSHttpBinding"));
}
catch (FaultException<OverflowException> oe)
{
Console.WriteLine(oe.Message);
}
catch (CommunicationException ce)
{
Console.WriteLine(ce.Message);
}
finally
{
wcfServiceProxyHttp.Close();
}
//For Debug
Console.WriteLine("Press any key to exit...");
Console.Read();
這裡運行程序,測試結果如下:
【6】總結:
以上就是WCF異常處理的全部內容。一下幾點我們要注意:
(1) 即使我們不加FaultContract特性,或者拋出非FaultException異常,客戶端也可以獲得異常消息,該異常會導致通道出現錯誤。
(2)此外我們也可以通過添加ServiceBehavior特性,將服務的IncludeExceptionDetailInFaults設置為 true(默認為 false),客戶端也可以捕獲拋出的非FaultException異常信息,但該異常仍然會導致通道出現錯誤。
(3)異常對WCF服務實例的影響和服務的激活類型有關。通常單調PerCall和會話模式,WCF服務異常會導致服務釋放服務實例,而Single單例模式不會,WCF服務會繼續運行。
(4)在發布服務與部署服務時,我們應避免將服務的IncludeExceptionDetailInFaults設置為true。這回帶來性能問題。
本文配套源碼