首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

元数据交换

服务有两种方案可以发布自己的元数据。一种是基于HTTP-GET协议提供元数据,另一种则是后面将要讨论的使用专门的终结点的方式。WCF能够为服务自动提供基于HTTP-GET的元数据,但需要显式地添加服务行为(Behavior)以支持这一功能。本书后面的章节会介绍行为的相关知识。现在,我们只需要知道行为属于服务的本地特性,例如是否需要基于HTTP-GET交换元数据,就是一种服务行为。我们可以通过编程方式或管理方式添加行为。在例1-10演示的宿主应用程序的配置文件中,所有引用了定制<behavior>配置节的托管服务都支持基于HTTP-GET协议实现元数据交换。为了使用HTTP-GET,客户端使用的地址需要注册服务的HTTP基地址。我们也可以在行为中指定一个外部URL以达到同样的目的。

例1-10:使用配制文件启用元数据交换行为

<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>

一旦启用了基于HTTP-GET的元数据交换,在浏览器中就可以通过HTTP基地址(如果存在)进行访问。如果一切正确,就会获得一个确认页面,如图1-6所示,告知开发者已经成功托管了服务。确认页面与IIS托管无关,即使使用自托管,我们也可以使用浏览器定位服务地址。

图1-6:服务的确认页面

编程方式启用元数据交换

若要以编程方式启用基于HTTP-GET的元数据交换,首先需要将行为添加到行为集合中,该行为集合是宿主针对服务类型而维持的。ServiceHostBase类定义了Description属性,类型为ServiceDescription:

public abstract class ServiceHostBase : ...

{

   public ServiceDescription Description

   {get;}

   //更多成员

}

顾名思义,ServiceDescription就是对服务各个方面与行为的描述。Service-Description类定义了类型为KeyedByTypeCollection<I>的属性Behaviors。其中,类型KeyedByTypeCollection<I>的泛型参数类型为IServiceBehavior接口:

public class KeyedByTypeCollection<I> : KeyedCollection<Type,I>

{

   public T Find<T>();

   public T Remove<T>();

   //更多成员

}

public class ServiceDescription

{

   public KeyedByTypeCollection<IServiceBehavior> Behaviors

   {get;}

}

所有行为的类与特性均实现了IServiceBehavior接口。KeyedByTypeCollection<I>定义了泛型方法Find<T>(),它能够返回包含在集合中的请求行为,如果在集合中没有找到,则返回null。查询集合时,最多只能有一个返回的符合条件的行为类型。例1-11演示了如何通过编程方式启用行为。

例1-11:编程方式启用元数据交换行为

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();

首先,托管代码调用KeyedByTypeCollection<I>的Find<T>()方法,它负责判断配置文件是否提供MEX终结点行为。Find<T>方法的类型参数为ServiceMetadata-Behavior类型。ServiceMetadataBehavior类定义在System.ServiceModel. Description命名空间下:

public class ServiceMetadataBehavior : IServiceBehavior

{

   public bool HttpGetEnabled

   {get;set;}

   //更多成员

}

如果返回的行为为null,托管代码就会创建一个新的ServiceMetadataBehavior对象,并将HttpGetEnabled属性值设为true,然后将它添加到服务描述的behaviors属性中。

元数据交换终结点

元数据交换终结点是一种特殊的终结点,有时候又被称为MEX终结点。服务能够根据元数据交换终结点发布自己的元数据。图1-7展示了一个具有业务终结点和元数据交换终结点的服务。不过,在通常情况下并不需要在设计图中显示元数据交换终结点。

图1-7:元数据交换终结点

元数据交换终结点支持元数据交换的行业标准,在WCF中表现为IMetadataExchange接口:

[ServiceContract(...)]

public interface IMetadataExchange

{

   [OperationContract(...)]

   Message Get(Message request);

   //更多成员

}

IMetadataExchange接口定义的细节并不合理。它与多数行业标准相似,都存在难以实现的问题。所幸,WCF自动地为服务宿主提供了IMetadataExchange接口的实现,公开元数据交换终结点。我们只需要指定使用的地址和绑定,以及添加服务元数据行为。对于绑定,WCF提供了专门的基于HTTP、HTTPS、TCP和IPC协议的绑定传输元素。对于地址,我们可以提供完整的地址,或者使用任意一个注册了的基地址。没有必要启用HTTP-GET选项,但是即使启用了也不会造成影响。例1-12演示的服务公开了三个MEX终结点,分别基于HTTP、TCP和IPC。出于演示的目的,TCP和IPC的MEX终结点使用了相对地址,HTTP则使用了绝对地址。

