1. 源码下载
下载地址:https://files.cnblogs.com/tianzhiliang/WCF.Chapter1.ServiceFactory.rar
2. 元数据简介
服务有两种方案可以发布自己的元数据。一种是基于 HTTP-GET 协议提供元数据,另一种是使用专门的终结点的方式。
WCF能够为服务自动提供基于 HTTP-GET 的元数据,但需要显式地添加服务行为(Behavior)以支持这一功能。服务行为属于服务的本地特性,例如是否需要基于 HTTP-GET 交换元数据,就是一种服务行为。
我们可以通过编程方式或管理配置方式添加行为,通过管理配置方式添加行为如下所示:
<system.serviceModel> <services> <service name = "MyService" behaviorConfiguration = "MEXGET"> <host> <baseAddresses> <add baseAddress = "http://localhost:8000/"/> </baseAddresses> </host> ... </service> <service name = "MyOtherService" behaviorConfiguration = "MEXGET"> <host> <baseAddresses> <add baseAddress = "http://localhost:8001/"/> </baseAddresses> </host> ... </service> </services> <behaviors> <serviceBehaviors> <behavior name = "MEXGET"> <serviceMetadata httpGetEnabled = "true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel><system.serviceModel> <services> <service name = "MyService" behaviorConfiguration = "MEXGET"> <host> <baseAddresses> <add baseAddress = "http://localhost:8000/"/> </baseAddresses> </host> ... </service> <service name = "MyOtherService" behaviorConfiguration = "MEXGET"> <host> <baseAddresses> <add baseAddress = "http://localhost:8001/"/> </baseAddresses> </host> ... </service> </services> <behaviors> <serviceBehaviors> <behavior name = "MEXGET"> <serviceMetadata httpGetEnabled = "true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
通过编程方式添加行为如下所示:
ServiceHost host = new ServiceHost(typeof(MyService)); ServiceMetadataBehavior metadataBehavior; metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>(); if(metadataBehavior == null) { metadataBehavior = new ServiceMetadataBehavior(); metadataBehavior.HttpGetEnabled = true; host.Description.Behaviors.Add(metadataBehavior); } host.Open();
一旦启用了基于 HTTP-GET 的元数据交换,在浏览器中就可以通过 HTTP 基地址(如果存在)进行访问。如果一切正确,就会获得一个确认页面,告知开发者已经成功托管了服务。确认页面与 IIS 托管无关,即使使用自托管,我们也可以使用浏览器定位服务地址。
3. 元数据交换终结点
元数据交换终结点是一种特殊的终结点,有时候又被称为 MEX 终结点。服务能够根据元数据交换终结点发布自己的元数据,如图 1.4-1 在通常情况下并不需要在设计图中显示元数据交换终结点。
WCF 自动地为服务宿主提供了 IMetadataExchange 接口的实现,公开元数据交换终结点。我们只需要指定使用的地址和绑定,以及添加服务元数据行为。对于绑定,WCF 提供了专门的基于 HTTP、HTTPS、TCP 和 IPC 协议的绑定传输元素。对于地址,我们可以提供完整的地址,或者使用任意一个注册了的基地址。没有必要启用 HTTP-GET 选项,但是即使启用了也不会造成影响。
通过管理方式配置MEX终结点,如下所示:
<services> <service name = "MyService" behaviorConfiguration = "MEX"> <host> <baseAddresses> <add baseAddress = "net.tcp://localhost:8001/"/> <add baseAddress = "net.pipe://localhost/"/> </baseAddresses> </host> <endpoint address = "MEX" binding = "mexTcpBinding" contract = "IMetadataExchange" /> <endpoint address = "MEX" binding = "mexNamedPipeBinding" contract = "IMetadataExchange" /> <endpoint address = "http://localhost:8000/MEX" binding = "mexHttpBinding" contract = "IMetadataExchange" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name = "MEX"> <serviceMetadata/> </behavior> </serviceBehaviors> </behaviors>
通过编程方式配置MEX终结点,WCF并没有为元数据交换终结点提供专门的绑定类型。为此,我们需要创建定制绑定。定制绑定使用了与之匹配的传输绑定元素(即 BindingElement 类型),然后将绑定元素对象作为构造函数的参数,传递给定制绑定实例。最后,调用宿主的 AddServiceEndpoint() 方法,参数值分别为地址、定制绑定与 IMetadataExchange契约类型。注意,在添加终结点之前,必须校验元数据行为是否存在。如下所示:
BindingElement bindingElement = new TcpTransportBindingElement(); CustomBinding binding = new CustomBinding(bindingElement); Uri tcpBaseAddress = new Uri("net.tcp://localhost:9000/"); ServiceHost host = new ServiceHost(typeof(MyService),tcpBaseAddress); ServiceMetadataBehavior metadataBehavior; metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>(); if(metadataBehavior == null) { metadataBehavior = new ServiceMetadataBehavior(); host.Description.Behaviors.Add(metadataBehavior); } host.AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX"); host.Open();
4. 通用 ServiceHost 扩展类
using System; using System.Linq; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.Diagnostics; namespace WCF.Chapter1.ServiceFactory.Host { public class ServiceHost<T> : ServiceHost { public ServiceHost() : base(typeof(T)) { } public ServiceHost(params Uri[] baseAddresses) : base(typeof(T), baseAddresses) { } public bool HasMexEndpoint { get { return Description.Endpoints.Any(endpoint => endpoint.Contract.ContractType == typeof(IMetadataExchange)); } } public void AddAllMexEndpoints() { Debug.Assert(HasMexEndpoint == false); foreach (Uri baseAddress in BaseAddresses) { BindingElement bindingElement = null; switch (baseAddress.Scheme) { case "net.tcp": { bindingElement = new TcpTransportBindingElement(); break; } case "net.pipe": { bindingElement = new NamedPipeTransportBindingElement(); break; } case "http": { bindingElement = new HttpTransportBindingElement(); break; } case "https": { bindingElement = new HttpsTransportBindingElement(); break; } } if (bindingElement != null) { Binding binding = new CustomBinding(bindingElement); AddServiceEndpoint(typeof(IMetadataExchange), binding, "MEX"); } } } public bool EnableMetadataExchange { set { if (State == CommunicationState.Opened) { throw new InvalidOperationException("Host is already opened !"); } ServiceMetadataBehavior metadataBehavior; metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>(); if (metadataBehavior == null) { metadataBehavior = new ServiceMetadataBehavior(); metadataBehavior.HttpGetEnabled = value; Description.Behaviors.Add(metadataBehavior); } if (value == true) { if (HasMexEndpoint == false) { AddAllMexEndpoints(); } } } get { ServiceMetadataBehavior metadataBehavior; metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>(); if (metadataBehavior == null) { return false; } return metadataBehavior.HttpGetEnabled; } } } }
EnableMetadataExchange通过判断 CommunicationObject 基类的 State 属性值,确保宿主没有被打开。如果在配置文件中没有找到元数据行为,EnableMetadataExchange不会重写 配置文件中的配置值,而只是将 value 赋给新建的元数据行为对象 metadatabehavior 的 HttpGetEnabled 属性。读取 EnableMetadataExchange 的值时,属性首先会检查值是否已经配置。 如果没有配置元数据行为,则返回 false ;否则返回它的HttpGetEnabled 值。HasMexEndpoint 属性将匿名方法赋给 Predicate泛型委托。匿名方法负责检查给定终结点的契约是否属于 IMetadataExchange 类型。然后,遍历集合中的每个元素并调用 Predicate 泛型委托对象mexEndPoint,如果集合中的任意一个元素符合 Predicate 指定的比较条件,则返回 tru e,否则返回 f alse 。AddAllMexEnd-Points()方法会遍历 BaseAddresses 集合。根据基地址的样式,创建匹配的 MEX 传输绑定元素,然后再创建一个定制绑定,并将它传入到 AddServiceEndpoint() 中。