自定义 Fluent Nhibernate 成员和角色提供程序






4.91/5 (19投票s)
使用 Fluent Nhibernate 自定义实现 Microsoft 成员和角色提供程序
引言
本文的目的是提供一个使用 Fluent Nhibernate 实现的 Microsoft 用户(和角色)提供程序。Fluent Nhibernate 提供了一个简化的 Nhibernate 实现,无需使用 XML (hbm) 映射文件。点击此处了解更多关于 Fluent NHibernate 的信息。
我对 Fluent Nhibernate(以及 Nhibernate)是新手,所以如果你发现代码中有任何明显的错误,请告诉我,我会尽力修复。
背景
有关 Fluent hibernate 的背景信息,请阅读 Fluent NHibernate Wiki。
环境
此应用程序是使用 VS2008 和 SQL server 2005 开发的,代码是用 C# 编写的。此应用程序也应该可以在 VS2005 中运行,尽管我 belum 在 VS2005 中测试过。
基础
Microsoft 的用户提供程序提供了一种简单的方法来集成 ASP.NET 应用程序中的用户管理。但是,如果你不使用 MS-SQL server 或不想处理默认提供程序创建的大量表和存储过程,你将需要提供一个自定义的提供程序实现来满足你的需求。
本文介绍了一个基本的 Fluent Nhibernate 实现的用户和角色提供程序。我们将实现一个简化的提供程序,它只使用实际提供程序使用的部分表,这对于大多数应用程序来说已经足够了。
数据库
在此实现中,我只使用了三个表:Users
、Roles
和 UsersInRoles
。表的结构和定义如下所示

“Users
”表列出了你系统中的所有用户,“Role
”表包含所有角色。“UsersInRoles
”是一个多对多表,关联了 Users
和 Roles
。在 Microsoft 的提供程序实现中,主键是 GUID,但在我们的例子中,我坚持使用整数,这对我来说更简单。
数据库脚本已包含在源代码中。
Fluent NHibernate 实现
如果你不熟悉 Fluent Nhibernate,我建议你先看一遍基本示例项目来理解这一部分。Fluent NHibernate 解决方案看起来是这样的,下面是一些解释:

