通过上一篇了解了模块内基本的层次划分之后,接下来我们来聊聊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:SqlMembershipProvider和ActiveDirectoryMembershipProvider,前者基于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)