WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇]

时间:2022-12-13 19:57:05

对于WCF服务端元数据架构体系来说,通过MetadataExporter将服务的终结点导出成MetadataSet(参考《如何导出WCF服务的元数据》),仅仅是完成了一半的工作。被成功导出的以MetadataSet对象表示的元数据需要最终作为可被访问的网络资源发布出来,才能被服务消费者获取,进而有效地帮助他们进行服务调用。元数据的发布最终是通过ServiceMetadataBehavior这样一个服务行为实现的,我们先来认识一下ServiceMetadataBehavior

一、 元数据发布的实现者:ServiceMetadataBehavior

ServiceMetadataBehavior是一个实现了IServiceBehavior的服务行为,它实现了基于如下两种协议的元数据发布模式:

  • HTTP-GET:采用HTTP协议的Get操作,向元数据目标地址发送HTTP请求,并以查询字符串(QueryString)的形式表示相应的查询参数。元数据最终以HTTP回复的形式返回;
  • WS-MEX:元数据提供者按照WS-MEX规范创建终结点发布元数据,元数据消费者创建同样基于WS-MEX的终结点与之交互,并最终通过SOAP的形式获取元数据。关于WS-MEX,可以参考我的文章《元数据(Metadata)架构体系全景展现[WS标准篇]

我们首先通过如下得代码来看看ServiceMetadataBehavior的定义,ServiceMetadataBehavior实现IServiceBehavior接口,并将所有发布元数据的行为定义在ApplyDispatchBehavior方法中。

   1: public class ServiceMetadataBehavior : IServiceBehavior
   2: {
   3:    
   4:     //其他成员
   5:     void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters);
   6:     void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase);
   7:     void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase);
   8:  
   9:     public Uri ExternalMetadataLocation { get; set; }    
  10:  
  11:     //HTTP
  12:     public bool HttpGetEnabled { get; set; }
  13:     public Uri HttpGetUrl { get; set; }
  14:     public Binding HttpGetBinding { get; set; }
  15:  
  16:     //HTTPS
  17:     public bool HttpsGetEnabled { get; set; }
  18:     public Binding HttpsGetBinding { get; set; }
  19:     public Uri HttpsGetUrl { get; set; }
  20:  
  21:     public MetadataExporter MetadataExporter { get; set; }
  22: }

ServiceMetadataBehavior定义了一系列的属性用于控制具体的元数据发布行为,其中绝大部分是基于HTTP-GET发布模式的。ExternalMetadataLocation表示返回给客户端的一个外部元数据地址,可以是绝对地址,也可以是基于HttpGetUrl或者HttpsGetUrl表述的相对地址;基于HTTP-GET的元数据发布同时支持HTTP和HTTPS两种形式,Http(s)GetEnabled是控制是否允许基于HTTP(s)进行元数据发布的开关,Http(s)GetUrl和Http(s)GetBinding这指定了采用的地址和绑定;MetadataExporter属性表示的MetadataExporter对象用于进行元数据的导出,默认为WsdlExporter

