WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)

基于HTTP-GET的元数据发布方式与基于WS-MEX原理类似,但是ServiceMetadataBehavior需要做的更多额外的工作。原因很简单,由于在WS-MEX模式下,我们为寄宿的服务添加了相应的MEX终结点,那么当服务被成功寄宿后,WCF已经为元数据的消息交换建立了如图1所示的分发体系,我们需要做的仅仅是对MEX终结点的DispatchRuntime进行相应的定制而已。

image 图1 WCF服务端分发体系

但是如果采用HTTP-GET模式,实际上我们需要从ChannelDispatcher开始,重新构建整个分发体系。接下来,我们在《WS-MEX原理》提供实例的基础上,对我们自定义ServiceMetadataBehaviorAttribute进行进一步的完善,使之同时对两种模式的元数据发布提供支持。 (Source Code从这里下载)

首先,我们需要定义一个新的服务契约接口:IHttpGetMetadata,Get操作处理任何形式的消息请求,因为它的输入参数和返回类型均为Message,并且Action和ReplyAction为*。

   1: using System.ServiceModel;
   2: using System.ServiceModel.Channels;
   3: namespace ServiceMetadataBehaviorSimulator
   4: {
   5:     [ServiceContract(Name = "IHttpGetMetadata", Namespace = "http://www.artech.com/")]
   6:     public interface IHttpGetMetadata
   7:     {
   8:         [OperationContract(Action = "*", ReplyAction = "*")]
   9:         Message Get(Message msg);
  10:     }
  11: }

然后我们让前面定义的MetadataProvisionService实现IHttpGetMetadata接口,在这里无需再写任何多余的代码,因为MetadataProvisionService已经具有了一个Get方法。

   1: public class MetadataProvisionService : IMetadataProvisionService, IHttpGetMetadata
   2: {
   3:       //省略成员
   4: }

接下来的工作就是构建一个全新的ChannelDispatcher,以及关联EndpointDispatcher,最后对EndpointDispatcherDispatchRuntime进行定制。为此,我单独写了一个方法:CreateHttpGetChannelDispatcher。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public class ServiceMetadataBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5: private const string SingletonInstanceContextProviderType = "System.ServiceModel.Dispatcher.SingletonInstanceContextProvider,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   6:      private const string SyncMethodInvokerType = "System.ServiceModel.Dispatcher.SyncMethodInvoker,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   7:      private const string MessageOperationFormatterType = "System.ServiceModel.Dispatcher.MessageOperationFormatter, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   8:  
   9:     private static void CreateHttpGetChannelDispatcher(ServiceHostBase host, Uri listenUri, MetadataSet metadata)
  10:     {
  11:         //创建Binding
  12:         TextMessageEncodingBindingElement messageEncodingElement = new TextMessageEncodingBindingElement() { MessageVersion = MessageVersion.None };
  13:         HttpTransportBindingElement transportElement = new HttpTransportBindingElement();
  14:         Utility.SetPropertyValue(transportElement, "Method", "GET");
  15:         Binding binding = new CustomBinding(messageEncodingElement, transportElement);
  16:  
  17:         //创建ChannelListener
  18:         IChannelListener listener = binding.BuildChannelListener<IReplyChannel>(listenUri, string.Empty, ListenUriMode.Explicit, new BindingParameterCollection());
  19:         ChannelDispatcher dispatcher = new ChannelDispatcher(listener, "ServiceMetadataBehaviorHttpGetBinding", binding) { MessageVersion = binding.MessageVersion };
  20:  
  21:         //创建EndpointDispatcher
  22:         EndpointDispatcher endpoint = new EndpointDispatcher(new EndpointAddress(listenUri), "IHttpGetMetadata", "http://www.artech.com/");
  23:  
  24:         //创建DispatchOperation,并设置DispatchMessageFormatter和OperationInvoker
  25:         DispatchOperation operation = new DispatchOperation(endpoint.DispatchRuntime, "Get", "*", "*");
  26:         operation.Formatter = Utility.CreateInstance<IDispatchMessageFormatter>(MessageOperationFormatterType, Type.EmptyTypes, new object[0]);
  27:         MethodInfo method = typeof(IHttpGetMetadata).GetMethod("Get");
  28:         operation.Invoker = Utility.CreateInstance<IOperationInvoker>(SyncMethodInvokerType, new Type[] { typeof(MethodInfo) }, new object[] { method });
  29:         endpoint.DispatchRuntime.Operations.Add(operation);
  30:  
  31:         //设置SingletonInstanceContext和InstanceContextProvider
  32:         MetadataProvisionService serviceInstance = new MetadataProvisionService(metadata);
  33:         endpoint.DispatchRuntime.SingletonInstanceContext = new InstanceContext(host, serviceInstance);
  34:         endpoint.DispatchRuntime.InstanceContextProvider = Utility.CreateInstance<IInstanceContextProvider>(SingletonInstanceContextProviderType, new Type[] { typeof(DispatchRuntime) }, new object[] { endpoint.DispatchRuntime });
  35:         dispatcher.Endpoints.Add(endpoint);
  36:  
  37:         //设置ContractFilter和AddressFilter
  38:         endpoint.ContractFilter = new MatchAllMessageFilter();
  39:         endpoint.AddressFilter = new MatchAllMessageFilter();
  40:  
  41:         host.ChannelDispatchers.Add(dispatcher);
  42:     }
  43: }