例1-12:添加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并没有为元数据交换终结点提供专门的绑定类型。为此,我们需要创建定制绑定。定制绑定使用了与之匹配的传输绑定元素,然后将绑定元素对象作为构造函数的参数,传递给定制绑定实例。最后,调用宿主的AddServiceEndpoint()方法,参数值分别为地址、定制绑定与IMetadataExchange契约类型。例1-13的代码添加了基于TCP的MEX终结点。注意,在添加终结点之前,必须校验元数据行为是否存在。

例1-13:编程方式添加TCP MEX终结点

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();

简化ServiceHost<T>类

我们可以扩展ServiceHost<T>类,从而自动实现例1-11和例1-13中的代码。ServiceHost<T>定义了Boolean型属性EnableMetadataExchange,通过调用该属性添加HTTP-GET元数据行为和MEX终结点:

public class ServiceHost<T> : ServiceHost

{

   public bool EnableMetadataExchange

   {get;set;}

   public bool HasMexEndpoint

   {get;}

   public void AddAllMexEndPoints();

   //更多成员

}

如果EnableMetadataExchange属性设置为true,就会添加元数据交换行为。如果没有可用的MEX终结点,它就会为每个已注册的基地址样式添加一个MEX终结点。使用ServiceHost<T>,例1-11和例1-13就可以简化为:

ServiceHost<MyService> host = new ServiceHost<MyService>();

host.EnableMetadataExchange = true;

host.Open();

ServiceHost<T>还定义了Boolean属性HasMexEndpoint。如果服务包含了任意一个MEX终结点(与传输协议无关),则返回true。ServiceHost<T>定义的AddAllMexEndPoints()方法可以为每个已注册的基地址添加一个MEX终结点,这些基地址的样式类型包括HTTP、TCP或IPC。例1-14介绍了这些方法的实现。

例1-14:实现EnableMetadataExchange以及它支持的方法

public class ServiceHost<T> : ServiceHost

{

   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;

      }

  }

   public bool HasMexEndpoint

   {

      Get

      {

       Predicate<ServiceEndpoint> mexEndPoint= delegate(ServiceEndpoint endpoint)

                                                 {

                return endpoint.Contract.ContractType == typeof(IMetadataExchange);

                                                 };

         return Collection.Exists(Description.Endpoints,mexEndPoint);

      }

   }

   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":

            {...}

            case "http":

            {...}

            case "https":

            {...}

         }

         if(bindingElement != null)

         {

            Binding binding = new CustomBinding(bindingElement);

            AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX");

         }

      }

  }

}

EnableMetadataExchange通过判断CommunicationObject基类的State属性值,确保宿主没有被打开。如果在配置文件中没有找到元数据行为,EnableMetadataExchange不会重写配置文件中的配置值,而只是将value赋给新建的元数据行为对象metadata behavior的HttpGetEnabled属性。读取EnableMetadataExchange的值时,属性首先会检查值是否已经配置。如果没有配置元数据行为,则返回false;否则返回它的HttpGetEnabled值。HasMexEndpoint属性将匿名方法(注1)赋给Predicate泛型委托。匿名方法负责检查给定终结点的契约是否属于IMetadataExchange类型。然后,调用Collection静态类的Exists()方法,方法的参数值为服务宿主中可用的终结点集合。Exists()方法将遍历集合中的每个元素并调用Predicate泛型委托对象mexEndPoint,如果集合中的任意一个元素符合Predicate指定的比较条件(也就是说,如果匿名方法的调用返回true),则返回true,否则返回false。AddAllMexEnd-Points()方法会遍历BaseAddresses集合。根据基地址的样式,创建匹配的MEX传输绑定元素,然后再创建一个定制绑定,并将它传入到AddServiceEndpoint()中,就像例1-13那样添加终结点。

元数据浏览器

元数据交换终结点提供的元数据不仅描述了契约与操作,还包括关于数据契约、安全性、事务性、可靠性以及错误的信息。为了可视化表示正在运行的服务的元数据,我们开发了元数据浏览器工具,它的实现包含在本书附带的源代码中。图1-8显示了使用元数据浏览器获得的例1-7定义的终结点。若要使用元数据浏览器,只需要提供HTTP-GET地址或者正在运行的服务的元数据交换终结点,就能获取返回的元数据。

图1-8:元数据浏览器

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论