代码改变世界

Chapter 1.4:WCF实践 元数据详解

2011-01-11 10:15  田志良  阅读(...)  评论(... 编辑 收藏

 

1. 源码下载

下载地址:http://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() 中。