Enterprise Library深入解析与灵活应用(8):WCF与Exception Handling AppBlock集成[上]

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

在《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 = "http://www.artech.com/")]
   7:     public class ServiceExceptionDetail : ExceptionDetail
   8:     {
   9:         public const string FaultSubCodeNamespace = "http://www.artech.com/exceptionhandling/";
  10:         public const string FaultSubCodeName = "ServiceError";
  11:         public const string FaultAction = "http://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: }

clip_image002

图1 WCF与EHAB集成实现原理

注:有人会觉得这和开启了IncludeExceptionDetailInFaults开关的ServiceDebugBehavior服务行为一样,异常信息会完全暴露给客户端,会存在敏感信息泄露的危险。实际上,如果你将敏感信息屏蔽的操作定义在相关的异常处理策略中,并通过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 = "http://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="http://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 = "http://www.artech.com/")]
   6:     public interface ICalculator
   7:     {
   8:         [OperationContract]
   9:         [ExceptionHandlingBehavior("myExceptionPolicy")]
  10:         [FaultContract(typeof(ServiceExceptionDetail), Action = "http://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="http://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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
RS-485网络中的标准端接与交流电端接应用解析
RS-485,作为一种广泛应用的差分信号传输标准,因其传输距离远、抗干扰能力强、支持多点通讯等优点,在工业自动化、智能建筑、交通运输等领域得到了广泛应用。在构建RS-485网络时,端接技术扮演着至关重要的角色,它直接影响到网络的信号完整性、稳定性和通信质量。
|
17天前
|
机器学习/深度学习 人工智能 自然语言处理
思通数科AI平台在尽职调查中的技术解析与应用
思通数科AI多模态能力平台结合OCR、NLP和深度学习技术,为IPO尽职调查、融资等重要交易环节提供智能化解决方案。平台自动识别、提取并分类海量文档,实现高效数据核验与合规性检查,显著提升审查速度和精准度,同时保障敏感信息管理和数据安全。
71 11
|
13天前
|
自然语言处理 并行计算 数据可视化
免费开源法律文档比对工具:技术解析与应用
这款免费开源的法律文档比对工具,利用先进的文本分析和自然语言处理技术,实现高效、精准的文档比对。核心功能包括文本差异检测、多格式支持、语义分析、批量处理及用户友好的可视化界面,广泛适用于法律行业的各类场景。
|
15天前
|
安全 编译器 PHP
PHP 8新特性解析与实践应用####
————探索PHP 8的创新功能及其在现代Web开发中的实际应用
|
17天前
|
机器学习/深度学习 人工智能 自然语言处理
医疗行业的语音识别技术解析:AI多模态能力平台的应用与架构
AI多模态能力平台通过语音识别技术,实现实时转录医患对话,自动生成结构化数据,提高医疗效率。平台具备强大的环境降噪、语音分离及自然语言处理能力,支持与医院系统无缝集成,广泛应用于门诊记录、多学科会诊和急诊场景,显著提升工作效率和数据准确性。
|
7天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
7天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
15 0
|
11天前
|
SQL 监控 安全
员工上网行为监控软件:SQL 在数据查询监控中的应用解析
在数字化办公环境中,员工上网行为监控软件对企业网络安全和管理至关重要。通过 SQL 查询和分析数据库中的数据,企业可以精准了解员工的上网行为,包括基础查询、复杂条件查询、数据统计与分析等,从而提高网络管理和安全防护的效率。
23 0
|
14天前
|
前端开发 中间件 PHP
PHP框架深度解析:Laravel的魔力与实战应用####
【10月更文挑战第31天】 本文作为一篇技术深度好文,旨在揭开PHP领域璀璨明星——Laravel框架的神秘面纱。不同于常规摘要的概括性介绍,本文将直接以一段引人入胜的技术剖析开场,随后通过具体代码示例和实战案例,逐步引导读者领略Laravel在简化开发流程、提升代码质量及促进团队协作方面的卓越能力。无论你是PHP初学者渴望深入了解现代开发范式,还是经验丰富的开发者寻求优化项目架构的灵感,本文都将为你提供宝贵的见解与实践指导。 ####
|
17天前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
34 0

推荐镜像

更多