WCF版的PetShop之三:實現分布式的Membership和上下文傳遞

作者: Artech  來源: 博客園  發布時間: 2009-12-08 11:14  閱讀: 3264 次  推薦: 0   原文鏈接   [收藏]  

系列文章導航:

WCF版的PetShop之一:PetShop簡介

WCF版的PetShop之二:模塊中的層次劃分

WCF版的PetShop之三:實現分布式的Membership和上下文傳遞

 

  通過上一篇了解了模塊內基本的層次劃分之后,接下來我們來聊聊PetShop中一些基本基礎功能的實現,以及一些設計、架構上的應用如何同WCF進行集成。本篇討論兩個問題:實現分布式的Membership和客戶端到服務端上下文(Context)的傳遞。

  一、 如何實現用戶驗證

  對登錄用戶的驗證是大部分應用所必需的,對于ASP.NET來說,用戶驗證及帳號管理實現在成員資格(Membership)模塊中。同ASP.NET的其他模塊一樣,微軟在設計Membership的時候,為了實現更好地可擴展性,采用了策略(Strategy)設計模式:將模塊相關的功能定義在被稱為Provider的抽象類型中,并通過繼承它提供具體的Provider。如果這些原生的Provider不能滿足你的需求,你也可以通過繼承該抽象的Provider,創建自定義的Provider。通過ASP.NET提供的配置,你可以很輕易地把自定義的Provider應用到你的應用之中。在一般情況下,最終的編程人員并不通過Provider調用相關的功能,而是通過一個外觀(Facade)類實現對相關功能的調用。

  ASP.NET成員資格模塊的設計基本上可以通過下面的類圖1反映出來:最終的編程人員通過外觀類型(Façade Class)Membership調用成員資格相關的功能,比如用戶認證、用戶注冊、修改密碼等;Membership通過抽象類MembershipProvider提供所有的功能,至于最終的實現,則定義在一個個具體的MembershipProvider中。基于成員資格信息不同的存儲方式,ASP.NET提供了兩個原生的MembershipProvider:SqlMembershipProviderActiveDirectoryMembershipProvider,前者基于SQL Server數據庫,后者基于AD。如果這兩個MembershipProvider均不能滿足需求,我們還可以自定義MembershipProvider。

clip_image002

圖1 ASP.NET Membership 設計原理

  我們的案例并不會部署于AD之中,所以不能使用ActiveDirectoryMembershipProvider;直接通過Web服務器進行數據庫的存取又不符合上述物理部署的要求(通過應用服務器進行數據庫訪問),所以SqlMembershipProvider也不能為我們所用。為此需要自定義MembershipProvider,通過WCF服務調用的形式提供成員資格所有功能的實現。我們將該自定義MembershipProvider稱為RemoteMembershipProvider。圖2揭示了RemoteMembershipProvider實現的原理:RemoteMembershipProvider通過調用WCF服務MembershipService提供對成員資格所有功能的實現;MembershipService則通過調用Membership實現服務;最終的實現還是落在了SqlMembershipProvider這個原生的MembershipProvider上。

clip_image004

