zoukankan      html  css  js  c++  java
  • [WCF]缺少一行代码引发的血案

    这是今天作项目支持的发现的一个关于WCF的问题,虽然最终我只是添加了一行代码就解决了这个问题,但是整个纠错过程是痛苦的,甚至最终发现这个问题都具有偶然性。具体来说,这是一个关于如何自动为服务接口(契约)的每个操作添加FaultContract与WCF服务元数据发布的问题。接下来通过一个简单的实例来说明这个因为少写了一行代码引发的血案。

    一、手工添加FaultContract

    WCF采用基于消息的通信方式,Endpoint的ABC三要素之一的契约(Contract)的本质就是定义消息的结构。契约不仅定义了正常请求和响应负载的结构,还定义了承载异常信息的响应消息的结构。为了让契约能够响应消息承载的错误信息,承载错误信息的类型需要利用FaultContractAttribute特性注册到服务接口的操作方法上。

       1: [ServiceContract]
       2: public interface IMyService
       3: {
       4:     [OperationContract]
       5:     [FaultContract(typeof(ServiceExceptionInfo))]
       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方法上。

    二、利用自定义ServiceHost自动注册ServiceExceptionInfo类型

    如果多个操作都需要注册这么一个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>

    三、获取元数据(WSDL)受阻

    在真的WCF服务调用过程中,我们定义的这个MyServiceHost和MyServiceHostFactory一点问题都没有。但是一旦我们利用HTTP-GET获取元数据(WSDL)的时候,会发生如下所示的NullReferenceException异常。

    image

    四、一行代码解决这个问题

    由于自定义的这个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:                     fault.Name = "ServiceExceptionInfoFault";
      18:                     fault.DetailType = typeof(ServiceExceptionInfo);
      19:                     op.Faults.Add(fault);
      20:                 }
      21:             }
      22:         }
      23:     }
      24: }

    image

  • 相关阅读:
    O(1)时间复杂度实现入栈、出栈、获得栈中最小元素、获得栈中最大元素(转)
    北京网选赛第二题(最大仰望角度)
    最小圆覆盖(随机增量法&模拟退火法)
    模拟退火算法A Star not a Tree?(poj2420)
    模拟退火算法(run away poj1379)
    模拟退火算法(西安网选赛hdu5017)
    最小费用流判负环消圈算法(poj2175)
    中国邮递员问题(一)
    破坏行动问题
    进化树问题
  • 原文地址:https://www.cnblogs.com/artech/p/5892586.html
Copyright © 2011-2022 走看看