在上篇中,我详细介绍了如何通过自定义ClientMessageInspector和ErrorHandler,实现WCF与微软企业库中的Exception Handling Application Block(EHAB)之间的集成。这个方案的基本思路就是:当异常从服务端抛出,利用EHAB针对某个配置好的异常处理策略进行处理;然后将处理有的异常通过ServiceExceptionDetail对象进行封装,最后序列化置于Fault消息,最终被返回给客户端;客户端接收到该Fault消息后,提取并创建ServiceExceptionDetail对象,并通过反射重建异常;最后将异常抛出,使客户端可以根据客户端配置的异常处理策略对该异常进行进一步的处理。(Source Code从这里下载)
为了实现WCF对ServiceExceptionDetail对象的序列化和反序列化,我们必须通过FaultContractAttribute特性将类型定义成错误契约,相应的形式如下面的代码所示。在一般的情况下,如果你定义的服务是为他人所用,比如第三方服务消费者,该错误契约的定义是必须的,因为相应的错误明细类型需要通过元数据的形式发布出来,指导客户端如何对接收到的消息进行反序列化。但是,如果服务仅供你自己的应用所用,那么你可以在运行时动态地添加相应的错误描述,从而避免在服务契约的每一个服务操作方法上应用这么一个FaultContractAttribute。
1: [ServiceContract(Namespace = "http://www.artech.com/")]
2: public interface ICalculator
3: {
4: [OperationContract]
5: [ExceptionHandlingBehavior("myExceptionPolicy")]
6: [FaultContract(typeof(ServiceExceptionDetail), Action = "http://www.artech.com/fault")]
7: int Divide(int x, int y);
8: }
我们应用在操作方法上面的FaultContractAttribute特性,最终会转换成操作描述(OperationDescription)的错误描述(FaultDescription),如果我们在运行时能够为所有的操作描述添加相应的错误描述,就能避免在每个服务操作上面应用相同的FaultContractAttribute特性。不过,为了服务的重用,我不介意这样偷懒,所以这种方案仅仅作为研究、学习之用。
一、通过自定义ServiceHost的方式动态添加错误描述(服务端)
首先需要在服务端为每一个服务操作添加基于ServiceExceptionDetail的错误描述,这可以通过自定ServiceHost来实现。由于服务描述需要在ServiceHost开启之前生成方才有效(具体的原因,相对比较复杂,大家可以在《WCF技术剖析(卷1)》第7章关于服务寄宿的部分找到答案),所以我们将相关的逻辑定义在OnOpening方法之中。在下面的代码中,我定义了这样一个简单的ServiceHost:ExceptionHandlingServiceHost。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Activation;
4: using System.ServiceModel.Description;
5:
6: namespace Artech.EnterLibIntegration.WcfExtensions
7: {
8: public class ExceptionHandlingServiceHost : ServiceHost
9: {
10: public ExceptionHandlingServiceHost(Type t, params Uri[] baseAddresses)
11: : base(t, baseAddresses)
12: { }
13:
14: protected override void OnOpening()
15: {
16: base.OnOpening();
17: foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
18: {
19: foreach (OperationDescription operation in endpoint.Contract.Operations)
20: {
21: FaultDescription faultDescription = new FaultDescription(ServiceExceptionDetail.FaultAction);
22: faultDescription.DetailType = typeof(ServiceExceptionDetail);
23: operation.Faults.Add(faultDescription);
24: }
25: }
26: }
27: }
28: }
逻辑相对比较简单:遍历所有终结点(ServiceEndpoint),为每一个终结点的契约(ContractDescription)的每一个操作(OperationDescription)添加错误明细类型为ServiceExceptionDetail的错误描述(FaultDescription),并指定预定义的Action。
对于自定义的ServiceHost,可以直接用于不需要.svc文件进行访问的寄宿场景,也就是说对于除了IIS和WAS的服务寄宿,可以直接采用自定义的ServiceHost。服务需要在基于IIS和WAS的寄宿方式中采用自定义的ServiceHost,还需要为之创建相应的ServiceHostFactory(关于ServiceHostFactory作用和用法,同样可以参阅《WCF技术剖析(卷1)》第7章)。下面,我们为ExceptionHandlingServiceHost定义了一个简单的ServiceHostFactory:ExceptionHandlingServiceHostFactory。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Activation;
4: using System.ServiceModel.Description;
5:
6: namespace Artech.EnterLibIntegration.WcfExtensions
7: {
8: public class ExceptionHandlingServiceHostFactory : ServiceHostFactory
9: {
10: protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
11: {
12: return new ExceptionHandlingServiceHost(serviceType, baseAddresses);
13: }
14: }
15: }
二、通过自定义ChannelFactory<TChanel>的方式动态添加错误描述(客户端)
服务端需要为每一个操作添加基于ServiceExceptionDetail的错误描述,以便实现对该对象的序列化;同理,客户端同样需要这样一个错误描述,以实现对该对象的反序列化。我们可以将这样的功能通过一个自定义ChannelFactory<TChannel>来实现。下面定义的ExceptionHandlingChannelFactory就是这样一个自定的ChannelFactory<TChannel>。对错误描述的添加实现在重写的CreateDescription方法中:
1: using System.ServiceModel;
2: using System.ServiceModel.Description;
3:
4: namespace Artech.EnterLibIntegration.WcfExtensions
5: {
6: public class ExceptionHandlingChannelFactory<TChannel>:ChannelFactory<TChannel>
7: {
8: public ExceptionHandlingChannelFactory(string endpointConfigurationName)
9: : base(endpointConfigurationName)
10: { }
11:
12: protected override ServiceEndpoint CreateDescription()
13: {
14: ServiceEndpoint serviceEndpoint = base.CreateDescription();
15: foreach (OperationDescription operation in serviceEndpoint.Contract.Operations)
16: {
17: FaultDescription faultDescription = new FaultDescription(ServiceExceptionDetail.FaultAction);
18: faultDescription.DetailType = typeof(ServiceExceptionDetail);
19: operation.Faults.Add(faultDescription);
20: }
21:
22: return serviceEndpoint;
23: }
24: }
25: }
三、实例演示
那么,对其我们给出的例子,我们就要使用我们上面创建的这两个组件了。首先,有了这两个组件的帮助,在服务契约中,我们再也不需要在繁琐地为每一个服务操作定义相同的FaultContractAttribute特性了。于是我们先将其拿掉。
1: [ServiceContract(Namespace = "http://www.artech.com/")]
2: public interface ICalculator
3: {
4: [OperationContract]
5: [ExceptionHandlingBehavior("myExceptionPolicy")]
6: int Divide(int x, int y);
7: }
然后,再进行服务寄宿的时候,直接利用我们定义的ExceptionHandlingServiceHost就可以了。
1: using System;
2: using Artech.EnterLibIntegration.WcfExtensions;
3: using Artech.WcfServices.Services;
4: namespace Artech.WcfServices.Hosting
5: {
6: public class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ExceptionHandlingServiceHost host = new ExceptionHandlingServiceHost(typeof(CalculatorService)))
11: {
12:
13: host.Open();
14: Console.Read();
15: }
16: }
17: }
18: }
而客户端,我们可以借助于我们定义的ExceptionHandlingChannelFactory<TChannel>实现对服务代理的创建。
1: using System;
2: using Artech.EnterLibIntegration.WcfExtensions;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Clients
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ExceptionHandlingChannelFactory<ICalculator> channelFactory = new ExceptionHandlingChannelFactory<ICalculator>(
11: "calculatorservice"))
12: {
13: ICalculator calculator = channelFactory.CreateChannel();
14: using (calculator as IDisposable)
15: {
16: try
17: {
18: int result = calculator.Divide(1, 0);
19: }
20: catch (CalculationException ex)
21: {
22: Console.WriteLine(ex.Message);
23: Console.WriteLine("InnerException");
24: Console.WriteLine("\tType:{0}", ex.InnerException.GetType());
25: Console.WriteLine("\tMessage:{0}", ex.InnerException.Message);
26: }
27: }
28: }
29:
30: Console.Read();
31: }
32: }
33: }
这样我们同样可以得到与上篇一样的执行结果:
计算错误
InnerException
Type:System.DivideByZeroException
Message:试图除以零.