

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.
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:
- CreateUser() function—this function is used by the CreateUserWizard control to allow users to register for a new account.
- ValidateUser() function—this function is used by the Login control to allow users to authenticate themselves.
![]() Figure 2. Setting it Up: The CreateUserWizard control is shown using the Elegant scheme, which quickly sets up a page to register a new user. | ![]() Figure 3. Log Services: The LoginView and LoginStatus controls are added to the AnonymousTemplate view in order to give the user a way to log in and out of the service. |
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.
![]() Figure 4. Username View: Adding the LoginName and LoginStatus controls in LoggedInTemplate view will display the username of a logged in user. | ![]() Figure 5. Logging In: The Login.aspx page uses only the Login control, also with the Elegant scheme applied, to allow users who already have accounts to authenticate. |
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:/NewMembershipProvider/App_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.
Field Name | Data Type | Field Size |
Username (key) | Text | 8 |
Password | Text | 8 |
Text | 50 | |
passwordQuestion | Text | 50 |
passwordAnswer | Text | 50 |
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. |
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:
Imports System.Data
Next, inherit from the MemberShipProvider class:
Public Class AccessMembershipProvider
Inherits MembershipProvider
End 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.
<system.web>
<authentication mode="Forms"/>
<membership
defaultProvider="AccessMembershipProvider" >
<providers>
<add name="AccessMembershipProvider"
type="AccessMembershipProvider"
requiresQuestionAndAnswer="true"
connectionString="Provider=Microsoft.Jet.
OLEDB.4.0;Data Source=C:/NewMembershipProvider/
App_Data/Members.mdb;Persist Security
Info=False" />
</providers>
</membership>
</system.web>
Note the following:
- Authentication mode should be "Forms."
- Add the new provider "AccessMembershipProvider" using the <add> element.
- 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 <membership> element.
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 <add> element in
Web.config will be passed into this method through the
config parameter. To retrieve these attributes values, specify the
config parameter using the name of the attribute, like this:
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 Get
End 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. |
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 Try
End Function
![]() | |
Figure 8. Registering: This is the UI for creating a new user account. |
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.