实体
Entities 包含用户和角色的业务领域表示。Users 有一个构造函数,以及用于添加和删除角色的方法。这些方法也添加到/从反向关系中添加/删除。Users 持有它拥有的角色的引用,角色持有与其关联的用户数量的引用。Utils.MinDate()
用于在此处为数据库(在此例中是 SQL server)设置默认的最小日期。
public class Users
{
public virtual int Id { get; private set; }
public virtual string Username { get; set; }
public virtual string ApplicationName { get; set; }
public virtual string Email { get; set; }
public virtual string Comment { get; set; }
public virtual string Password { get; set; }
public virtual string PasswordQuestion { get; set; }
public virtual string PasswordAnswer { get; set; }
public virtual bool IsApproved { get; set; }
public virtual DateTime LastActivityDate { get; set; }
public virtual DateTime LastLoginDate { get; set; }
public virtual DateTime LastPasswordChangedDate { get; set; }
public virtual DateTime CreationDate { get; set; }
public virtual bool IsOnLine { get; set; }
public virtual bool IsLockedOut { get; set; }
public virtual DateTime LastLockedOutDate { get; set; }
public virtual int FailedPasswordAttemptCount { get; set; }
public virtual int FailedPasswordAnswerAttemptCount { get; set; }
public virtual DateTime FailedPasswordAttemptWindowStart { get; set; }
public virtual DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
public virtual IList<roles> Roles { get; set; }
public Users()
{
this.CreationDate = Utils.MinDate();
this.LastPasswordChangedDate = Utils.MinDate();
this.LastActivityDate = Utils.MinDate();
this.LastLockedOutDate = Utils.MinDate();
this.FailedPasswordAnswerAttemptWindowStart = Utils.MinDate();
this.FailedPasswordAttemptWindowStart = Utils.MinDate();
this.LastLoginDate = Utils.MinDate();
}
public virtual void AddRole(Roles role)
{
role.UsersInRole.Add(this);
Roles.Add(role);
}
public virtual void RemoveRole(Roles role)
{
role.UsersInRole.Remove(this);
Roles.Remove(role);
}
}
类似的实体类存在于 Role
中(详情请参阅项目文件)。
映射
Mappings 文件夹包含 XML 映射的 Fluent Nhibernate 等效项。在 UsersMap
中,“Has ManyToMany”通过“UsersInRoles
”表标识了与角色的关系。反之,在 RolesMap
中,HasManyToMany 与“UsersInRoles
”有一个反向关系。
public class UsersMap: ClassMap<users>
{
public UsersMap()
{
Id(x => x.Id);
Map(x => x.Username);
Map(x => x.ApplicationName);
Map(x => x.Email);
Map(x => x.Comment);
Map(x => x.Password);
Map(x => x.PasswordQuestion);
Map(x => x.PasswordAnswer);
Map(x => x.IsApproved);
Map(x => x.LastActivityDate);
Map(x => x.LastLoginDate);
Map(x => x.LastPasswordChangedDate);
Map(x => x.CreationDate);
Map(x => x.IsOnLine);
Map(x => x.IsLockedOut);
Map(x => x.LastLockedOutDate);
Map(x => x.FailedPasswordAttemptCount);
Map(x => x.FailedPasswordAnswerAttemptCount);
Map(x => x.FailedPasswordAttemptWindowStart);
Map(x => x.FailedPasswordAnswerAttemptWindowStart);
HasManyToMany(x => x.Roles )
.Cascade.All()
.Table("UsersInRoles");
}
}
类似的映射类存在于 Role
中(详情请参阅解决方案文件)。
成员资格提供程序
用户提供程序的实际实现位于 FNHMembershipProvider
类中。该类继承自 Security.MembershipProvider
类。它提供了对基类中所有可用方法的重写。在此类中,我已经用 NHibernate 会话工厂的 Fluent Data 访问代码替换了所有数据访问代码。类中还有其他几个方法需要实现,这里我们将看其中两个。在 GetUserNameByEmail
中,我们看到了一个使用 Nhibernate 按条件查询的示例,返回强类型 Entity Users。
public override string GetUserNameByEmail(string email)
{
Entities.Users usr = null;
using (ISession session = SessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
usr = session.CreateCriteria(typeof(Entities.Users))
.Add(NHibernate.Criterion.Restrictions.Eq("Email", email))
.UniqueResult < Entities.Users>();
}
catch (Exception e)
{
if(WriteExceptionsToEventLog)
WriteToEventLog(e, "GetUserNameByEmail");
throw new ProviderException(exceptionMessage);
}
}
}
if (usr == null)
return string.Empty;
else
return usr.Username; ;
}
CreateUser
过程创建一个新的用户提供程序用户,它使用 Session.Save
将用户信息保存到数据库。
public override MembershipUser CreateUser(string username,
string password,
string email,
string passwordQuestion,
string passwordAnswer,
bool isApproved,
object providerUserKey,
out MembershipCreateStatus status)
{
ValidatePasswordEventArgs args =
new ValidatePasswordEventArgs(username, password,true);
OnValidatingPassword(args);
if (args.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
{
status = MembershipCreateStatus.DuplicateEmail;
return null;
}
MembershipUser u = GetUser(username, false);
if (u == null)
{
DateTime createDate = DateTime.Now;
//provider user key in our case is auto int
using (ISession session = SessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Entities.Users user = new Entities.Users();
user.Username = username;
user.Password = EncodePassword(password);
user.Email = email;
user.PasswordQuestion = passwordQuestion;
user.PasswordAnswer = EncodePassword(passwordAnswer);
user.IsApproved = isApproved;
user.Comment = "";
user.CreationDate = createDate;
user.LastPasswordChangedDate = createDate;
user.LastActivityDate = createDate;
user.ApplicationName = _applicationName;
user.IsLockedOut = false;
user.LastLockedOutDate = createDate;
user.FailedPasswordAttemptCount = 0;
user.FailedPasswordAttemptWindowStart = createDate;
user.FailedPasswordAnswerAttemptCount = 0;
user.FailedPasswordAnswerAttemptWindowStart = createDate;
try
{
int retId = (int)session.Save(user);
transaction.Commit();
if ((retId <1))
status = MembershipCreateStatus.UserRejected;
else
status = MembershipCreateStatus.Success;
}
catch(Exception e)
{
status = MembershipCreateStatus.ProviderError;
if(WriteExceptionsToEventLog)
WriteToEventLog(e, "CreateUser");
}
}
}
//retrieve and return user by user name
return GetUser(username, false);
}
else
status = MembershipCreateStatus.DuplicateUserName;
return null;
}
RoleProvider
角色提供程序的实现 FNHRoleProvider
涉及重写 Security.RoleProvider
类。它提供了创建/删除角色、将用户添加/删除到/从角色的方法以及查询角色的方法。下面显示的方法根据给定的角色名称创建一个角色。
public override void CreateRole(string rolename)
{
if (rolename.Contains(","))
throw new ArgumentException("Role names cannot contain commas.");
if (RoleExists(rolename))
throw new ProviderException("Role name already exists.");
using (ISession session = SessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
Entities.Roles role = new Entities.Roles();
role.ApplicationName = this.ApplicationName;
role.RoleName = rolename;
session.Save(role);
transaction.Commit();
}
catch (OdbcException e)
{
if (WriteExceptionsToEventLog)
WriteToEventLog(e, "CreateRole");
else
throw e;
}
}
}
}
每个提供程序都有一个 initialize
方法,该方法从 web.config 文件读取配置值,并使用默认设置初始化每个提供程序。
SessionHelper
static
类用于配置数据库连接并创建会话工厂。我使用的是 SQL Server 2005 的连接,但你可以轻松地将数据库更改为任何其他受支持的数据库(MySql、Oracle、Jet、MsSqlCe、PostGre、SqlLite),而无需进行其他代码更改。这就是 Nhibernate 的优点。你可以在 Fluent Nhibernate 中找到更多关于数据库配置的信息 在此处。public static ISessionFactory CreateSessionFactory(string connstr)
{
return Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005
.ConnectionString(connstr)
)
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf
<inct.fnhproviders.membership.fnhmembershipprovider>())
.BuildSessionFactory();
}
示例应用
这是一个示例应用程序(FNHCustomProviders.SampleApp
),演示了 Fluent Nhibernate 用户提供程序的使用。该应用程序包含简单的 aspx 页面,用于
- 创建用户
- 管理密码/忘记密码
- 管理用户和角色
“管理用户和角色”页面允许你添加/删除角色,并将用户分配/移除到/从角色。

