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。

图1 ASP.NET Membership 设计原理

我们的案例并不会部署于AD之中,所以不能使用ActiveDirectoryMembershipProvider;直接通过Web服务器进行数据库的存取又不符合上述物理部署的要求(通过应用服务器进行数据库访问),所以SqlMembershipProvider也不能为我们所用。为此需要自定义MembershipProvider,通过WCF服务调用的形式提供成员资格所有功能的实现。我们将该自定义MembershipProvider称为RemoteMembershipProvider。图2揭示了RemoteMembershipProvider实现的原理:RemoteMembershipProvider通过调用WCF服务MembershipService提供对成员资格所有功能的实现;MembershipService则通过调用Membership实现服务;最终的实现还是落在了SqlMembershipProvider这个原生的MembershipProvider上。

图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<IMembershipService>(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<T>创建该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<ApplicationContext>(
  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>(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<ServiceEndpoint> 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>

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-12-24 22:18:42

WCF版的PetShop之三:实现分布式的Membership和上下文传递的相关文章

艾伟_转载:WCF版的PetShop之三:实现分布式的Membership和上下文传递

本系列文章导航 WCF版的PetShop之一:PetShop简介 WCF版的PetShop之二:模块中的层次划分 WCF版的PetShop之三:实现分布式的Membership和上下文传递 通过上一篇了解了模块内基本的层次划分之后,接下来我们来聊聊PetShop中一些基本基础功能的实现,以及一些设计.架构上的应用如何同WCF进行集成.本篇讨论两个问题:实现分布式的Membership和客户端到服务端上下文(Context)的传递. 一. 如何实现用户验证 对登录用户的验证是大部分应用所必需的,对

艾伟_转载:WCF版的PetShop之一:PetShop简介

本系列文章导航 WCF版的PetShop之一:PetShop简介 WCF版的PetShop之二:模块中的层次划分 WCF版的PetShop之三:实现分布式的Membership和上下文传递 在<WCF技术剖析(卷1)>的最后一章,我写了一个简单基于WCF的Web应用程序,该程序模拟一个最简单的网上订购的场景,所以我将其命名为PetShop.PetShop的目在于让读者体会到在真正的项目开发中,如何正确地.有效地使用WCF.在这个应用中,还会将个人对设计的一些总结融入其中,希望能够对读者有所启发

WCF版的PetShop之二:模块中的层次划分[提供源代码下载]

上一篇文章主要讨论的是PetShop的模块划分,在这一篇文章中我们来讨论在一个模块中如何进行层次划分.模块划分应该是基于功能的,一个模块可以看成是服务于某项功能的所有资源的集合:层次划分侧重于关注点分离(SoC:Separation of Concern ),让某一层专注于某项单一的操作,以实现重用性.可维护性.可测试性等相应的目的.Source Code从这里下载. 一.基本的层次结构 我们接下来将目光聚焦到模块内部,看看每一个模块具体又有怎样的层次划分.我们将Infrastructures.

[WCF的Binding模型]之三:信道监听器(Channel Listener)

信道管理器是信道的创建者,一般来说信道栈的中每个信道对应着一个信道管理器.基于不同的消息处理的功能,将我们需要将相应的信道按照一定的顺序能组织起来构成一个信道栈,由于信道本身是由信道管理器创建的,所以信道对应的信道管理器也构成一个信道管理器栈,栈中信道管理器的顺序决定由它所创建信道的顺序. 对于WCF的信道层来说,信道管理器在服务端和客户端扮演着不同的角色,服务端的信道管理器在于监听来自客户端的请求,而客户端的信道仅仅是单纯的创建用于消息发送的信道.因此,客户端的消息管理器又称为信道监听器(Ch

艾伟:[WCF的Binding模型]之三:信道监听器(Channel Listener)

信道管理器是信道的创建者,一般来说信道栈的中每个信道对应着一个信道管理器.基于不同的消息处理的功能,将我们需要将相应的信道按照一定的顺序能组织起来构成一个信道栈,由于信道本身是由信道管理器创建的,所以信道对应的信道管理器也构成一个信道管理器栈,栈中信道管理器的顺序决定由它所创建信道的顺序. 对于WCF的信道层来说,信道管理器在服务端和客户端扮演着不同的角色,服务端的信道管理器在于监听来自客户端的请求,而客户端的信道仅仅是单纯的创建用于消息发送的信道.因此,客户端的消息管理器又称为信道监听器(Ch

WCF从理论到实践(11)-异步

本文目的 通过阅读本文,您能了解以下知识 1) 如何在WCF中实现异步 2) 异步操作的优缺点及其应用场合 3) 总结对比各种异步操作的实现方式 4) 代码不骗人,实现一个WCF异步小范例 本文适合的读者 本文因为涉及一些常用的基础知识和开发技巧,需要对多线程等具有一定的认识,所以初学者可能不能立即掌握,本文适合WCF中级用户或有其他分布式技术开发经验的WCF初学者 如何在WCF中实现异步 在ARM(异步编程模型)中,我们经常看到BeingXXX(..),EndXXX(..)这样的函数定义,那和

WCF从理论到实践(2):决战紫禁之巅

本文的出发点 通过阅读本文,能解决如下问题: WCF与以往的分布式技术有何区别? WCF 在安全性方面做了哪些改进? WCF在性能方面有那些改进? WCF开发模型和以往的其他分布式技术有何区别? 本文适合的读者 有过分布式开发和SOA相关实施经验的开发人员 WCF与以往的分布式技术有何区别? 在上篇文章 WCF从理论到实践一:揭开神秘面纱 中曾经阐述过WCF的前生今世,说的比较概括,本文详细的说明WCF和以往的分布式技术的区别,目的是让大家更详细的了解WcF的应用场合,为日后系统的架构作铺垫.首

《我的WCF之旅》博文系列汇总

WCF是构建和运行互联系统的一系列技术的总称,它是建立在Web Service架构上的一个全新的通信平台.你可以把它看成是.NET平台上的新一代的Web Service.WCF为我们提供了安全.可靠的的消息通信,也为我们提供了更好的可互操作性是的我们可以和其他的平台进行"交流". 微软斥巨资打造WCF,在我们看来主要出于下面两个目的:实现其对现有的分布式技术的整合以及顺应SOA的浪潮.在WCF之前,微软已经为了提供了一套完整的基于分布式的技术和产品,这些技术和产品使我们构建一个基于于分

《WCF全面剖析》-章节内容简介

(上册) 第1章 WCF简介(WCF Overview) 本章简单讲述了WCF产生的历史背景,以及在微软产品线中所处的地位.为了使读者对基于WCF的编程模型有一个直观的印象,我们将带领读者创建一个完整的WCF应用.本章实例应用涵盖了构建一个基本WCF应用所需的所有步骤,包括服务契约的定义.服务的实现.服务的寄宿.元数据的发布和导入.服务代理的创建和服务调用等. 第2章 地址(Address) 作为终结点三要素之一的地址在WCF应用编程接口中通过EndpointAddress表示,本章会从编程的角