[WCF]缺少一行代碼引發的血案
這是今天作項目支持的發現的一個關於WCF的問題,雖然最終我隻是添加了一行代碼就解決了這個問題,但是整個糾錯過程是痛苦的,甚至最終發現這個問題都具有偶然性。具體來說,這是一個關於如何自動為服務接口(契約)的每個操作添加FaultContract與WCF服務元數據發布的問題。接下來通過一個簡單的實例來說明這個因為少寫了一行代碼引發的血案。
WCF采用基於消息的通信方式,Endpoint的ABC三要素之一的契約(Contract)的本質就是定義消息的結構。契約不僅定義了正常請求和響應負載的結構,還定義了承載異常信息的響應消息的結構。為了讓契約能夠響應消息承載的錯誤信息,承載錯誤信息的類型需要利用FaultContractAttribute特性注冊到服務接口的操作方法上。
1: [ServiceContract]
2: public interface IMyService
3: {
4: [OperationContract]
5:
6: string GetData(int value);
7: }
8:
9: public class MyService : IMyService
10: {
11: public string GetData(int value)
12: {
13: var ex = new InvalidOperationException("Invalid operation...");
14: throw new FaultException<ServiceExceptionInfo>(new ServiceExceptionInfo(ex));
15: }
16: }
17:
18: [DataContract]
19: public class ServiceExceptionInfo
20: {
21: [DataMember]
22: public string ExceptionType { get; set; }
23:
24: [DataMember]
25: public string Message { get; set; }
26: public ServiceExceptionInfo(Exception ex)
27: {
28: this.ExceptionType = ex.GetType().AssemblyQualifiedName;
29: this.Message = ex.Message;
30: }
31: }
如下麵的代碼片段所示,由於GetData操作拋出的FaultException對象采用一個ServiceExceptionInfo來描述詳細錯誤信息,所以我們在定義服務接口的時候需要利用FaultContractAttribute將ServiceExceptionInfo這個類型注冊到GetData方法上。
如果多個操作都需要注冊這麼一個ServiceExceptionInfo類型,這其實是一件很繁瑣的事情。對於今天找我們作技術支持的那個項目來說,由於采用了我們提供的一個自動化異常處理框架,要求所有的操作都需要注冊一個類似於ServiceExceptionInfo的類型來描述最終的錯誤消息。為了讓具體的項目可以不用在每個操作上都添加一個FaultContractAttribute,我們自定義了一個ServiceHost來實現了對它的自動注冊。如下所示的MyServiceHost模擬了FaultContract自動化注冊的邏輯。
1: public class MyServiceHost: ServiceHost
2: {
3: public MyServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
4: { }
5:
6: protected override void OnOpening()
7: {
8: base.OnOpening();
9: foreach (var endpoint in this.Description.Endpoints)
10: {
11: string ns = endpoint.Contract.Namespace.TrimEnd('/');
12: foreach (var op in endpoint.Contract.Operations)
13: {
14: if (!op.Faults.Any(it => it.DetailType == typeof(ServiceExceptionInfo)))
15: {
16: FaultDescription fault = new FaultDescription($"{ns}/{op.Name}_ServiceExceptionInfo");
17: fault.DetailType = typeof(ServiceExceptionInfo);
18: op.Faults.Add(fault);
19: }
20: }
21: }
22: }
23: }
24:
25: public class MyServiceHostFactory : ServiceHostFactory
26: {
27: protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
28: {
29: return new MyServiceHost(serviceType, baseAddresses);
30: }
31: }
MyServiceHostFactory是MyServiceHost對應的工廠,我們可以采用如下的配置使用它。
1: <system.serviceModel>
2: <behaviors>
3: <serviceBehaviors>
4: <behavior>
5: <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
6: <serviceDebug includeExceptionDetailInFaults="true"/>
7: </behavior>
8: </serviceBehaviors>
9: </behaviors>
10: <services>
11: <service name="WcfService.MyService">
12: <endpoint binding="basicHttpBinding" contract="WcfService.IMyService"/>
13: </service>
14: </services>
15: <serviceHostingEnvironment >
16: <serviceActivations>
17: <add service="WcfService.MyService" relativeAddress="myservice.svc" factory="WcfService.MyServiceHostFactory"/>
18: </serviceActivations>
19: </serviceHostingEnvironment>
20: </system.serviceModel>
在真的WCF服務調用過程中,我們定義的這個MyServiceHost和MyServiceHostFactory一點問題都沒有。但是一旦我們利用HTTP-GET獲取元數據(WSDL)的時候,會發生如下所示的NullReferenceException異常。
由於自定義的這個MyServiceHost的代碼實在太簡單,我實在想不到那個地方導致WsdlExporter的CreateWsdlOperationFault方法(根據Stacktrace,這個異常是從這個方法中拋出來的)。沒有辦法,隻有看WCF的源代碼了,這個過程是很痛苦的,因為涉及的代碼太多,而且根本不知道這個Null Reference究竟是哪個變量。
既然查看源代碼並沒有真正解決這個問題,我們還得從自定義的這個MyServiceHost上找原因。這個MyServiceHost的作用簡單明了,就是為所有的操作添加一個針對ServiceExceptionInfo類型的FaultDescription對象而已,那麼是不是因為添加的FaultDescription對象缺少了某些屬性導致的這個異常呢?為此,我將FaultDescription的所有屬性都進行了設置,最終發現隻要按照如下的方式設置它的Name屬性就可以了。
1: public class MyServiceHost: ServiceHost
2: {
3: public MyServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
4: { }
5:
6: protected override void OnOpening()
7: {
8: base.OnOpening();
9: foreach (var endpoint in this.Description.Endpoints)
10: {
11: string ns = endpoint.Contract.Namespace.TrimEnd('/');
12: foreach (var op in endpoint.Contract.Operations)
13: {
14: if (!op.Faults.Any(it => it.DetailType == typeof(ServiceExceptionInfo)))
15: {
16: FaultDescription fault = new FaultDescription($"{ns}/{op.Name}_ServiceExceptionInfo");
17:
18: fault.DetailType = typeof(ServiceExceptionInfo);
19: op.Faults.Add(fault);
20: }
21: }
22: }
23: }
24: }
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-25 11:05:02
上一篇:
如何利用ETW(Event Tracing for Windows)記錄日誌
下一篇:
.NET Core的文件係統[5]:擴展文件係統構建一個簡易版“雲盤”
如何利用數據分析提升用戶留存率?
數字電視標準綜述(1)
pythonchallenge_level12
最新阿裏雲優惠券2017在哪裏領取,如何使用?
看大牛如何複盤遞歸神經網絡!
C++編程規範之31:不要編寫依賴於函數參數求值順序的代碼
增強AW_Blog插件之後台文章編輯新增一圖片字段
Android TextView中文字通過SpannableString來設置超鏈接、顏色、字體等屬性
用ExpandableListView實現類似QQ好友列表
java.lang.ClassCastException:org.apache.catalina.util.DefaultAnnotationProcessor cannot be cast to org.apache.AnnotationProcesso