你可以通过配置的方式来设置除MetadataExporter之外的所有ServiceMetadataBehavior的属性,此外,WCF还提供给你一些额外的配型项供你更好地控制元数据的发布行为。对于WCF的开发者或者实施者来说,当你没有一份完备的文档指导你进行基于服务行为或者终结点行为的配置时,你可以查看该行为对应的BehaviorExtensionElement的定义获取与该行为相关的所有配置信息。ServiceMetadataBehavior相关的配置项全部定义在ServiceMetadataPublishingElement中,下面给出了ServiceMetadataPublishingElement的定义:

   1: public sealed class ServiceMetadataPublishingElement : BehaviorExtensionElement
   2: {
   3:     //其他成员
   4:     public override Type BehaviorType { get; }
   5:     [ConfigurationProperty("externalMetadataLocation")]
   6:     public Uri ExternalMetadataLocation { get; set; }
   7:  
   8:     //HTTP
   9:     [ConfigurationProperty("httpGetEnabled", DefaultValue = false)]
  10:     public bool HttpGetEnabled { get; set; }
  11:     [ConfigurationProperty("httpGetUrl")]
  12:     public Uri HttpGetUrl { get; set; }
  13:     [StringValidator(MinLength=0), ConfigurationProperty("httpGetBinding", DefaultValue="")]
  14:     public string HttpGetBinding { get; set; }
  15:     [ConfigurationProperty("httpGetBindingConfiguration", DefaultValue=""), StringValidator(MinLength=0)]
  16:     public string HttpGetBindingConfiguration { get; set; }
  17:  
  18:     //HTTPS
  19:     [ConfigurationProperty("httpsGetEnabled", DefaultValue = false)]
  20:     public bool HttpsGetEnabled { get; set; }
  21:     [ConfigurationProperty("httpsGetUrl")]
  22:     public Uri HttpsGetUrl { get; set; }
  23:     [StringValidator(MinLength=0), ConfigurationProperty("httpsGetBinding", DefaultValue="")]
  24:     public string HttpsGetBinding { get; set; }
  25:     [StringValidator(MinLength=0), ConfigurationProperty("httpsGetBindingConfiguration", DefaultValue="")]
  26:     public string HttpsGetBindingConfiguration { get; set; }
  27:  
  28:     [ConfigurationProperty("policyVersion", DefaultValue="Default"), TypeConverter(typeof(PolicyVersionConverter))]
  29:     public PolicyVersion PolicyVersion { get; set; }
  30: }

通过对ServiceMetadataPublishingElement的定义我们可以看出:我们可以通过除MetadataExporter之外的所有ServiceMetadataBehavior的属性,还可以通过policyVersion配置向指定具体采用的WS-Policy的版本。下面是一个ServiceMetadataBehavior配置的例子:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service behaviorConfiguration="MetadataPublishBehavior" name=" Artech.Services.CalculatorService">
   6:                 <endpoint address="http://127.0.0.1:3721/calcuulatorservice"
   7:                     binding="basicHttpBinding" bindingConfiguration="" contract=" Artech.Contracts.ICalculator" />                
   8:             </service>
   9:         </services>
  10:  
  11:       <behaviors>
  12:         <serviceBehaviors>
  13:           <behavior name="MetadataPublishBehavior">
  14:             <serviceMetadata externalMetadataLocation="http://127.0.1/mex/calculatorservice"
  15:                 httpGetEnabled="true" httpGetUrl="http://127.0.0.1/calculatorservice/mex"
  16:                 httpsGetEnabled="true" httpsGetUrl="https://127.0.0.1/calculatorservice/mex"
  17:                 policyVersion="Policy15" />
  18:           </behavior>
  19:         </serviceBehaviors>
  20:       </behaviors>
  21: </system.serviceModel>
  22: </configuration>

如果你希望通过WS-MEX的方式进行元数据的发布,你需要为服务添加一个基于WS-MEX的终结点。基于WS-MEX的终结点和一般意义上的终结点一样由地址、绑定和契约三部分组成。其中,地址表示发布元数据的目标地址,而绑定和契约因为需要按照WS-MEX规范进行消息的交换,所以对绑定和契约具有特殊的要求。在具体对MEX终结点展开介绍之前,我们不妨先来看看如何通过配置的方式为服务添加MEX终结点:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service name="Artech.Services.CalculatorService">
   6:                 <endpoint address="http://127.0.0.1:3721/calcuulatorservice"
   7:                     binding="basicHttpBinding" bindingConfiguration="" contract="Artech.Contracts.ICalculator" />
   8:                 <endpoint address="http://127.0.0.1:3721/calcuulatorservice/mex"
   9:                     binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" />               
  10:             </service>
  11:         </services> 
  12:     </system.serviceModel>
  13: </configuration>

二、MEX 终结点有何不同?

我们通过为服务添加基于WS-MEX的终结点(以下简称MEX终结点)实现支持WS-MEX的元数据发布方式。总的来说,MEX终结点和一般意思上的终结点并没有本质的不同,也是由地址、绑定和契约三要素构成。但是,为了支持WS-MEX规定的消息交换模式和请求/回复消息的结构,对契约和绑定具有一些特殊的要求,先来看看MEX终结点的契约。

1、MEX终结点的契约:IMetadataExchange

