Click here to Skip to main content
15,867,568 members
Articles / Web Development / ASP.NET

Custom Fluent Nhibernate Membership and Role Provider

Rate me:
Please Sign up or sign in to vote.
4.91/5 (20 votes)
12 Aug 2012CPOL6 min read 131K   7.3K   92   41
Custom implementation of Microsoft Membership and Role provider using Fluent Nhibernate

Introduction

The intent of this article is to provide an implementation of Microsoft’s Membership (and Role) provider using Fluent Nhibernate. Fluent Nhibernate offers a simplified implementation of Nhibernate without using the XML(hbm) mapping files. Click here for more information on Fluent NHibernate.

I am new to Fluent Nhibernate (and to Nhibernate for that matter) so if you find any glaring mistakes in the code, please let me know and I will try to fix those.

Background

For background on Fluent hibernate, please read the Fluent NHibernate Wiki.

Environment

This application was developed using VS2008 and SQL server 2005 and the code is written in C#. This application should also work in VS2005 although I have not tested it in VS2005.

Basics

Microsoft’s Membership Provider provides an easy way to integrate user management in ASP.NET applications. However if you are not using MS-SQL server or don't want to deal with scores of tables and stored procedures that the default provides creates, you would have to provide a custom implementation of the provider to suit your needs.

This article explains a basic Fluent Nhibernate implementation of Membership and Role provider. We are going to implement a simplified provider that only uses a subset of tables that the actual provider is using which is sufficient for most of the applications.

Database

In this implementation, I am using only three tables Users, Roles and UsersInRoles. The structure and definition of the tables is shown below:

Fluent-MembershipProvider/database.JPG

Users” table has a listing of all the users in your system, and “Role” table has all the roles. “UsersInRoles” is a Many to Many table which has Users and roles associated. In Microsoft’s implementation of the provider, the primary keys are GUIDs, but in our case I have stuck with integers which sound simpler to me.

Database scripts are included as part of the source code.

Fluent NHibernate Implementation

If you are not familiar with Fluent Nhibernate, I would suggest going through the basic sample project to understand this section.This is how the Fluent NHibernate solution looks and some explanations follow:

Fluent-MembershipProvider/solution.JPG

Entities

Entities contain our business domain representation of the Users and Roles. Users has a constructor and methods for adding and removing the Roles. These methods also add/remove from the inverse relationships. Users hold a reference to the roles it has, a role holds reference to how many users are associated with the role. Utils.MinDate() is used here to set a default min date for the database (SQL server in this case).

C#
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);
        }
    } 

Similar entity classes exist for Role (see the project file for details).

Mappings

Mappings folder contains the Fluent Nhibernate equivalent of the XML mappings. In UsersMap “Has ManyToMany” identifies the relationship to roles via the “UsersInRoles” table. Conversely in RolesMap HasManyToMany has an inverse relationship to “UsersInRoles”.

C#
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");
        }
    }

Similar mapping classes exist for Role (see the solution file for details).

Membership Provider

The actual implementation of the membership provider is in the class FNHMembershipProvider. This class inherits from the Security.MembershipProvider class. It provides overrides for all the methods available in the base class. In this class, I have replaced all the data access code with Fluent Data access code via the NHibernate session factory. There are several other methods that are to be implemented in the class, here we will look at two of them. In the GetUserNameByEmail, we see an example of how we query by criteria using Nhibernate, returning strongly typed Entity Users:

C#
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 procedure creates a new membership user, it uses Session.Save to save the user information to the database:

C#
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

The implementation of Role provider FNHRoleProvider involves overriding the Security.RoleProvider class. It has methods for creating/deleting roles, for adding/removing users to/from roles and querying roles. The method shown below creates a role based on a given role name.

C#
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;
                    }
                }
            }
        }

Each of the providers has an initialize method which reads the configuration values from the web.config file that initializes each provider with the default settings.

SessionHelper

This static class is used to configure the connection to the database and create a session factory. I am using a connection to a SQL Server 2005, but you can easily change the database to any other supported database (MySql, Oracle, Jet, MsSqlCe, PostGre, SqlLite) without any other code changes. That’s the beauty of Nhibernate. You can read more about the database configurations in Fluent Nhibernate here.
C#
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();

        }

Sample Application

This is a sample application (FNHCustomProviders.SampleApp) which demonstrates the use of the Fluent Nhibernate Membership provider. The application contains simple aspx pages which are used to:

  • Create User
  • Manage password/forgot password
  • Manage users and roles

The Manage User and Roles page allows you to add/delete a role and assign/remove users to/from roles.

Fluent-MembershipProvider/application.JPG

Putting It All Together

  1. Database: Creates the database and tables using the script
  2. FNH Membership Provider: Compiles as INCT.FNHProviders.dll. You should have the following supporting assemblies present in your bin folder for a successful build.
    • 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
  3. FNHCustomProviders.SampleApp: Needs a reference to FluentNHibernate and FNHCustomProviders. You should have the following sections in the webconfig set up for the app to run. This should go inside system.web section.
  4. If you want to use the email password feature, then mail settings section needs to be there:

    Fluent-MembershipProvider/XML.JPG

  5. Lastly, you need to change the connection string to point to your own database.

Updates

I have added a profile provider to the existing project. The profile I have implemented is simple but suffices for most needs. It is added to the project as a separate zip file MembershipWithPorfileProvider-Part2.zip and includes the membership, role and profile provider. This also contains the web-config section to be modified and the additional table for profile. Look for other settings.txt in the zip. It contains the web-config changes and the table scripts.

Updates Profile provider

I realized there were some questions about usage of profile provider. I added a zip file with usage sample. Two things need to be added a)In your project add UserProfileBase class (usage profile.zip) (b)In web.config use the inherits attribute of Profile provider to point to the location of UserProfileBase For usage something like this will work :

C#
//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();

References

History

  • 29th January, 2010: Initial post
  • 7th June, 2010: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Lead Enterprise Application development and architecture using Asp.net/C#/Ajax/SSIS with back end databases like Ms-SQL, Oracle.

You can contact me at suhel.shah@gmail.com

Comments and Discussions

 
QuestionHow to get records order by UserCount Pin
PraveenSabbisetti7-Jan-12 1:25
professionalPraveenSabbisetti7-Jan-12 1:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.