圖2 RemoteMembershipProvider實現原理

  1、服務契約和服務實現

  首先來看看MembershipService實現的服務契約的定義。由于MembershipService最終是為RemoteMembershipProvider這個自定義MembershipProvider服務的,所以服務操作的定義是基于MembershipProvider的API定義。MembershipProvider包含兩種類型的成員:屬性和方法,簡單起見,我們可以為MembershipProvider每一個抽象方法定義一個匹配的服務操作;而對于所有屬性,完全采用服務端(應用服務器)的MembershipProvider相關屬性。在RemoteMembershipProvider初始化的時候通過調用MembershipService獲取所有服務端MembershipProvider的配置信息。為此,我們為MembershipProvider的所有屬性定義了一個數據契約:MembershipConfigData。在PetShop中,MembershipConfigData和服務契約一起定義在Infrastructures.Service.Interface項目中。

   1: using System.Runtime.Serialization;
   2: using System.Web.Security;
   3: namespace Artech.PetShop.Infrastructures.Service.Interface
   4: {
   5:     [DataContract(Namespace = "http://www.artech.com/")]
   6:     public class MembershipConfigData
   7:     {
   8:         [DataMember]
   9:         public  string ApplicationName
  10:         { get; set; }
  11:  
  12:         [DataMember]
  13:         public bool EnablePasswordReset
  14:         { get; set; }
  15:  
  16:         [DataMember]
  17:         public bool EnablePasswordRetrieval
  18:         { get; set; }
  19:  
  20:         [DataMember]
  21:         public int MaxInvalidPasswordAttempts
  22:         { get; set; }
  23:  
  24:         [DataMember]
  25:         public int MinRequiredNonAlphanumericCharacters
  26:         { get; set; }
  27:  
  28:         [DataMember]
  29:         public int MinRequiredPasswordLength
  30:         { get; set; }
  31:  
  32:         [DataMember]
  33:         public int PasswordAttemptWindow
  34:         { get; set; }
  35:  
  36:         [DataMember]
  37:         public MembershipPasswordFormat PasswordFormat
  38:         { get; set; }
  39:  
  40:         [DataMember]
  41:         public string PasswordStrengthRegularExpression
  42:         { get; set; }
  43:  
  44:         [DataMember]
  45:         public bool RequiresQuestionAndAnswer
  46:         { get; set; }
  47:  
  48:         [DataMember]
  49:         public bool RequiresUniqueEmail
  50:         { get; set; }
  51:     }
  52: }

  在服務契約中,定義了一個額外的方法GetMembershipConfigData獲取服務端MembershipProvider的所有配置信息,而對于服務操作的定義,則與MembershipProvider同名抽象方法相對應。

   1: using System.ServiceModel;
   2: using System.Web.Security;
   3: namespace Artech.PetShop.Infrastructures.Service.Interface
   4: {
   5:     [ServiceContract(Namespace="http://www.artech.com/")]
   6:     public interface IMembershipService
   7:     {
   8:         [OperationContract]
   9:         bool ChangePassword(string username, string oldPassword, string newPassword);
  10:         [OperationContract]
  11:         bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
  12:         [OperationContract]
  13:         MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status);
  14:         [OperationContract]
  15:         bool DeleteUser(string username, bool deleteAllRelatedData);
  16:         [OperationContract]
  17:         MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords);
  18:         [OperationContract]
  19:         MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords);
  20:         [OperationContract]
  21:         MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords);
  22:         [OperationContract]
  23:         int GetNumberOfUsersOnline();
  24:         [OperationContract]
  25:         string GetPassword(string username, string answer);
  26:         [OperationContract(Name="GetUserByName")]
  27:         MembershipUser GetUser(string username, bool userIsOnline);
  28:         [OperationContract(Name="GetUserByID")]
  29:         MembershipUser GetUser(object providerUserKey, bool userIsOnline);
  30:         [OperationContract]
  31:         string GetUserNameByEmail(string email);
  32:         [OperationContract]
  33:         string ResetPassword(string username, string answer);
  34:         [OperationContract]
  35:         bool UnlockUser(string userName);
  36:         [OperationContract]
  37:         void UpdateUser(MembershipUser user);
  38:         [OperationContract]
  39:         bool ValidateUser(string username, string password);
  40:         [OperationContract]
  41:         MembershipConfigData GetMembershipConfigData();
  42:     }
  43: }

  服務的實現,則異常簡單,我們須要做的僅僅是通過Membership.Provider獲得當前的MembershipProvider,調用同名的屬性或方法即可。MembershipService定義在Infrastructures.Service中,定義如下:

   1: using System.Web.Security;
   2: using Artech.PetShop.Infrastructures.Service.Interface;
   3: namespace Artech.PetShop.Infrastructures.Service
   4: {
   5:     public class MembershipService : IMembershipService
   6:     {
   7:         #region IMembershipService Members
   8:  
   9:         public bool ChangePassword(string username, string oldPassword, string newPassword)
  10:         {
  11:             return Membership.Provider.ChangePassword(username, oldPassword, newPassword);
  12:         }
  13:  
  14:         public bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
  15:         {
  16:             return Membership.Provider.ChangePasswordQuestionAndAnswer(username, password, newPasswordQuestion, newPasswordAnswer);
  17:         }
  18:         //其他成員
  19:         public MembershipConfigData GetMembershipConfigData()
  20:         {
  21:             return new MembershipConfigData
  22:             {
  23:                 ApplicationName = Membership.Provider.ApplicationName,
  24:                 EnablePasswordReset = Membership.Provider.EnablePasswordReset,
  25:                 EnablePasswordRetrieval = Membership.Provider.EnablePasswordRetrieval,
  26:                 MaxInvalidPasswordAttempts = Membership.Provider.MaxInvalidPasswordAttempts,
  27:                 MinRequiredNonAlphanumericCharacters = Membership.Provider.MinRequiredNonAlphanumericCharacters,
  28:                 MinRequiredPasswordLength = Membership.Provider.MinRequiredPasswordLength,
  29:                 PasswordAttemptWindow = Membership.Provider.PasswordAttemptWindow,
  30:                 PasswordFormat = Membership.Provider.PasswordFormat,
  31:                 PasswordStrengthRegularExpression = Membership.Provider.PasswordStrengthRegularExpression,
  32:                 RequiresQuestionAndAnswer = Membership.Provider.RequiresQuestionAndAnswer,
  33:                 RequiresUniqueEmail = Membership.Provider.RequiresUniqueEmail
  34:             };
  35:         }
  36:  
  37:         #endregion
  38:     }
  39: }

  2、RemoteMembershipProvider的實現

  由于RemoteMembershipProvider完全通過調用WCF服務的方式提供對所有成員資格功能的實現,所以進行RemoteMembershipProvider配置時,配置相應的終結點就可以了。

   1: xml version="1.0"?>
   2: <configuration>    
   3:     <system.web>        
   4:         <membership defaultProvider="RemoteProvider">
   5:             <providers>
   6:                 <add name="RemoteProvider" type="Artech.PetShop.Infrastructures.RemoteMembershipProvider,Artech.PetShop.Infrastructures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" endpoint="membershipservice"/>
   7:             providers>
   8:         membership>
   9:       system.web>
  10:     <system.serviceModel>    
  11:         <client>
  12:             <endpoint address="http://localhost/PetShop/Infrastructures/MembershipService.svc" behaviorConfiguration="petShopBehavior" binding="ws2007HttpBinding"  contract="Artech.PetShop.Infrastructures.Service.Interface.IMembershipService" name="membershipservice"/>
  13:         client>
  14:     system.serviceModel>    
  15: configuration>

  在RemoteMembershipProvider中,通過Initialize方法獲取配置的終結點名稱并創建服務代理。通過該代理調用GetMembershipConfigData操作獲取服務端MembershipProvider的配置信息,并對RemoteMembershipProvider進行初始化,RemoteMembershipProvider定義如下:

   1: using System.Collections.Specialized;
   2: using System.Configuration;
   3: using System.Linq;
   4: using System.Web.Security;
   5: using Artech.PetShop.Common;
   6: using Artech.PetShop.Infrastructures.Service.Interface;
   7:  
   8: namespace Artech.PetShop.Infrastructures
   9: {
  10:     public class RemoteMembershipProvider : MembershipProvider
  11:     {
  12:         private bool _enablePasswordReset;
  13:         private bool _enablePasswordRetrieval;
  14:         //其他字段成員
  15:  
  16:         public IMembershipService MembershipProxy
  17:         { get; private set; }        
  18:  
  19:         public override int MaxInvalidPasswordAttempts
  20:         {
  21:             get { return this._maxInvalidPasswordAttempts; }
  22:         }       
  23:         
  24:         //其他屬性成員        
  25:         public override void Initialize(string name, NameValueCollection config)
  26:         {
  27:             if (!config.AllKeys.Contains<string>("endpoint"))
  28:             {
  29:                 throw new ConfigurationErrorsException("Missing the mandatory \"endpoint\" configuraiton property.");
  30:             }
  31:  
  32:             this.MembershipProxy = ServiceProxyFactory.Create(config["endpoint"]);
  33:             base.Initialize(name, config);
  34:             MembershipConfigData configData = this.MembershipProxy.GetMembershipConfigData();
  35:             this.ApplicationName = configData.ApplicationName;
  36:             this._enablePasswordReset = configData.EnablePasswordReset;
  37:             this._enablePasswordRetrieval = configData.EnablePasswordRetrieval;            
  38:             //......
  39:         }
  40:     }
  41: }

  對于其他抽象方法的實現,僅僅須要通過上面創建的服務代理,調用相應的服務操作即可。

  注: 為了避免在服務操作調用后頻繁地進行服務代理的關閉(Close)和終止(Abort)操作,我們采用基于AOP的方式實現服務的調用,將這些操作封裝到一個自定義的RealProxy中,并通過ServiceProxyFactory創建該RealProxy的TransparentProxy。相關實現可以參考《WCF技術剖析(卷1)》第九章。

  二、 上下文的共享及跨域傳遞

  在進行基于N-Tier的應用開發中,我們往往需要在多個層次之間共享一些上下文(Context)信息,比如當前用戶的Profile信息;在進行遠程服務調用時,也經常需要進行上下文信息的跨域傳遞。比如在PetShop中,服務端進行審核(Audit)的時候,須要獲取當前登錄的用戶名。而登錄用戶名僅僅對于Web服務器可得,所以在每次服務調用的過程中,需要從客戶端向服務端傳遞。

  1、ApplicationContext

  基于上下文的共享,我創建了一個特殊的類型:ApplicationContext。ApplicationContext定義在Common項目中,簡單起見,直接將其定義成字典的形式。至于上下文數據的真正存儲,如果當前HttpContext存在,將其存儲與HttpSessionState中,否則將其存儲于CallContext中。

  注: 由于CallConext將數據存儲于當前線程的TLS(Thread Local Storage)中,實際上HttpContext最終也采用這樣的存儲方式,所以ApplicaitonContext并不提供上下文信息跨線程的傳遞。

   1: using System.Collections.Generic;
   2: using System.Runtime.Remoting.Messaging;
   3: using System.Web;
   4: namespace Artech.PetShop.Common
   5: {
   6:     public class ApplicationContext:Dictionary<string, object>
   7:     {
   8:         public const string ContextKey = "Artech.PetShop.Infrastructures.ApplicationContext";
   9:         public const string ContextHeaderLocalName = "ApplicationContext";
  10:         public const string ContextHeaderNamespace = "http://www.artech.com/petshop/";
  11:         public static ApplicationContext Current
  12:         {
  13:             get
  14:             {
  15:                 if (HttpContext.Current != null)
  16:                 {
  17:                     if (HttpContext.Current.Session[ContextKey] == null)
  18:                     {
  19:                         HttpContext.Current.Session[ContextKey] = new ApplicationContext();
  20:                     }
  21:  
  22:                     return HttpContext.Current.Session[ContextKey] as ApplicationContext;
  23:                 }
  24:  
  25:                 if (CallContext.GetData(ContextKey) == null)
  26:                 {
  27:                     CallContext.SetData(ContextKey, new ApplicationContext());
  28:                 }
  29:  
  30:                 return CallContext.GetData(ContextKey) as ApplicationContext;
  31:             }
  32:            set
  33:             {
  34:                 if (HttpContext.Current != null)
  35:                 {
  36:                     HttpContext.Current.Session[ContextKey] = value; ;
  37:                 }
  38:                 else
  39:                 {
  40:                     CallContext.SetData(ContextKey, value);
  41:                 }
  42:             }
  43:         }
  44:         public string UserName
  45:         {
  46:             get
  47:             {
  48:                 if (!this.ContainsKey("__UserName" ))
  49:                 {
  50:                     return string.Empty;
  51:                 }
  52:  
  53:                 return (string)this["__UserName"];
  54:             }
  55:             set
  56:             {
  57:                 this["__UserName"] = value;
  58:             }
  59:         }
  60:     }
  61: }

  2、ApplicationContext在WCF服務調用中的傳遞

  下面我們來介紹一下如何實現上下文信息在WCF服務調用過程中的“隱式”傳遞。在PetShop中,我們通過WCF的擴展實現此項功能。上下文傳遞的實現原理很簡單:在客戶端,將序列化后的當前上下文信息置于出棧(Outgoing)消息的SOAP報頭中,并為報頭指定一個名稱和命名空間;在服務端,在服務操作執行之前,通過報頭名稱和命名空間將上下文SOAP報頭從入棧(Incoming)消息中提取出來,進行反序列化,并將其設置成服務端當前的上下文。

  所以,上下文的傳遞實際上包含兩個方面:SOAP報頭的添加和提取。我們通過兩個特殊的WCF對象來分別實現這兩個功能:ClientMessageInspector和CallContextInitializer,前者在客戶端將上下文信息封裝成SOAP報頭,并將其添加到出棧消息報頭集合;后者則在服務端實現對上下文SOAP報頭的提取和當前上下文的設置。關于ClientMessageInspector和CallContextInitializer,本書的下一卷關于客戶端和服務端處理流程,以及WCF擴展的部分,還將進行詳細的介紹。自定義的ClientMessageInspector和CallContextInitializer定義在Infrastructures項目中,下面是相關代碼實現:

  ContextSendInspector:

   1: using System.ServiceModel;
   2: using System.ServiceModel.Channels;
   3: using System.ServiceModel.Dispatcher;
   4: using System.Threading;
   5: using Artech.PetShop.Common;
   6: namespace Artech.PetShop.Infrastructures
   7: {
   8:     public class ContextSendInspector: IClientMessageInspector
   9:     {
  10:         public void AfterReceiveReply(ref Message reply, object correlationState)
  11:         {}
  12:  
  13:         public object BeforeSendRequest(ref Message request, IClientChannel channel)
  14:         {
  15:             if (string.IsNullOrEmpty(ApplicationContext.Current.UserName))
  16:             {
  17:                 ApplicationContext.Current.UserName = Thread.CurrentPrincipal.Identity.Name;
  18:             }
  19:             request.Headers.Add(new MessageHeader(
  20:                 ApplicationContext.Current).GetUntypedHeader(
  21:                 ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
  22:  
  23:             return null;
  24:         }
  25:     }
  26: }

  ContextReceivalCallContextInitializer:

   1: using System.ServiceModel;
   2: using System.ServiceModel.Channels;
   3: using System.ServiceModel.Dispatcher;
   4: using Artech.PetShop.Common;
   5: namespace Artech.PetShop.Infrastructures
   6: {
   7:     public class ContextReceivalCallContextInitializer : ICallContextInitializer
   8:     {
   9:         public void AfterInvoke(object correlationState)
  10:         {
  11:             ApplicationContext.Current.Clear();
  12:         }
  13:  
  14:         public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
  15:         {
  16:             ApplicationContext.Current = message.Headers.GetHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
  17:             return null;
  18:         }
  19:     }
  20: }

  和應用大部分自定義擴展對象一樣,上面自定義的ClientMessageInspector和CallContextInitializer可以通過相應的WCF行為(服務行為、終結點行為、契約行為或者操作行為)應用到WCF執行管道中。在這里我定義了一個行為類型:ContextPropagationBehaviorAttribute,它同時實現了IServiceBehavior和 IEndpointBehavior,所以既是一個服務行為,也是一個終結點行為。同時ContextPropagationBehaviorAttribute還繼承自Attribute,所以可以通過特定的方式應用該行為。自定義ClientMessageInspector和CallContextInitializer分別通過ApplyClientBehavior和ApplyDispatchBehavior方法應用到WCF客戶端運行時和服務端運行時。ContextPropagationBehaviorAttribute定義如下:

   1: using System;
   2: using System.ServiceModel.Description;
   3: using System.ServiceModel.Dispatcher;
   4: namespace Artech.PetShop.Infrastructures
   5: {
   6:    public class ContextPropagationBehaviorAttribute:Attribute, IServiceBehavior,IEndpointBehavior
   7:     {
   8:         #region IServiceBehavior Members
   9:         public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  10:         {
  11:         }
  12:  
  13:         public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
  14:         {
  15:             foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
  16:             {
  17:                 foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
  18:                 {
  19:                     foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
  20:                     {
  21:                         operation.CallContextInitializers.Add(new ContextReceivalCallContextInitializer());
  22:                     }
  23:                 }
  24:             }
  25:         }
  26:  
  27:         public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
  28:         {
  29:         }
  30:  
  31:         #endregion
  32:  
  33:         #region IEndpointBehavior Members
  34:  
  35:         public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  36:         {
  37:         }
  38:  
  39:         public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
  40:         {
  41:             clientRuntime.MessageInspectors.Add(new ContextSendInspector());
  42:         }
  43:  
  44:         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
  45:         {
  46:             foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
  47:             {
  48:                 operation.CallContextInitializers.Add(new ContextReceivalCallContextInitializer());
  49:             }
  50:         }
  51:  
  52:         public void Validate(ServiceEndpoint endpoint)
  53:         {
  54:         }
  55:  
  56:         #endregion
  57:     }
  58: }

  對于服務行為,我們既可以通過自定義特性的方式,也可以通過配置的方式進行行為的應用;而終結點行為的應用方式則僅限于配置(通過編程的形式除外)。為此我們還需要為行為定義一個特殊的類型:BehaviorExtensionElement。

   1: using System;
   2: using System.ServiceModel.Configuration;
   3: namespace Artech.PetShop.Infrastructures
   4: {
   5:    public class ContextPropagationBehaviorElement: BehaviorExtensionElement
   6:     {
   7:         public override Type BehaviorType
   8:         {
   9:             get { return typeof(ContextPropagationBehaviorAttribute); }
  10:         }
  11:  
  12:         protected override object CreateBehavior()
  13:         {
  14:             return new ContextPropagationBehaviorAttribute();
  15:         }
  16:     }
  17: }

那么ContextPropagationBehaviorAttribute就可以通過下面的配置應用到具體的服務或終結點上了。

服務端(ServiceBehavior):

   1: xml version="1.0"?>
   2: <configuration>    
   3:     <system.serviceModel>
   4:         <behaviors>
   5:             <serviceBehaviors>
   6:                 <behavior name="petshopbehavior">
   7:                     <contextPropagation/>
   8:                     <unity/>
   9:                 behavior>
  10:             serviceBehaviors>
  11:         behaviors>
  12:         <extensions>
  13:             <behaviorExtensions>
  14:                 <add name="contextPropagation" type="Artech.PetShop.Infrastructures.ContextPropagationBehaviorElement, Artech.PetShop.Infrastructures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>                
  15:             behaviorExtensions>
  16:         extensions>
  17:         <services>
  18:             <service behaviorConfiguration="petshopbehavior" name="Artech.PetShop.Products.Service.ProductService">
  19:                 <endpoint binding="ws2007HttpBinding" contract="Artech.PetShop.Products.Service.Interface.IProductService"/>
  20:             service>            
  21:         services>
  22:     system.serviceModel>    
  23: configuration>

客戶端(EndpointBehavior)

   1: xml version="1.0"?>
   2: <configuration>    
   3:     <system.serviceModel>
   4:         <behaviors>
   5:             <endpointBehaviors>
   6:                 <behavior name="petShopBehavior">
   7:                     <contextPropagation/>
   8:                 behavior>
   9:             endpointBehaviors>
  10:         behaviors>
  11:         <extensions>
  12:             <behaviorExtensions>
  13:                 <add name="contextPropagation" type="Artech.PetShop.Infrastructures.ContextPropagationBehaviorElement, Artech.PetShop.Infrastructures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  14:             behaviorExtensions>
  15:         extensions>
  16:         <client>
  17:             <endpoint address="http://localhost/PetShop/Products/productservice.svc" behaviorConfiguration="petShopBehavior" binding="ws2007HttpBinding"  contract="Artech.PetShop.Products.Service.Interface.IProductService" name="productservice"/>            
  18:         client>
  19:     system.serviceModel>    
  20: configuration>
作者:Artech
出處:http://artech.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
0
0
 
標簽:WCF PetShop
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()