Working with big applications requires extending the Microsoft ASP.NET 2.0 Membership API to handle more detailed member records.
In this article, I’ll present one of the available techniques used to extend the Microsoft ASP.NET 2.0 Membership API to solve some of the limitations of that API.
Microsoft ASP.NET 2.0 shipped with a complete membership API that allows developers to manage the application’s users and their roles. However, this API best suits small to medium Web sites due to their limitation in expressing a detailed member record.
To have a better understanding of the provider model in ASP.NET 2.0, I highly recommend the following link: Provider Model in Depth (http://msdn.microsoft.com/asp.net/downloads/providers/default.aspx?pull=/library/en-us/dnaspp/html/aspnetprovmod_intro.asp)
This article discusses one of the techniques that you can use to overcome this limitation and extend the Microsoft ASP.NET 2.0 Membership API to accommodate custom member records with a solution that works on top of the Membership API without requiring any change in the API.
Article Overview
In the days of ASP.NET 1.x, managing an application’s members and roles was a hectic job, especially when most of the middle and higher-level applications needed that kind of management. You would usually end up creating your own membership API to be able to use in any application you were working on.
Microsoft ASP.NET 2.0 provides many new features, such as the Membership API, where you no longer need to worry about membership management in any application you develop. Microsoft built their Membership API upon the provider model. As with the other new features in ASP.NET 2.0, Microsoft integrated the Membership API into the .NET Framework and you can access all its objects and methods from one namespace reference, System.Web.Security.
The Membership API provides many ready-made features that you had always needed to build in ASP.NET 1.x and that took hundreds of lines of code to accomplish.
For example, the Membership API has the Login control. This control contains the username and password fields used to authenticate every user who tries to access a secure area inside the Web application. In ASP.NET 1.x, you had to add this control to each application you developed. You would end up creating a User control or a Server control so you would not need to repeat your work again and again.
ASP.NET 2.0 provides a Role Management API that works with the Membership API to provide a full solution for the authentication and authorization needed for most Web applications you develop. I will not spend more time on the Membership API controls in this article-you can find many online resources and articles to get more information.
The Membership API works fine with small Web applications. But a problem arises when working with huge applications. For example, the MembershipUser class, which is found in the Membership API and represents a member saved in the application’s database, contains a limited number of properties. This class does not support the First Name and Last Name properties, for example. Usually, in middle to large-scale applications, a member’s record requires the presence of a lot more properties and those properties are not all currently found in the MembershipUser class.
Although the Membership API presents a generic member’s record, Microsoft built the Membership API upon the provider model, so, you can easily solve that limitation and extend the current Membership API to serve your needs.
In addition to the Membership API’s need for more properties, it has another important limitation-by default it only works with Microsoft SQL Server and Active Directory. This last issue is not mainly a limitation just because Microsoft built the Membership API upon the provider model-another provider can easily replace the model with any database implementation available.
Ways to Solve the Problem
You can choose a number of ways to overcome the limitation of the MembershipUser properties in ASP.NET 2.0’s Membership API. This article will focus mainly on extending the default database that ships with the new database-related features in ASP.NET 2.0 so that you can store additional related information about a member in the Web application.
The Membership API provider model consists of the MembershipProvider, which inherits from the ProviderBase, which is the base provider for all the new provider-based features in ASP.NET 2.0. The SqlMembershipProvider and ActiveDirectoryMembershipProvider represent a concrete implementation of the MembershipProvider class.
The Membership class contains a set of static methods that provide the entire functionality of the Membership API to the user-interface layer in any Web application. In addition, the MembershipUser class discussed above represents a single member in the Membership API database. The above can be better understood by having a look at the Membership API class hierarchy (Figure 1).
A typical scenario to overcome the limitation of the Membership API is to develop a new provider that inherits from the MembershipProvider where you override the existing methods and add more functionality as the application requires, but in this article I will show you an entirely different approach. Before I get to my new approach, here is a brief breakdown, through a simple example, of how the scenario mentioned above works so you can see the difference.
In the user-interface layer, an ASP.NET page gathers all the required information about the member using the Profile object to store the additional data. The page calls the static method, CreateUser, which is part of the Membership class. In the new membership provider, the CreateUser method would still function as before by adding the member’s record into the default database; however, it will also be responsible to add the additional member-related data that was saved in the profile object previously, into a new table added to the database that will hold the additional related data on the member’s record.
In this article, I will extend the Membership API in a completely different way. I will show you how to wrap the current Membership API without touching it or even inheriting from it. The basic idea is to create a wrapper over the methods that ship with the Membership API. This way you are extending the set of these methods that affect the data collected to a member’s record. By extending the set, you get a richer environment to work with; the default Membership API methods are still usable in other places where there is no need to create, update, delete, and get a member’s record from a database.
The Concept
The idea behind extending the Membership API is as follows: I’ll create a new ExtendedMembershipUser class that inherits all the default properties from the MembershipUser, and then I’ll add my own custom properties that I will use throughout the article to explain one of the methodologies you can use to extend the ASP.NET 2.0 Membership API. You can later customize this object to fit your needs and requirements.
The new object contains three added properties: FirstName, LastName, and DateOfBirth. This snippet shows a sample of the ExtendedMembershipUser class you’ll learn about in this article:
public ExtendedMembershipUser(MembershipUser _mu,
string FirstName,
string LastName,
DateTime DateOfBirth)
: base(_mu.ProviderName, _mu.UserName,
_mu.ProviderUserKey,
_mu.Email, _mu.PasswordQuestion, _mu.Comment,
_mu.IsApproved, _mu.IsLockedOut, _mu.CreationDate,
_mu.LastLoginDate, _mu.LastActivityDate,
_mu.LastPasswordChangedDate,
_mu.LastLockoutDate)
{
// Assign local fields
this._FirstName = FirstName;
this._LastName = LastName;
this._DateOfBirth = DateOfBirth;
}
As you can see, the ExtendedMembershipUser object inherits from the MembershipUser class. The constructor’s input is an object of type MembershipUser and the three added properties mentioned above. In this case, the constructor makes a call to the base class inheriting from the MembershipUser.
The full constructor shown in the code snippet below has as input a list of all the individual properties of the MembershipUser together with the custom properties that I added.
public ExtendedMembershipUser(string
ProviderName, string UserName, object
ProviderUserKey, string Email, string
PasswordQuestion,
string Comment, bool IsApproved,
bool IsLockedOut, DateTime CreationDate,
DateTime LastLoginDate,
DateTime LastActivityDate,
DateTime LastPasswordChangedDate,
DateTime LastLockoutDate,
string FirstName,
string LastName, DateTime DateOfBirth)
: base(ProviderName,
UserName, ProviderUserKey, Email,
PasswordQuestion, Comment, IsApproved,
IsLockedOut,
CreationDate, LastLoginDate, LastActivityDate,
LastPasswordChangedDate, LastLockoutDate)
{
// Assign local fields
this._FirstName = FirstName;
this._LastName = LastName;
this._DateOfBirth = DateOfBirth;
}
The next step is to decide what should go in the ExtendedMembership class, which is the provider model’s manager class that should include the static methods available to manage the custom ExtendedMembershipUser. Basically, you need to upgrade the CreateUser, Update, Delete, GetUser, and GetAllUsers methods, which you will mostly use in a Web application. Why are these methods the only methods you need to upgrade?
The answer is simple if you look at all the other methods available in the Membership class:
- FindUsersByEmail: I doubt this method is widely used. How many times do you allow users to have the same e-mail address in your application? So you don’t need to upgrade this method.
- FindUsersByName: Do you often allow members in the same application to have the same UserName? Probably not, so once again, you don’t need to upgrade this one.
- FindUserNameByEmail: You do not need to upgrade this method. You can use it “as is” since you can retrieve the UserName from the Membership API database regardless of what custom properties you add to the database.
So now you know the required methods and the main ExtendedMembershipUser to work with, it is time to start developing this new API.
One important thing to mention before proceeding is that you will build the extended Membership API upon a provider model just as Microsoft built the Membership API.
You’ll use the provider model mainly to build the additional features required to process the new custom properties you added to the ExtendedMembershipUser class. Therefore, the implementation of the methods inside the ExtendedMembership class contains calls to both the default Membership methods (represented by built-in methods like: Membership.GetUser, Membership.CreateUser, etc.) and you’ll create the methods implemented by the new provider model to handle the custom properties added.
After developing the new API, note that you will have a rich environment to work with since you can use both APIs: one to handle the default MembershipUser combined with the custom added properties and one to handle routine functions, such as GetPassword, FindUserNameByEmail, and ValidateUser.
To sum up this section, you will use the new extended Membership to Create, Update, Delete, Get, and GetAll methods now that you have integrated the built-in MembershipUser and the custom properties. For example, adding a new member requires that you add data to the tables that ship with the Membership API and to the table that holds the additional member’s data. You can still use all the other built-in functionalities from the Membership class, for example, the Login control automatically validates a user the same way since you did not change the ValidateUser method.
Set up the Database
This section discusses the table I added to hold the values of the custom properties and the accompanying stored procedures that the new ExtendedMembershipProvider uses.
In this article, I’ll use Microsoft SQL Server 2000. To benefit from the Membership API, you need to create a new empty database, and then install the application services database used by many new database-related features in ASP.NET 2.0, such as the Membership API. To do this, use the following procedure:
- Go to Microsoft SQL Server Enterprise Manager and create a new database called ExtendedMemberShipDb.
- Type the following text at a command prompt to install the application services database:
{Drive Letter}:\Windows\Microsoft.NET\
Framework\{Version Number}\aspnet_regsql.exe
Use the aspnet_regsql.exe utility to install the application service’s tables into the current database. Once the executable runs, install the required tables, stored procedures, and user-defined functions to the ExtendedMemberShipDb database created above.
For more information on how to do the above process, you can check this step-by-step post on my blog: “Install Application Services Database on Microsoft SQL Server 2000” (http://bhaidar.net/cs/archive/2006/01/22/install-application-services-database-on-microsoft-sql-server-2000.aspx).
Now that you have the database installed, create a new table (called UserInfo) to hold the values for the custom properties that you added.
This table contains the following columns:
- UserId of type UniqueIdentifier and designated as the primary key of the UserInfo table. It is also the primary key of this table.
- FirstName of type VarChar and size 50
- LastName of type VarChar and size 50
- DateOfBirth of type DateTime
This table stores the extended data related to the member in the application. Note that you used the UserId as a primary key, which is of type UniqueIdentifier. UserId is the same key used as a primary key for the member’s record in the default Membership API tables. By using the same key, you can relate the UserInfo table to all the other member management tables.
Now it is time to create the stored procedures that you will use in the SqlMembershipProvider (you will implement SqlMembershipProvider later in this article).
The stored procedures are:
- aspnet_ExtendedMemberShip_CreateUser
- aspnet_ExtendedMembership_DeleteUser
- aspnet_ExtendedMembership_GetAllUsers
- aspnet_ExtendedMembership_GetUserByUserId
- aspnet_ExtendedMembership_GetUsersByUserIds
- aspnet_ExtendedMembership_UpdateUser
The CreateUser stored procedure (Listing 1) checks whether the member to be added is already found in the table-if not found, CreateUser will insert the record.
The same logic follows in the DeleteUser stored procedure (Listing 2); that is, if the record is already in the table, delete it.
I will not cover the entire stored procedures here since they all follow the same logic shown above. You can look at all of those stored procedures in the downloadable code accompanying this article.
Now that you have created the table and stored procedures, I’ll show you how to develop the ExtendedMembershipProvider.
ExtendedMembershipProvider
The Extended Membership API consists of the following:
- The ExtendedMembershipUser class, which represents a single user record with the default and custom properties of a member.
- The ExtendedMembership class containing the static methods accessible by the user interface layer.
- All the files that constitute the Extended Provider, which is used to provide concrete implementation for the custom methods added in the ExtendedMembership class.
The ExtendedMembership class is similar to the Membership class in the ASP.NET 2.0 Membership API. The static methods contained in the ExtendedMembership class combine functionalities from both the default Membership class and added implemented methods in the ExtendedMembershipProvider used to handle the custom data. I’ll go through each implemented method and explain how it works.
- CreateUser: Used to add a new member to the database.
- DeleteUser: Used to delete a member stored in the database.
- Update: Used to update a member’s record in the database.
- GetUser: Used to retrieve a single user stored in the database
- GetAllUsers: Used to retrieve a list of users stored in the database.
You will use the above methods to manage both the default and custom properties of a member’s record. This idea will be clear once I discuss the above methods in detail.
The CreateUser method has two overloaded methods: the first uses a CreateUserWizard ASP.NET control to create the user and the second creates a user programmatically using the ExtendedMembership API. In the code below, you can see an example of the method version that you can use inside the CreateUser event that ASP.NET will fire after it creates the user through the implicit call to the Membership API inside the CreateUserWizard:
public static bool
CreateUser(string UserName, string FirstName,
string LastName, DateTime DateOfBirth)
{
// Get the UserId
object UserId =
Membership.GetUser(UserName).ProviderUserKey;
// Call the custom provider
return MemberShip.Provider.CreateUser(
new UserInfo(UserId, FirstName, LastName,
DateOfBirth));
}
This method takes as input the UserName, FirstName, LastName, and DateOfBirth. It is useful when working with the CreateUserWizard. Later in the section I will present some examples on how to use the Extended Membership Provider where you will see that I add the custom fields in the CreateUserWizard; however, to add the user data into the default tables used by Membership API, I have to let the CreateUserWizard call the CreateUser method in the default Membership provider. Then I have to manually add the details of that user to the custom table using the above method.
The method starts by using the Membership.GetUser method to insert the UserId. Then you’ll call the method from the provider that you will build soon. ASP.NET calls the method CreateUser, which takes as input a parameter of type UserInfo class. This method is an internal object that the Extended Membership Provider uses to facilitate passing data and ASP.NET adds the custom properties to it as details for the default member.
The other version of this method takes as input all the properties of a MembershipUser, in addition to the custom properties you added. Use this method when you are creating the user programmatically and not through the CreateUserWizard (Listing 3).
The method creates the member’s record with the default properties using the Membership.CreateUser method. If the creation was successful, it calls the method inside the ExtendedMembershipProvider that adds the custom properties to the UserInfo table. If that method inserts the record successfully, it returns a new instance of the ExtendedMembershipUser object; otherwise, it deletes the record and returns a value of null.
You can see the DeleteUser method in the following code snippet:
public static bool DeleteUser(string UserName)
{
// A simple call to the default
// DeleteUser method specifying that
// all related data are to be deleted
// Get the UserId to delete
object UserId =
Membership.GetUser(UserName).ProviderUserKey;
if (UserId != null)
{
if (Membership.DeleteUser(UserName, true)
== true)
{
// You can delete from your
// custom table
// Execute your custom method
// to delete from your custom
// table
return MemberShip.Provider.Delete(UserId);
}
}
return false;
}
The above method accepts as input the UserName. DeleteUser first tries to delete the member’s record from the Membership API default database; if it deletes the record successfully, it deletes the details of that record from the UserInfo table.
The way the Update method (Listing 4) works is as follows. It assumes that ASP.NET only allows you to update the Comment, Email, IsApproved, and DateOfBirth properties for a member’s record, which is exactly what happens in the Membership API.
Instead of directly updating the record, it checks if the above listed properties of the input ExtendedMembershipUser parameter are different from those stored for that member in the database. ASP.NET uses a flag for that process-if the flag is changed, at least one of the fields of the member’s record stored in the database has changed and needs to be updated. Based on that flag, the Update method would update the member’s record in both the default database and the UserInfo table.
The GetUser method has several overloads (Listing 5 shows the main overload). The method first tries to retrieve the member’s record from the Membership API database. If it finds the record, it uses the ProviderUserKey, which is the primary key in the UserInfo table, to retrieve the detail record from the UserInfo table.
The GetAllUsers method has two overloads; Listing 6 shows the main one.
The GetAllUsers method gets a list of all member records from the default Membership database. It then creates a comma-delimited list of all member IDs and passes the list to the ExtendedMembershipProvider method to get all records whose IDs are present in the list. After that, GetAllUsers joins the data for each record from both data sources and returns a collection of ExtendedMembershipUser object.
The above explanation dealt with the ExtendedMembership class, which contains all the static methods available for the new extended Membership API. In provider model terms, the ExtendedMembership class is the Manager class. The Manager class and the following classes together form the complete ExtendedMembershipProvider:
- ExtendedMembershipProvider
- ExtendedMembershipProviderCollection
- ExtendedMembershipConfiguration
- ExtendedSqlMembershipProvider
- ExtendedMembership
The above five classes constitute the major elements of a provider model in ASP.NET 2.0. Since I am discussing the extended membership provider, there is a very good link for a set of articles on the different providers available in ASP.NET 2.0, and a Provider Toolkit that creates for you the above five classes. It mainly creates the needed skeleton for a provider and you just add your functionality to those classes using your own methods. One note about the Provider Toolkit is that you need to configure the classes included with the provider name, namespace, and the type of the provider you want Oracle, Microsoft Access, MySql, or Microsoft SQL Server to use. This configuration should take you approximately 30-60 minutes to do.
To save you time, I created a small Windows application utility (Provider ToolKit), which you can download at (http://bhaidar.net/cs/archive/2006/01/23/55.aspx). I’ve explained this utility on that site, and the Provider ToolKit can really make your life easy in developing providers for your own features in ASP.NET 2.0. Moreover, I used the toolkit to create the above classes of the ExtendedMembershipProvider.
The MembershipProvider inherits from the ProviderBase, which is the base class for all providers in ASP.NET 2.0, and contains the abstract methods listed in Table 1:
- The ExtendedSqlMembershipProvider inherits from the ExtendedMembershipProvider and implements the above methods, which the ExtendedMembership class accesses, as shown in the code snippet below:
public class ExtendedSqlMembershipProvider :
ExtendedMembershipProvider
{
…
}
The implementation follows the same technique used by the default Membership Provider API. I will just show one sample of those methods; Listing 7 shows the complete SqlMemeberShipProvider .I will discuss the CreateUser method located inside the SqlMemberShipProvider (Listing 7).
The CreateUser method takes as input a UserInfo instance, does some checking on the validity of the input parameter, and then uses the normal ADO.NET code to add the new member record to the UserInfo table. The other methods in the class follow the same steps in their implementation.
You will not have to touch the other classes that are part of the ExtendedMembershipProvider and that the utility above created. ASP.NET uses them to manage the configuration section of the new provider in the Web.config file.
Test the Extended MemberShip API
Now that you have developed the ExtendedMemberShipProvider, I’ll look at a set of examples on how to use this provider.
Before you can take the next step, you need to configure the newly developed provider in the Web.config file of the Web application. The Provider ToolKit utility creates the configuration sections needed to add to the Web.config file (Listing 8).
The Web.config file configures the current Web application to use the new ExtendedMemberShipProvider.
Now that you have configured the new provider in the Web.config file, you can start testing that provider. All the samples that I’ve presented are part of a Web application in the accompanying downloadable code of this article.
The first sample creates a new user (see the ASPX page in Figure 2 below). Figure 2 shows the CreateUserWizard that you customized to have three additional fields: FirstName, LastName, and DateOfBirth. By default, the CreateUserWizard calls the CreateUser in the Membership Provider; however, since I’m using a different provider, I will keep the default behavior of that control the same, and then I can override the event called OnCreatedUser, where I’ll add my custom fields to the table UserInfo.
Once the CreateUserWizard finishes adding the member records to the Membership-related tables, ASP.NET raises the OnCreatedUser event to process the custom fields. Listing 9 shows the implementation of that event.
The CreateUserWizard1_CreatedUser method gets the custom-field values from the CreateUserWizard control, checks if ASP.NET actually created the member records in the database, and then adds the detailed information about that member to the UserInfo table. The second sample deletes a user from the database.
Figure 3 below shows a drop-down list with all the user names found in the database. When you select a user name, you need to press the Delete button to completely delete the member record and its details from the UserInfo table.
I’ve implemented the event handler for the Delete button in the following code snippet:
protected void btnDelete_Click(object sender,
EventArgs e)
{
// Get the selected user name:
string UserName =
this.ddlUserNames.SelectedValue.ToString();
// Delete
if (ExtendedMembership.DeleteUser(UserName)
== true)
{
this.lblMsg.Text = "User deleted";
return;
}
this.lblMsg.Text = "User could not be deleted!";
}
The last sample that I’ll discuss is the Update User screen (Figure 4). The UI displays the member’s user name, Bilal, from the drop-down list. Once you select the user name, you need to click the Submit button. After that ASP.NET displays a populated form with the chosen member’s information below the drop-down list. You can now edit those fields and click “Update User” to update that user in the database.
I’ve implemented the event handler for the Update User button (Listing 10). The method above gets the field values from the form, and then calls the ExtendedMembership’s Update method to update the default values stored and the ones stored in the UserInfo table.
There are still two methods that I have not mentioned here: ExtendedMembership.GetUser and ExtendedMembership.GetAllUsers. For instance, I used the ExtendedMembership.GetAllUsers method above when I loaded the UserName drop-down list. I used the ExtendedMembership.GetUser method above to retrieve and display all the details of a member record when I selected a user name.
I suggest you look at all the above code samples in the downloadable code accompanying this article.
Advantages and Disadvantages of the Extended Membership API
As mentioned at the beginning of this article, there are two ways to extend the Membership API in ASP.NET 2.0. One extension inherits from the MembershipProvider and thus overrides all the methods that the MembershipProvider provides using custom code to extend the Membership API. I discussed the second extension throughout this article where instead of touching the default Membership API you built upon it-gaining the chance to use both the Extended Membership API and the default one in the same Web application.
From a performance point of view, the latter method inherits from MembershipProvider, and then uses other new features in ASP.NET 2.0 to process any customized data. For example, using the first method you will have to override the CreateUser method to add member records to the default Membership API database and to the UserInfo table. In that case, you must save all the custom data from the CreateUserWizard in the Profile object (another new feature in ASP.NET 2.0) in the user-interface layer. The Profile object allows you to process custom data inside the overridden CreateUser method-thus, utilizing more than one feature for the sake of extending the Membership API.
In this article I created a provider that works as a wrapper around the default MembershipProvider without touching any of its methods; instead, I used the default methods provided by the Membership provider like CreateUser, DeleteUser, etc.
In the latter method, if you want to use all the available methods in the Membership API, you have to override all the built-in methods that ship with the MembershipProvider; while in the second method, creating the wrapper, you are extending the set of those methods that affect the data collected to a member’s record. By extending the set, you get a richer environment to work with; the default Membership API methods are still usable in other places where there is no need to create, update, delete, and get a member’s record from a database. For instance, you can still use the ValidateUser method, which is automatically called by the Login control from the default Membership API, since it does nothing but check if the user name and password that the user entered are correct and valid in the default database. This method was not extended by the new provider since it can function well without the need to have any information about the new customized data added. By doing this, you are leaving most of the Membership controls to function normally. However, in the first way of extending the Membership API, you have to override the ValidateUser method if you are going to validate a user and it is something you would normally do!
Finally, the techniques I’ve discussed in this article give you the chance to use the default Membership API in addition to a set of customized methods through the Extended Membership API. The first method lacks that rich environment and requires more work to implement all built-in methods in the Membership API, if you need to utilize them, and uses the Profile object in ASP.NET 2.0 to accomplish the task.
Conclusion
To wrap things up, I have discussed one of the methods used in ASP.NET 2.0 to extend the Membership API. I’ve demonstrated extended functionality to the Membership API and left the main Membership API untouched; thus, giving you a richer environment to work with.
In addition to the main focus on extending the Membership API, I have introduced the Provider ToolKit, which you can download for free, to help you build your own provider models and presented a utility that you can use to configure and customize the Provider ToolKit to your needs in a matter of few seconds.
Last but not least, I would like to thank Peter Kellner who inspired me to write this article, Alister Jones who never stops supporting me, to the LebDev.NET user group, and to all my professors and friends. Finally, I would like to dedicate this article to the memory of my late, great friend, Jim Ross, who believed in me and never missed a chance to push me forward.