从上面给出的基于MEX终结点的配置中,我们可以看到该终结点的契约被配置成“IMetadataExchange”。实际上IMetadataExchange是WCF内部定义的一个特殊服务契约接口,定义在System.ServiceModel.Description命名空间下,下面是IMetadataExchange的定义:

   1: [ServiceContract(ConfigurationName = "IMetadataExchange", Name = "IMetadataExchange", Namespace = "http://schemas.microsoft.com/2006/04/mex")]
   2: public interface IMetadataExchange
   3: {
   4:      [OperationContract(Action = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get", ReplyAction = "http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse", AsyncPattern = true)]
   5:     IAsyncResult BeginGet(Message request, AsyncCallback callback, object state);
   6:     Message EndGet(IAsyncResult result);
   7:     [OperationContract(Action = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get", ReplyAction = "http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse")]
   8:     Message Get(Message request);
   9: }

从定义可以看出,IMetadataExchange实际上仅仅包含一个Get服务操作,其中Get方法是正常的同步模式服务操作,而BeginGet/EndGet是按照标准的异步操作模式对Get服务操作的定义(关于异步服务操作模式,在《WCF技术剖析(卷1)》的第4章有详细的介绍)。

进一步分析IMetadataExchange的定义,由于通过ServiceContractAttribute特性将ConfigurationName属性设定成“IMetadataExchange”,这也是为何在进行MEX终结点的配置的时候,契约可以直接配置成“IMetadataExchange”,而不是“System.ServiceModel.Description. IMetadataExchange”。

再来看看Get操作,通过OperationContractAttribute特性将Action和ReplyAction设置成了http://schemas.xmlsoap.org/ws/2004/09/transfer/Gethttp://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse,如果读者《元数据(Metadata)架构体系全景展现[WS标准篇]》WS-Transfer相关介绍还有印象的话,应该知道它们就是WS-Transfer Get请求和回复SOAP消息对应的Action的值。在介绍WS—MEX的时候,我们提到过WS-MEX支持两种形式的元数据获取方式:WS-Transfer Get操作请求和Get Metadata操作请求。从这里可以看出,WCF采用的是基于WS-Transfer Get操作的元数据请求方式。

2、MEX终结点的绑定:MetadataExchangeBindings

WCF专门为MEX终结点定制了一系列的绑定,以实现对不同的网络传输协议(HTTP、HTTPS、TCP或者Named Pipe)的支持。这些定制的MEX绑定定义在MetadataExchangeBindings静态类中,你可以通过相应CreateMexXxxBinding方法创建基于某种传输协议的绑定。MetadataExchangeBindings的定义如下:

   1: public static class MetadataExchangeBindings
   2: {
   3:     //其他成员
   4:     public static Binding CreateMexHttpBinding();
   5:     public static Binding CreateMexHttpsBinding();
   6:     public static Binding CreateMexTcpBinding();   
   7:     public static Binding CreateMexNamedPipeBinding(); 
   8: }

如果你采用编程的方式为服务添加MEX终结点,那么你可以直接借助MetadataExchangeBindings创建相应的MEX绑定。如果采用配置的方式,仅仅需要将终结点的binding配置成:mexHttpBinding、mexHttpsBinding、mexTcpBinding和mexNamedPipeBinding即可,具体配置可以参考下面:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service behaviorConfiguration="MetadataPublishBehavior" name=" Artech.Services.CalculatorService">
   6:                 <endpoint address="http://127.0.0.1:3721/calcuulatorservice"
   7:                     binding="basicHttpBinding" bindingConfiguration="" contract="Artech.Contracts.ICalculator" />                
   8:                 <endpoint address="http://127.0.0.1/calculatorservice/mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   9:                 <endpoint address="https://127.0.0.1/calculatorservice/mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
  10:                 <endpoint address="net.tcp://127.0.0.1/calculatorservice/mex"                    binding="mexTcpBinding" contract="IMetadataExchange" />
  11:                 <endpoint address="net.pipe://127.0.0.1/calculatorservice/mex"                    binding="mexNamedPipeBinding" contract="IMetadataExchange" />
  12:             </service>
  13:         </services>
  14:     </system.serviceModel>
  15: </configuration>

下一篇中,我们将会讨论ServiceMetadataBehavior在内部是如何实现基于HTTP-GET和WS-MEX两种协议的元数据发布的。

作者: Artech
出处: http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。