大体上介绍一下创建ChannelDispatcher的逻辑。首先创建绑定对象,该绑定由两个绑定元素构成:TextMessageEncodingBindingElementHttpTransportBindingElement,这些因为元数据请求消息就是单纯的HTTP-GET请求消息,并不是一个SOAP,所以需要将HttpTransportBindingElement的消息版本设为None,并将Method属性(这是一个internal属性)设为GET。

然后利用创建的绑定对象创建ChannelListener,并基于该ChannelListener创建ChannelDispatcher对象。当ChannelDispatcher成功创建,开始创建EndpointDispatcher对象,并定制该EndpointDispatcherDispatchRuntime。这其中包括创建DispatchOperation对象以及相关的消息格式化器以及操作执行器。然后是我们熟悉的对InstanceContextProvider和SingletonInstanceContext的设定。最后需要设置EndpointDispatcher的两个消息筛选器:契约筛选器地址筛选器,在这将它们设置成MatchAllMessageFilter类型,使之能够匹配所有的请求消息。关于WCF的消息筛选机制,在《WCF技术剖析(卷1)》第2章有详细介绍。

DispatchRuntime被成功定制,将创建的EndpointDispatcher添加到ChannelDispatcherEndpointDispatcher列表,最终再将ChannelDispatcher添加到ServiceHost的ChannelDispatcher列表中。

然后,我们在ServiceMetadataBehaviorAttribute添加两个属性:HttpGetEnabledHttpGetUrl,前者表示是否采用基于HTTP-GET的元数据发布模式,后者指定元数据发布的地址。并将上面定义的CreateHttpGetChannelDispatcher添加到ApplyDispatchBehavior方法中。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public class ServiceMetadataBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5:     public bool HttpGetEnabled
   6:     { get; set; }
   7:     public string HttpGetUrl
   8:     { get; set; }
   9:     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  10:     {
  11:         MetadataSet metadata = GetExportedMetadata(serviceDescription);
  12:         CustomizeMexEndpoints(serviceDescription, serviceHostBase, metadata);
  13:         if (this.HttpGetEnabled)
  14:         {
  15:             CreateHttpGetChannelDispatcher(serviceHostBase, new Uri(this.HttpGetUrl), metadata);
  16:         }    
  17: }
  18: }

那么现在我们就可以通过下面的方式将ServiceMetadataBehaviorAttribute应用到我们的CalculatorService,并通过HttpGetUrl属性指定原数据发布的目标地址:

   1: [ServiceMetadataBehavior(HttpGetEnabled = true, HttpGetUrl = "http://127.0.0.1:9999/calculatorservice/mex")]
   2: public class CalculatorService : ICalculator, IMetadataProvisionService
   3: {
   4:    //省略成员
   5: }

如果CalculatorService被成功寄宿,直接通过浏览器访问元数据发布的地址(http://127.0.0.1:9999/calculatorservice/mex),你可以看到与图2一样的结果。 

image

图2 通过IE获取发布的元数据

作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2009-12-27 17:50  Artech  阅读(...)  评论(... 编辑 收藏