整合
- 数据库:使用脚本创建数据库和表
- FNH 用户提供程序:编译为 INCT.FNHProviders.dll。为了成功构建,你的 bin 文件夹中应包含以下支持程序集。
- Antlr3.Runtime.dll
- Castle.Core.dll
- Castle.Core.xml
- Castle.DynamicProxy2.dll
- Castle.DynamicProxy2.xml
- FluentNHibernate.dll
- Iesi.Collections.dll
- Iesi.Collections.xml
- INCT.FNHProviders.dll
- log4net.dll
- log4net.xml
- nhibernate-mapping.xsd
- NHibernate.ByteCode.Castle.dll
- NHibernate.ByteCode.Castle.xml
- NHibernate.dll
- NHibernate.xml
FNHCustomProviders.SampleApp
:需要对FluentNHibernate
和FNHCustomProviders
的引用。你应该在 webconfig 中设置以下部分,以便应用程序运行。这应该放在 system.web 部分内。- 如果你想使用电子邮件密码功能,那么邮件设置部分是必需的。
- 最后,你需要更改连接字符串以指向你自己的数据库。
更新
我已将配置文件提供程序添加到现有项目中。我实现的配置文件很简单,但足以满足大多数需求。它作为单独的 zip 文件 MembershipWithPorfileProvider-Part2.zip 添加到项目中,包含用户、角色和配置文件提供程序。它还包含需要修改的 web-config 部分以及用于配置文件的附加表。请在 zip 文件中查找其他 settings.txt。它包含 web-config 更改和表脚本。
更新配置文件提供程序
我意识到有一些关于配置文件提供程序用法的疑问。我添加了一个包含用法示例的 zip 文件。需要添加两件事:a) 在你的项目中添加 UserProfileBase 类(用法 profile.zip)(b)在 web.config 中使用 Profile 提供程序的 inherits 属性指向 UserProfileBase 的位置。用法类似下面这样应该可以工作:
//get a profile
UserProfileBase profile = UserProfileBase.GetUserProfile(Page.User.Identity.Name);
string i = profile.UserName;
//creates profile ;
profile.City = "stl";
profile.BirthDate = System.DateTime.Now.AddYears(-30);
profile.FirstName = "Suhel";
profile.Gender = "M";
profile.Language = "English";
profile.LastName = "s";
profile.Occupation = "Director";
profile.State = "mo";
profile.Street = "123 main";
profile.Subscription = "yes";
profile.Website = "www.incedeit.com";
profile.Zip = "1234";
profile.Country = "US";
profile.Save();
参考文献
历史
- 2010 年 1 月 29 日:初始发布
- 2010 年 6 月 7 日:文章更新