Understanding ASP.NET Provider Model (Creating Custom Membership and Role Providers) - Part 3
Introduction
In the Part 1 and Part 2 we learnt the concept and internal architecture of ASP.NET provider model. We know that the provider model is extensible and one can implement custom providers to suit his requirement. In this part we will develop two custom providers - one for membership and the other for roles.
Why develop a custom membership and role provider?
Well. There can be many reasons. Here are few:
- You have custom data store (not SQL Server or Access) in which you want to store user data.
- You are using some non-standard database for which there is no inbuilt membership or role providers.
- You want to implement custom encryption mechanism for the data being saved and retrieved
- You want to write database independent membership and role provider
One more reason that I have not listed in the above list is - You may want to use your own table schema instead of using inbuilt one. At first glance it may look odd but it can be a great way to save your work while migrating applications.
Requirements
Let's decide the requirements for building our custom membership and role providers.
- We want to use our application database for storing membership and role information. That also means that we do not have a central database for storing membership details of multiple applications
- We want to store membership details in a table called Users
- We want to store available roles in the system in a table called Roles
- We want to store user-role mapping in a table called UserRoles
- For the sake of simplicity we will not include any encryption-decryption logic
- User can register by supplying user name, password and email. No security question is required
- We do not need features such as password reset and accounting locking
Database access
We will be using BinaryIntellect DatabaseHelper open source component for all our database access.
Creating the Web Site
To begin, create a new web site and add two classes called MyMembershipProvider and MyRoleProvider to App_Code folder. For the sake of simplicity we will be creating all the necessary classes in the web site itself. In a more real world situations you may create a separate class library project to contain these classes.
Configuring the web site to use our providers
Open the web.config file and add the following markup:
<membership defaultProvider="mymembershipprovider"> <providers> <add name="mymembershipprovider" type="MyMembershipProvider" connectionStringName="connstr"/> </providers> </membership>
<roleManager enabled="true" defaultProvider="myrolesprovider"> <providers> <add name="myrolesprovider" type="MyRolesProvider" connectionStringName="connstr"/> </providers> </roleManager>
Here, we instruct ASP.NET to use MyMembershipProvider class as membership provider and MyRolesProvider class as roles provider.
Creating custom membership provider
Recollect from Part 2 that custom membership providers need to inherit from System.Web.Security.MembershipProvider class. The MembershipProvider class in turn inherits from ProviderBase class. The MembershipProvider class contains several abstract methods that you must implement in your class.
If you are using VS.NET then your job is simple. Right click on the MembershipProvider class in the class definition line and choose "Implement Abstract Class". VS.NET will add dummy delimitations for all the required methods and properties from the MembershipProvider class. The following table lists all the properties and methods that you need to implement (methods are shown with parenthesis).
Property/Method Name | Description |
Initialize()* | Receives the connection string name specified in the web.config file. You can use it to perform database operation in your class. |
Name* | Represents name of our custom provider |
CreateUser()* | Creates a user |
UpdateUser()* | Saves modified information about an existing user |
DeleteUser()* | Deletes a user |
GetUser()* | Gets a user as MembershipUser instance |
GetAllUsers()* | Gets all the users as MembershipUserCollection |
ChangePassword()* | Changes password of a user |
GetPassword()* | Retrieves password of a user. Used when implementing "Forgot Password" feature |
ValidateUser()* | Authenticates the user |
EnablePasswordReset* | Indicates whether the password can be reset by the user |
EnablePasswordRetrieval* | Indicates whether the password can be retrieved by teh user |
RequiresQuestionAndAnswer* | Indicates whether user should supply a security question and answer during registration |
RequiresUniqueEmail* | Indicates whether the email supplied during registration should be unique |
ApplicationName | Name of the web application. This name is used in case you are using a central database for storing membership data of multiple applications |
MaxInvalidPasswordAttempts | Indicates the number of times user can try to login to the system |
MinRequiredNonAlphanumericCharacters | Indicates minimum no. of non alpha numeric characters that the user must supply during registration and password change |
MinRequiredPasswordLength | Indicates the minimum length required for the password when user registers or changes the password |
ChangePasswordQuestionAndAnswer() | Allows to change user's security question and answer |
FindUsersByEmail() | Searches user database on the basis of email |
FindUsersByName() | Searches user database on the basis of user name |
GetNumberOfUsersOnline() | Returns total no. of uses that are signed in |
GetUser() | Returns MembershipUser instance representing a specific user |
GetUserNameByEmail() | Returns the user name on the basis of email |
PasswordAttemptWindow | Indicates the time span for multiple login attempts |
PasswordFormat | Indicates the format of password e.g.clear, hashed etc. |
PasswordStrengthRegularExpression | Indicates a regular expression to be used to check the strength of password |
ResetPassword() | Resets the password |
UnlockUser() | Unlocks the user account |
In our example we will code the methods and properties marked with * above. The remaining members will simply throw a "Not implemented" exception.
The complete source code of our custom membership provider can be found in the download (MyMembershipProvider.cs). As an example CreateUser() method implementation is given below:
public override MembershipUser CreateUser (string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { MembershipUser user = new MembershipUser(Name, username, providerUserKey, email, passwordQuestion, null, isApproved, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now); string sql = "INSERT INTO USERS(USERNAME,PASSWORD, EMAIL,ISACTIVE) VALUES(@UID,@PWD,@EMAIL,@ISACTIVE)"; db.AddParameter("@UID", username); db.AddParameter("@PWD", password); db.AddParameter("@EMAIL", email); db.AddParameter("@ISACTIVE", (isApproved == true ? "Y" : "N")); int i = db.ExecuteNonQuery(sql);
if (i > 0) { status = MembershipCreateStatus.Success; return user; } else { status = MembershipCreateStatus.ProviderError; return null; } }
Creating custom roles provider
Creating custom roles provider involves creating a class that inherits from RoleProvider class. The following table lists all the properties and methods that you need to implement (methods are shown with parenthesis).
Property/Method Name | Description |
Initialize()* | Receives the connection string name specified in the web.config file. You can use it to perform database operation in your class. |
Name* | Represents name of our custom provider |
CreateRole* | Create a new role |
DeleteRole* | Deletes an existing role |
GetAllRoles* | Returns all roles as string array |
RoleExists* | Checks if role exists in the database |
AddUsersToRoles* | Adds users to specified roles |
RemoveUsersFromRoles* | Removes users from specified roles |
GetRolesForUser* | Returns all the roles for a specific user |
GetUsersInRole* | Returns all the users belonging to a specified role |
IsUserInRole* | Checks if a user exists in a specified role |
ApplicationName | Name of the web application. This name is used in case you are using a central database for storing membership data of multiple applications |
FindUsersInRole | Searches for users belonging to a specified role |
In our example we will code the methods and properties marked with * above. The remaining members will simply throw a "Not implemented" exception.
The complete source code of our custom membership provider can be found in the download (MyRolesProvider.cs). As an example CreateRole() method is given below:
public override void CreateRole(string roleName) { db.AddParameter("@ROLE", roleName); db.ExecuteNonQuery ("INSERT INTO ROLES(ROLENAME) VALUES(@ROLE)"); }
Testing our providers
There are four test web forms provided along with the download - Default.aspx, Login.aspx, RoleManager.aspx and UserRoles.aspx. The first two test the membership provider and the later two test the roles provider. We use essentially the same Membership and Roles classes of ASP.NET. These classes in turn call our custom provides to get the job done.
Summary
In this article we saw how easy it is to develop your own providers for membership and role management. You can extend the application to suit your needs. You can also add more security features such as encryption and password strength.