閱讀204 返回首頁    go 小米 go 小米6


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 = "https://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", "https://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 = "https://127.0.0.1:9999/calculatorservice/mex")]
   2: public class CalculatorService : ICalculator, IMetadataProvisionService
   3: {
   4:    //省略成員
   5: }

如果CalculatorService被成功寄宿,直接通過瀏覽器訪問元數據發布的地址(https://127.0.0.1:9999/calculatorservice/mex),你可以看到與圖2一樣的結果。 

image

圖2 通過IE獲取發布的元數據


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 16:34:14

  上一篇:go  WCF技術剖析之二十七: 如何將一個服務發布成WSDL[基於WS-MEX的實現](提供模擬程序)
  下一篇:go  WCF技術剖析之二十八:自己動手獲取元數據[附源代碼下載]