Enterprise Library深入解析與靈活應用(8):WCF與Exception Handling AppBlock集成[上]
在《WCF技術剖析(卷1)》的最後一章,我給出了一個具體的應用WCF的分布式應用實例,我把這個實例命名為PetShop。在這個例子中,我利用WCF的擴展實現了一些設計、架構模式,比如AOP、IoC等。看過本書的讀者,一定還記得我還通過WCF擴展實現了於微軟企業庫(Enterprise Library)異常處理應用塊(Exception Handling Application Block:EHAB)的集成。當時由於缺乏相應的背景知識,不可能介紹具體的實現,現在我們可以詳細來講述這是如何實現的。 (Source Code從這裏下載)
一、 基本原理介紹
在一個基於WCF的分布式應用中,服務端和客戶端需要進行單獨的異常處理。在服務端,讓EHAB處理拋出的異常是很容易的,我們隻需要按照上麵代碼所示的方式調用ExcpetionPolicy的HandleException方法,傳入拋出的異常並指定相應的異常處理策略名稱即可。關鍵的是如何實現讓EHAB處理客戶端進行服務調用拋出的異常。
我們知道,客戶端進行 服務調用拋出的異常類型總是FaultException(包括FaultException<TDetail>)。而EHAB采用的是完全基於異常類型的異常,即拋出的異常類型決定了異常處理方式。也就是說,即使兩種完全不同的出錯場景,隻要拋出的異常具有相同的類型,EHAB就會采用相同的方式來處理該異常。采用這樣的方式來直接處理調用WCF服務拋出的異常,顯然具有很大的局限:如果服務不錯任何處理,客戶端捕獲的永遠是FaultException(不包括FaultException<TDetail>)異常,如果采用EHAB的話,意味著隻有一種唯一異常處理方式。當然,在服務端的操作實現中你可以根據具體的場景拋出FaultException<TDetail>異常,並通過不同類型的錯誤明細(TDetail)封裝具體的錯誤信息,那麼客戶端就可以針對具體的FaultException<TDetail>異常類型選擇不同的方式進行處理。這樣使你的異常處理方式是真正的場景驅動的。
理論上來講,我們需要的正是這種方式的異常處理方式。但是在快速開發中,這樣的方式不太具有可操作性,因為異常的一個本質屬性就是具有不可預測性。對於某項服務操作,不太可能羅列出所有的錯誤場景並拋出相應類型的異常。這也正是我們需要一種像EHAB這樣一種可配置的異常處理框架的原因,這樣我們才能夠通過修改相應的配置為某個我們之前沒有預知的異常定義相應的異常處理策略。
我們接下來的介紹的解決方案通過一種變通的方式解決了上麵的問題,這種方式與通過ServiceDebugBehavior實現異常的傳播有點類似:服務端拋出的異常先通過EHAB按照配置好的異常處理策略進行相應的處理。然後將處理後的異常相關的信息(包括異常類型的AssemblyQualifiedName)封裝到一個類似於ExceptionDetail的可序列化對象中。最後,以該對象為基礎創建MessageFault,並進一步生成Fault消息傳回客戶端;客戶端在接收到該Fault消息後,提取服務端異常相關的信息利用反射重建異常對象(已經明確了異常類型的AssemblyQualifiedName使異常對象的重建變成可能)比將其拋出。那麼對於客戶端的應用程序來說,就像是捕獲從服務端拋出的異常一樣了。通過EHAB針對客戶端配置的異常處理策略對拋出的異常進行處理,那麼這種異常處理方式依然是場景驅動的。
在本例中,我們通過如下一個名稱為ServiceExceptionDetail的類型來封裝異常相關信息。為了簡單起見,我直接讓ServiceExceptionDetail繼承自ExceptionDetail。由於ServiceExceptionDetail對象需要從服務端向客戶端傳遞,我將其定義成數據契約。在ServiceExceptionDetail僅僅定義了一個唯一的屬性成員:AssemblyQualifiedName,表示異常的類型的程序集有效名稱,這是為了基於反射的異常重建的需要。在ServiceExceptionDetail中,定義了3個字符串常量表示對應SOAP Fault的SubCode名稱和命名空間,以及對應Fault消息的Action。整個解決方法實現的原理大體上可以通過圖1示。
1: using System;
2: using System.Runtime.Serialization;
3: using System.ServiceModel;
4: namespace Artech.EnterLibIntegration.WcfExtensions
5: {
6: [DataContract(Namespace = "https://www.artech.com/")]
7: public class ServiceExceptionDetail : ExceptionDetail
8: {
9: public const string FaultSubCodeNamespace = "https://www.artech.com/exceptionhandling/";
10: public const string FaultSubCodeName = "ServiceError";
11: public const string FaultAction = "https://www.artech.com/fault";
12:
13: [DataMember]
14: public string AssemblyQualifiedName
15: { get; private set; }
16:
17: [DataMember]
18: public new ServiceExceptionDetail InnerException
19: { get; private set; }
20:
21: public ServiceExceptionDetail(Exception ex)
22: : base(ex)
23: {
24: this.AssemblyQualifiedName = ex.GetType().AssemblyQualifiedName;
25: if (null != ex.InnerException)
26: {
27: this.InnerException = new ServiceExceptionDetail(ex.InnerException);
28: }
29: }
30:
31: public override string ToString()
32: {
33: return this.Message;
34: }
35: }
36: }
圖1 WCF與EHAB集成實現原理
二、異常處理、封裝與重建
從上麵給出的整個解決方案實現原理介紹中,我們可以看出,這個結構體係需要解決如下三個功能:
- 通過EHAB處理服務端拋出的原始異常(XxxException):利用EHAB針對預定義的異常處理策略對服務操作拋出的異常進行處理;
- 通過MessageFault封裝EHAB處理後的異常(YyyException):創建ServiceExceptionDetail對象封裝通過EHAB處理後的異常,進而創建MessageFault對象,最終創建Fault消息將異常相關的信息向客戶端傳遞;
- 客戶端實現異常的重建(YyyException):客戶端接收到Fault消息後,提取異常相關信息,重建異常對象,使得客戶端可以利用EHAB針對基於客戶端的異常處理策略對其進行相應的處理。
在本例中,我們通過兩個重要的WCF組件實現對以上3個功能的實現,其中前兩個通過自定義的ErrorHandler實現,最後一個通過MessageInspector實現。我們現在就來介紹這兩個組件的實現方式。
1、自定義ErrorHandler實現基於EHAB的異常處理和封裝
為了實現利用EHAB自動處理服務操作拋出的異常,已經對處理後異常的封裝和傳遞,我定義了如下一個自定義的ErrorHandler:ServiceErrorHandler。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Channels;
4: using System.ServiceModel.Dispatcher;
5: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
6: namespace Artech.EnterLibIntegration.WcfExtensions
7: {
8: public class ServiceErrorHandler : IErrorHandler
9: {
10: public string ExceptionPolicyName
11: { get; private set; }
12: public ServiceErrorHandler(string exceptionPolicyName)
13: {
14: if (string.IsNullOrEmpty(exceptionPolicyName))
15: {
16: throw new ArgumentNullException("exceptionPolicyName");
17: }
18: this.ExceptionPolicyName = exceptionPolicyName;
19: }
20:
21: #region IErrorHandler Members
22: public bool HandleError(Exception error)
23: {
24: return false;
25: }
26: public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
27: {
28: if(typeof(FaultException).IsInstanceOfType(error))
29: {
30: return;
31: }
32:
33: try
34: {
35: if (ExceptionPolicy.HandleException(error, this.ExceptionPolicyName))
36: {
37: fault = Message.CreateMessage(version, BuildFault(error), ServiceExceptionDetail.FaultAction);
38: }
39: }
40: catch (Exception ex)
41: {
42: fault = Message.CreateMessage(version, BuildFault(ex), ServiceExceptionDetail.FaultAction);
43: }
44: }
45:
46: private MessageFault BuildFault(Exception error)
47: {
48: ServiceExceptionDetail exceptionDetail = new ServiceExceptionDetail(error);
49: return MessageFault.CreateFault(FaultCode.CreateReceiverFaultCode(ServiceExceptionDetail.FaultSubCodeName, ServiceExceptionDetail.FaultSubCodeNamespace),
50: new FaultReason(error.Message), exceptionDetail);
51: }
52:
53: #endregion
54: }
55: }
在ServiceErrorHandler中定義的隻讀屬性ExceptionPolicyName表示服務端配置的異常處理策略的名稱,該屬性在構造函數中指定。在ProvideFault方法中,先判斷拋出的異常是否是FaultException,如果是則不作處理(在這種情況下,一般是服務提供者人為拋出的,並不希望再作進一步的處理)。否則調用ExceptionPolicy的HandleException方法,傳入異常處理策略名稱,對該異常進行處理。對於處理後的異常,通過BuildFault方法創建ServiceExceptionDetail對象對異常信息進行封裝,並最終生成Fault消息。
2、自定義MessageInspector實現異常的重建
當封裝有異常信息的Fault消息返回到客戶端後,需要將異常信息提取出來並通過反射重建並拋出異常對象,我們通過自定義MessageInspector來實現這樣的功能。為此,我們定義了如下一個實現了IClientMessageInspector接口的類型:ExceptionHandlingMessageInspector。
1: using System.ServiceModel;
2: using System.ServiceModel.Channels;
3: using System.ServiceModel.Dispatcher;
4: using System;
5: namespace Artech.EnterLibIntegration.WcfExtensions
6: {
7: public class ExceptionHandlingMessageInspector : IClientMessageInspector
8: {
9: public void AfterReceiveReply(ref Message reply, object correlationState)
10: {
11: if (!reply.IsFault)
12: {
13: return;
14: }
15:
16: if (reply.Headers.Action == ServiceExceptionDetail.FaultAction)
17: {
18: MessageFault fault = MessageFault.CreateFault(reply, int.MaxValue);
19: if(fault.Code.SubCode.Name == ServiceExceptionDetail.FaultSubCodeName &&
20: fault.Code.SubCode.Namespace == ServiceExceptionDetail.FaultSubCodeNamespace)
21: {
22: FaultException<ServiceExceptionDetail> exception = (FaultException<ServiceExceptionDetail>)FaultException.CreateFault(fault, typeof(ServiceExceptionDetail));
23: throw GetException(exception.Detail);
24: }
25: }
26: }
27:
28: private Exception GetException(ServiceExceptionDetail exceptionDetail)
29: {
30: if (null == exceptionDetail.InnerException)
31: {
32: return (Exception)Activator.CreateInstance(Type.GetType(exceptionDetail.AssemblyQualifiedName), exceptionDetail.Message);
33: }
34:
35: Exception innerException = GetException(exceptionDetail.InnerException);
36: return (Exception)Activator.CreateInstance(Type.GetType(exceptionDetail.AssemblyQualifiedName), exceptionDetail.Message, innerException);
37: }
38:
39: public object BeforeSendRequest(ref Message request, IClientChannel channel)
40: {
41: return null;
42: }
43: }
44: }
在AfterReceiveReply方法中,通過比較Fault消息的Action,以及SubCode的名稱和命名空間確定接收到的消息正是服務端通過我們自定義的ServiceErrorHandler創建。然後從中提取出封裝了異常信息的ServiceExceptionDetail對象,通過反射的方式重新創建異常對象。
注:在創建異常對象的時候,默認調用的是參數列表是String(Message)和Exception(InnerException)類型的公共構造函數,基本上絕大部分異常類型都具有這樣的構造函數。如果某個異常不具有這樣的構造函數簽名,一般意味著並不希望異常對象從外部創建。比較典型的屬SqlException,由於這樣的異常隻能通過System.Data.SqlClient數據存取提供者(Data Access Provier)創建,所以並不具有我們希望的構造函數。對於這種情況,在服務端必須利用替換機製將其替換成另一個可以創建的異常類型(比如將SqlException替換成自定義的DbException)。
3、通過行為應用自定義ErrorHandler和MessageInspector
我們上麵創建的自定義ErrorHandler(ServiceErrorHandler)和MessageInspector(ExceptionHandlingMessageInspector),最終通過相應的WCF行為將它們分別應用到WCF服務端和客戶端運行時。我們可以采用4種行為(操作行為、契約行為、終結點行為和服務行為)中的任何一種來實現,他們之間唯一的不同就是應用的方式(自定義特性或者配置)和作用範圍不同。為此,我們創建了一個行為類型:ExceptionHandlingBehaviorAttribute:
1: using System;
2: using System.Collections.ObjectModel;
3: using System.ServiceModel;
4: using System.ServiceModel.Channels;
5: using System.ServiceModel.Description;
6: using System.ServiceModel.Dispatcher;
7: namespace Artech.EnterLibIntegration.WcfExtensions
8: {
9: public class ExceptionHandlingBehaviorAttribute:Attribute,IOperationBehavior,IContractBehavior,IEndpointBehavior,IServiceBehavior
10: {
11: public string ExceptionPolicyName
12: { get; private set; }
13:
14: public ExceptionHandlingBehaviorAttribute(string exceptionPolicyName)
15: {
16: if (string.IsNullOrEmpty(exceptionPolicyName))
17: {
18: throw new ArgumentNullException("exceptionPolicyName");
19: }
20: this.ExceptionPolicyName = exceptionPolicyName;
21: }
22:
23: #region IOperationBehavior Members
24: public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {}
25: public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
26: {
27: clientOperation.Parent.MessageInspectors.Add(new ExceptionHandlingMessageInspector());
28: }
29: public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
30: {
31: dispatchOperation.Parent.ChannelDispatcher.ErrorHandlers.Add(new ServiceErrorHandler(this.ExceptionPolicyName));
32: dispatchOperation.Parent.ChannelDispatcher.ErrorHandlers.Add(new ServiceErrorHandler(this.ExceptionPolicyName));
33: }
34: public void Validate(OperationDescription operationDescription) {}
35: #endregion
36:
37: #region IEndpointBehavior Members
38: public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
39: {}
40: public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
41: {
42: clientRuntime.MessageInspectors.Add(new ExceptionHandlingMessageInspector());
43: }
44: public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {}
45: public void Validate(ServiceEndpoint endpoint) {}
46: #endregion
47:
48: #region IServiceBehavior Members
49: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
50: {}
51: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
52: {
53: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
54: {
55: channelDispatcher.ErrorHandlers.Add(new ServiceErrorHandler(this.ExceptionPolicyName));
56: }
57: }
58: public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase){}
59: #endregion
60:
61: #region IContractBehavior Members
62: public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}
63: public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
64: {
65: clientRuntime.MessageInspectors.Add(new ExceptionHandlingMessageInspector());
66: }
67:
68: public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
69: {
70: dispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(new ServiceErrorHandler(this.ExceptionPolicyName));
71: }
72: public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
73: {}
74: #endregion
75: }
76: }
在ApplyClientBehavior方法中,創建自定義的ExceptionHandlingMessageInspector對象,並將其加入ClientRuntime的MessageInspector列表中;在ApplyDispatchBehavior中,創建自定義的ServiceErrorHandler對象,並將其加入到ChannelDispatcher的ErrorHandler列表中。由於ExceptionHandlingBehaviorAttribute既是操作行為,又是契約行為和服務行為,同時又是一個自定義特性,所以我們可以直接通過特性的方式將其應用到操作方法、契約接口或者類型和服務類型上麵。在下麵的代碼中,我們將其應用到服務契約的Divide操作上麵:
1: using System.ServiceModel;
2: using Artech.EnterLibIntegration.WcfExtensions;
3: namespace Artech.WcfServices.Contracts
4: {
5: [ServiceContract(Namespace = "https://www.artech.com/")]
6: public interface ICalculator
7: {
8: [OperationContract]
9: [ExceptionHandlingBehavior("myExceptionPolicy")]
10: int Divide(int x, int y);
11: }
12: }
同時作為服務行為和終結點行為,我們又具有另外一種服務應用的方式:配置。如果要實現通過配置方式應用該行為,我們還需要定義對應的繼承自System.ServiceModel.Configuration.BehaviorExtensionElement類型的配置元素(Configuraiton Element)。為此,定義了如下一個類型:ExceptionHandlingBehaviorElement。
1: using System;
2: using System;
3: using System.Configuration;
4: using System.ServiceModel.Configuration;
5: namespace Artech.EnterLibIntegration.WcfExtensions
6: {
7: public class ExceptionHandlingBehaviorElement:BehaviorExtensionElement
8: {
9: [ConfigurationProperty("exceptionPolicy")]
10: public string ExceptionPolicy
11: {
12: get
13: {return this["exceptionPolicy"] as string;}
14: set
15: { this["exceptionPolicy"] = value; }
16: }
17: public override Type BehaviorType
18: {
19: get { return typeof(ExceptionHandlingBehaviorAttribute); }
20: }
21: protected override object CreateBehavior()
22: {
23: return new ExceptionHandlingBehaviorAttribute(this.ExceptionPolicy);
24: }
25: }
26: }
這樣,我們就可以通過配置的方式將其應用到某個服務(作為服務行為)或者終結點(作為終結點行為)上了。在下麵的配置中,我將此行為應用到CalculatorService服務上麵。
1: <?xml version="1.0" encoding="utf-8"?>
2: <configuration>
3: <system.serviceModel>
4: <behaviors>
5: <serviceBehaviors>
6: <behavior name="exceptionHandling">
7: <exceptionHandling exceptionPolicy="myExceptionPolicy" />
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <extensions>
12: <behaviorExtensions>
13: <add name="exceptionHandling" type="Artech.EnterLibIntegration.WcfExtensions.ExceptionHandlingBehaviorElement, Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
14: </behaviorExtensions>
15: </extensions>
16: <services>
17: <service behaviorConfiguration="exceptionHandling" name="Artech.WcfServices.Services.CalculatorService">
18: <endpoint address="https://127.0.0.1:3721/calculatorservice" binding="ws2007HttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
19: </service>
20: </services>
21: </system.serviceModel>
22: </configuration>
三、 實例演示
接下來我們我們將上麵我們定義的行為應用到真正的實例之中,看看它們是否會按照我們之前希望的方式進行異常的處理。簡單起見,我們還是用我們熟悉的計算服務的例子。現在,我們將ExceptionHandlingBehaviorAttribute作為操作行為應用到服務契約接口ICalculator的Divide操作方法上,並指明異常處理策略名稱(myExceptionPolicy)。
1: using System.ServiceModel;
2: using Artech.EnterLibIntegration.WcfExtensions;
3: namespace Artech.WcfServices.Contracts
4: {
5: [ServiceContract(Namespace = "https://www.artech.com/")]
6: public interface ICalculator
7: {
8: [OperationContract]
9: [ExceptionHandlingBehavior("myExceptionPolicy")]
10: [FaultContract(typeof(ServiceExceptionDetail), Action = "https://www.artech.com/fault")]
11: int Divide(int x, int y);
12: }
13: }
細心的讀者可能注意到了:在Divide操作上麵還同時應用了FaultContractAttribute特性,並將ServiceExceptionDetail類型作為錯誤明細類型。這樣做的目的在於:用於封裝異常信息的ServiceExceptionDetail類型必須作為錯誤契約,其對象才能被FaultFormatter序列化和反序列化。所以,將ServiceExceptionDetail作為錯誤契約時必須的。下麵是服務類型的代碼:
1: using System.ServiceModel;
2: using Artech.WcfServices.Contracts;
3: namespace Artech.WcfServices.Services
4: {
5: [ServiceBehavior(Namespace="https://www.artech.com/")]
6: public class CalculatorService : ICalculator
7: {
8: public int Divide(int x, int y)
9: {
10: return x / y;
11: }
12: }
13: }
接下來,我們利用微軟企業庫提供的配置工具(Configuration Console)定義如下的異常處理策略,並命名為在ExceptionHandlingBehaviorAttribute指定的名稱:myExceptionPolicy。此策略專門針對在Divide操作中會跑出的DivideByZeroException異常類型。
1: <?xml version="1.0" encoding="utf-8"?>
2: <configuration>
3: <configSections>
4: <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
5: </configSections>
6: <exceptionHandling>
7: <exceptionPolicies>
8: <add name="myExceptionPolicy">
9: <exceptionTypes>
10: <add type="System.DivideByZeroException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
11: postHandlingAction="ThrowNewException" name="DivideByZeroException">
12: <exceptionHandlers>
13: <add exceptionMessage="計算錯誤"
14: wrapExceptionType="Artech.WcfServices.Contracts.CalculationException,Artech.WcfServices.Contracts" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
15: name="Wrap Handler" />
16: </exceptionHandlers>
17: </add>
18: </exceptionTypes>
19: </add>
20: </exceptionPolicies>
21: </exceptionHandling>
22: </configuration>
該異常策略定義非常簡單,僅僅是將DivideByZeroException異常封裝成我們自定義的CalculationException異常(封裝後,原來的DivideByZeroException異常將會作為CalculationException異常的InnerException),並指定異常消息("計算錯誤")。CalculationException僅僅是一個普通的自定義異常:
1: using System;
2: namespace Artech.WcfServices.Contracts
3: {
4: [global::System.Serializable]
5: public class CalculationException : Exception
6: {
7: public CalculationException() { }
8: public CalculationException(string message) : base(message) { }
9: public CalculationException(string message, Exception inner) : base(message, inner) { }
10: protected CalculationException(
11: System.Runtime.Serialization.SerializationInfo info,
12: System.Runtime.Serialization.StreamingContext context)
13: : base(info, context) { }
14: }
15: }
最後,下麵是客戶端的代碼,運行我們的應用程序,客戶端將會得到如下的輸出。
1: using System;
2: using System.ServiceModel;
3: using Artech.WcfServices.Contracts;
4: using Artech.EnterLibIntegration.WcfExtensions;
5: namespace Artech.WcfServices.Clients
6: {
7: class Program
8: {
9: static void Main(string[] args)
10: {
11: using (ExceptionHandlingChannelFactory<ICalculator> channelFactory = new ExceptionHandlingChannelFactory<ICalculator>(
12: "calculatorservice"))
13: {
14: ICalculator calculator = channelFactory.CreateChannel();
15: using (calculator as IDisposable)
16: {
17: try
18: {
19: int result = calculator.Divide(1, 0);
20: }
21: catch (CalculationException ex)
22: {
23: Console.WriteLine(ex.Message);
24: Console.WriteLine("InnerException");
25: Console.WriteLine("\tType:{0}", ex.InnerException.GetType());
26: Console.WriteLine("\tMessage:{0}", ex.InnerException.Message);
27: }
28: }
29: }
30:
31: Console.Read();
32: }
33: }
輸出結果:
計算錯誤
InnerException
Type:System.DivideByZeroException
Message:試圖除以零.
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-30 11:04:57
上一篇:
國民基金餘額寶對企業研發效能的探索與思考
下一篇:
Enterprise Library深入解析與靈活應用(8):WCF與Exception Handling AppBlock集成[下]
Unexpected XML declaration. The XML declaration must be the first node in the document and no white
AliCDN,盛開在雲端的花朵
dr.com導致Eclipse調試錯誤
8月9日雲棲精選夜讀:大數據時代,如何構建國家地質基礎數據更新體係
《HttpClient官方文檔》第六章 HTTP 緩存
8月16日雲棲精選夜讀:如何快速掌握阿裏巴巴內部高效測試流程?
香港服務器租用現狀
線程間共享數據
《Servlet、JSP和Spring MVC初學指南》——1.5 ServletResponse
破解世界性技術難題! GTS讓分布式事務簡單高效