ne feature introduced in ASP.NET 2.0 is the use of the “provider model” to provide maximum flexibility and extensibility to your Web applications. Using the provider model, developers can easily extend the capabilities provided by the ASP.NET 2.0 runtime, in many different ways. For example, you can extend the provider model to store the membership information for your Web site users in a custom data store, rather than the default SQL Server Express 2005 database.
To understand how the provider model works, consider the new Login controls in ASP.NET 2.0. The relationships between the Login controls and their underlying APIs are shown in Figure 1.
|Figure 1. The Membership Provider Model: Everything involved in the use of the ASP.NET provider model is shown.
At the top level are the various Login controls themselves. Underlying the controls are the various APIs that perform the work needed to implement the functions of the various controls. The Membership class takes care of tasks such as adding and deleting users, while the MembershipUser class is responsible for managing users’ information, such as passwords, password questions, etc. The Membership and the MembershipUser classes use one of the membership providers to save user information to data stores.
In Visual Studio 2005 (beta 2 as at this writing), ASP.NET 2.0 ships with one default membership provider?SQL Express Membership Provider. The role of the provider is to act as a bridge between the membership APIs and the membership data, so that developers need not be concerned with the details of data storage and retrieval. Think of the membership provider as a middle-man in charge of all communications between the APIs and the data.
However, if the provider supplied with ASP.NET 2.0 does not meet your needs, you can write your own custom provider. In this article, I will show you how to build a custom membership provider that uses an Access database (rather than the default SQL Express 2005 database) to store users’ information. You can then adapt the concepts covered in this article to use any data source (such as Oracle database, XML files, or even plain text files) you want for your membership provider.
Creating the Project
As there are numerous controls in the Login controls family that use the membership provider, for simplicity I will only implement a subset of the features available in the membership provider. For this project, I will implement the:
- ValidateUser() function?this function is used by the Login control to allow users to authenticate themselves.
CreateUser() function?this function is used by the CreateUserWizard control to allow users to register for a new account.
Using Visual Studio 2005 Beta 2, create a new Web site project using the Visual Basic language. Save the project as C:NewMembershipProvider. Populate the Default.aspx Web form with the CreateUserWizard control (see Figure 2) and apply the Elegant scheme using the Auto Format link in the Smart tag of the control.
Also, add the LoginView control as shown in Figure 3 to the bottom of Default.aspx. In the AnonymousTemplate view of the LoginView control, add the LoginStatus control. The LoginView control allows you to display different messages depending on whether the user is authenticated (messages to display are set in the LoggedInTemplate view; see below) or not (messages are set in the AnonymousTemplate view). The LoginStatus control displays a hyperlink to redirect the user to a login page if he is not authenticated and allows the user to logout if he is authenticated.
In the LoggedInTemplate view of the LoginView control, type the message “You are logged in as” and add the LoginName and LoginStatus control as shown in Figure 4. The LoginName control will display the user’s login ID.
Creating the Database
The custom provider you are building will save its data into an Access database. Therefore, you need to create an Access database. To maintain consistency with the location of databases in a typical ASP.NET 2.0 web application, save this database in C:NewMembershipProviderApp_Data. Give it the name Members.mdb.
Create a new table in the Members.mdb database and name it Membership. Define the table as shown in Table 1. :
Table 1. Fields for the Membership table of memers.mdb.
Implementing Your Own Custom Provider
Before you learn how to implement your own custom provider, you need to understand the provider object model in ASP.NET 2.0. The default membership provider in Beta 2 is known as SqlMembershipProvider and it contains methods and properties specific to using SQL Express 2005 as a data store for membership information. It inherits from the MembershipProvider class, which provides data store-agnostic methods for storing membership information. The MembershipProvider class itself inherits from the ProviderBase class, the root of all providers (see Figure 6).
|Figure 6. Providers: The provider model class hierarchy is shown.
If you just want to modify some of the implementations of the existing SQL membership provider (such as changing the way passwords are hashed or encrypted), then you simply need to create a class that inherits from SqlMembershipProvider and override the methods of concern, like this:
Public Class ModifiedSqlMembershipProvider Inherits SqlMembershipProvider Public Overrides Function CreateUser (...) ... End Function ...End Class
However, if you do not want to use the SqlMembershipProvider supplied in Beta 2 (for example if you are creating your own custom data store for saving members’ information, like using an Access database), then you need to implement your own class and inherit from the MembershipProvider class. The MembershipProvider class contains methods and properties for manipulating membership information, and when inheriting from this class you need to provide the implementation for all its methods and properties.
For simplicity, I will only provide the implementation for allowing users to register for a new account and to login to a Web site. For all of it, I will use the CreateUserWizard and Login controls.
Right-click on the project name in Solution Explorer and select Add New Item?. Select the Class template and name it as AccessMembershipProvider.vb. Click OK. You will be prompted to save the file in the App_Code folder. Click Yes.
First, import the System.Data namespace for database access:
Next, inherit from the MemberShipProvider class:
Public Class AccessMembershipProvider Inherits MembershipProviderEnd Class
You will notice that Visual Studio 2005 will automatically add all the stubs for the methods that you need to override in the base class.
As the SqlMembershipProvider is the default provider used by the various security controls, you need to register the new membership provider you are creating in this article so that the ASP.NET runtime will now use your custom provider. To do so, add a Web.config file to your project (right-click on project name in Solution Explorer and select Add New Item?. Select the Web Configuration File template and click OK.
Note the following:
- Authentication mode should be “Forms.”
- Add the new provider “AccessMembershipProvider” using the
- Specify the requiresQuestionAndAnswer attribute to “true” (you will see how this is used in later in this article).
- Specify the connection string to the Access database in the connectionString attribute.
- Specify the default membership provider as “AccessMembershipProvider” using the defaultProvider attribute in the
In general, you can specify any attribute name you like in the
In AccessMembershipProvider.vb, add the following private member variables:
'---for database access use--- Private connStr As String Private comm As New OleDb.OleDbCommand Private _requiresQuestionAndAnswer As Boolean Private _minRequiredPasswordLength As Integer
Add the following Initialize() method:
Public Overrides Sub Initialize( _ ByVal name As String, _ ByVal config As _ System.Collections.Specialized.NameValueCollection) '===retrives the attribute values set in 'web.config and assign to local variables=== If config("requiresQuestionAndAnswer") = "true" Then _ _requiresQuestionAndAnswer = True connStr = config("connectionString") MyBase.Initialize(name, config)End Sub
The Initialize() method will be fired when the provider is loaded at runtime. The values specified in the attributes of the
If config("requiresQuestionAndAnswer") = "true" Then _ _requiresQuestionAndAnswer = True connStr = config("connectionString")
In this case, I have retrieved the requiresQuestionAndAnswer and connectionString attribute values and saved them to the private variables declared earlier.
The requiresQuestionAndAnswer attribute value is used to set the RequiresQuestionAndAnswer property:
Public Overrides ReadOnly Property _ RequiresQuestionAndAnswer() _ As Boolean Get If _requiresQuestionAndAnswer = True Then Return True Else Return False End If End GetEnd Property
It is important to set this property; if you don’t, the password question and password answer textboxes will not be shown in the CreateUserWizard control (see Figure 7).
|Figure 7. Question and Answer: If you don’t set the RequiresQuestionandAnswer property the CreateUserWizard control will display without the password question and answer textboxes.
The CreateUserWizard control will invoke the CreateUser() method when the user clicks on the Create User button, so the next step you need to perform is to code this method (see Listing 1).
In this method, you can perform all the checks to ensure that the user has entered all the necessary information, such as the minimum number of characters for the password, to ensure that the password question and answer are supplied, etc. Once all these data are available, you can proceed to add the user information to your own custom data store?an Access database, in this case.
If the creation of the user is successful, you need to return a MembershipUser object. You also need to return the status of the creation through the status parameter (passed in by reference (ByRef)).
To ensure that the Login control validates user login through your custom database, you also need to implement the ValidateUser() method:
Public Overrides Function ValidateUser( _ ByVal username As String, _ ByVal password As String) As Boolean Dim conn As New OleDb.OleDbConnection(connStr) Try conn.Open() Dim sql As String = _ "Select * From Membership WHERE " & _ "username=@username AND password=@password" Dim comm As New OleDb.OleDbCommand(sql, conn) comm.Parameters.AddWithValue("@username", _ username) comm.Parameters.AddWithValue("@password", _ password) Dim reader As OleDb.OleDbDataReader = _ comm.ExecuteReader If reader.HasRows Then Return True Else Return False End If conn.Close() Catch ex As Exception Console.Write(ex.ToString) Return False End TryEnd Function
|Figure 8. Registering: This is the UI for creating a new user account.
The ValidateUser() method is fired when the user clicks on the Log In button in the Login control. Here I basically retrieve any record that matches the supplied username and password. If there is at least one record, then the login is successful and the function returns a “true” value.
Testing the Application
That’s it! Press F5 to test and debug the application. Figure 8 shows what happens when you try to register for a new user account.
Once the account is created, click on the Login hyperlink to test the new account. If you are successfully authenticated, you should see the login name (in my case it is “weimeng”) used to login to the site (see Figure 9).
|Figure 9. Logging In: This is the UI for logging in to the site using an existing username/password combination.
To verify that you are actually using the new membership provider, open the Access database and check that the user account is present in the table.
In this article, you have seen that creating your own custom provider is not a very difficult task. Creating a custom provider allows you to choose the best solution for your application, without being restricted to what is available. And this is precisely the design goal of the ASP.NET team?make Web development easy and at the same time extensible.