Using login controls for an existing data source by creating a custom membership provider in ASP.NET 2.0

Article translations Article translations
Article ID: 910440 - View products that this article applies to.
ASP.NET Support Voice column

Using login controls for an existing data source by creating a custom membership provider in ASP.NET 2.0

To customize this column to your needs, we want to invite you to submit your ideas about topics that interest you and issues that you want to see addressed in future Knowledge Base articles and Support Voice columns. You can submit your ideas and feedback using the Ask For It form. There's also a link to the form at the bottom of this column.
Expand all | Collapse all

On This Page

Introduction

Hello and welcome back! My name is Parag Agarwal, and I'm a support engineer here at Microsoft. This month we are going to discuss another cool feature in Microsoft ASP.NET 2.0, managing providers for login controls.

Overview

In this month's column, I will discuss the following topics:
  • Brief overview of login controls
  • Brief overview of the provider model in ASP.NET 2.0
  • Walkthrough on creating a custom provider that can be used by login controls with an existing data source

Login controls

It's a very common requirement to have login functionality in almost every Web application. Before ASP.NET 2.0 was released, we used to design user interfaces (UI) for authenticating the user. This involved writing a lot of redundant code. To avoid this, ASP.NET 2.0 provides a complete login solution in the form of a bunch of server controls for Web applications that require no programming. Internally, these controls are responsible for rendering the appropriate UI where a user can enter his or her credentials and validate them. Now, we don't have to design the UI as a page developer, and we don't need to take care of authenticating the user by writing our own code. The underlying provider model used by the Login control takes care of that. We will see how the Login control uses the providers for authenticating the user in the next section. You can find more information about login controls and how we can use them at the following Web site:
http://msdn2.microsoft.com/en-us/library/ms178329.aspx

Provider model

The provider model allows developers to build pluggable software. It is basically intended to decouple an abstraction from the implementation so that both pieces can vary independently. In order to do this, ASP.NET provides certain abstract base classes that have all the abstract methods and properties required to be implemented by the deriving class that provides the implementation for those methods and properties.

For more information on abstract base classes, visit the following Web site:
http://msdn2.microsoft.com/en-us/library/k535acbf(vs.71).aspx
So, to summarize, providers are used as intermediaries by controls to interact with a data store. They provide abstraction between the application and the data source in the same way device drivers provide abstraction from a hardware device.

Because this article talks about membership providers, we will discuss what classes ASP.NET 2.0 provides for the membership feature. The ASP.NET 2.0 membership feature defines an abstract base class called the MembershipProvider class. Furthermore, MembershipProvider derives from a different base class called the ProviderBase class, which is a common class to all the providers. Therefore, developers can create their own provider classes by deriving the existing MembershipProvider class.

For more information on the MembershipProvider and ProviderBase classes, visit the following Web sites:
http://msdn2.microsoft.com/en-us/library/system.web.security.membershipprovider.aspx

http://msdn2.microsoft.com/en-us/library/system.configuration.provider.providerbase.aspx
Once we have defined the membership provider, it must be described in a configuration file, either in Machine.config (for all Web applications) or in Web.config (for a specific Web application). The appropriate provider is instantiated at run time from the information provided in the configuration file by ASP.NET. However, it is possible to change the provider dynamically at run time as well.

For more information on specifying the configuration settings for a membership provider, visit the following Web site:
http://msdn2.microsoft.com/en-us/library/ms178329.aspx
Right now, the Login control is shipped with two built-in membership providers that use a specific data scheme/data structure: However, if we want to work with an existing database structure, we can easily code a custom membership provider to get login controls to talk to the old database structure.

Creating a custom membership provider

Now that we have enough information on login controls and the underlying provider model that they use, let's create a custom membership provider to get existing login controls to work against a custom data store.

Note The custom provider will use a SQL Server database called TestDB. TestDB will have a table named Users with the fields UserID, UserName, and Password and other information, such as e-mail ID and address.
  1. Start Microsoft Visual Studio 2005.
  2. Create a class library project, and give it a name, for example, CustomMembershipProviderLib.
  3. Add a source file to the project, for example, CustomMembershipProvider.cs.
  4. Include System.Web and System.Configuration in the references section.
  5. Verify that the following namespaces are included in the CustomMembershipProvider.cs file.
    using System;
    using System.Web;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web.Security;
    using System.Collections.Specialized;
    using System.Data.SqlClient;
  6. Inherit the CustomMembershipProvider class with the MembershipProvider class.
    class CustomMembershipProvider : 
    MembershipProvider
  7. As we already know, MembershipProvider is an abstract class, so we need to override all the abstract methods in the CustomMembershipProvider class. There is a very cool feature in Visual Studio 2005 that does this automatically. As soon as you extend any abstract class, just right-click Abstract class, and then click Implement Abstract Class. This automatically places declarations for all the abstract methods. You will notice that the body for each method contains a common line of code.
    throw new Exception("The method or operation is not implemented.");
    This indicates what features are supported by the custom provider.

    Note Implementation for the Initialize method is mandatory.
  8. In the custom provider, we will concentrate on providing a few features such as the following:
    • Creating a new user by using the CreateUserWizard control
    • Validating the user credentials by using the Login control
    We will implement these features one by one. First, implement the Initialize method. This method is called by ASP.NET when the provider is loaded. Also, providers are loaded when the application uses them for the first time, and they are created once per application domain.
    public override void Initialize(string name,NameValueCollection config)
    {
        // Verify that config isn't null
        if (config == null)
            throw new ArgumentNullException("config");
    
        // Assign the provider a default name if it doesn't have one
        if (String.IsNullOrEmpty(name))
            name = "AspNetCustomMembershipProvider";
    
        // Add a default "description" attribute to config if the
        // attribute doesn't exist or is empty
        if (string.IsNullOrEmpty(config["description"]))
        {
            config.Remove("description");
            config.Add("description", "Custom SQL Provider");
        }
    
        // Call the base class's Initialize method
        base.Initialize(name, config);
    }
  9. Next, implement the ValidateUser method. It takes the input user name and password and verifies that the membership data source contains a matching user name and password. If the method returns true, the Login control allows the user to pass through the verification. Otherwise, it asks for the credentials again.
    public override bool ValidateUser(string username, string password)
    {
        SqlConnection cnn = null;
        SqlCommand cmd = null;
        bool userExists = true;
        try
        {
            cnn = new SqlConnection();
            cnn.ConnectionString = "connection string for the existing data source";
            cnn.Open();
            string selectQry = "Select query for username and password";
            cmd = new SqlCommand(selectQry, cnn);
            SqlDataReader rdr = cmd.ExecuteReader();
            if (!rdr.Read())
                userExists = false;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            cmd.Dispose();
            cnn.Close();
        }
        return userExists;
    }
  10. Implement one more method called CreateUser that is called by the CreateUserWizard control. It takes input, such as user name, password, e-mail address, and other information, and adds a new user to the existing membership data source. It returns the MembershipUser object, which represents a newly created user. It also sets MembershipCreateStatus, which tells whether the user was successfully created. If the user was not successfully created, we can specify the reason.
    public override MembershipUser CreateUser(string username, string
              password, string email, string passwordQuestion, string
              passwordAnswer, bool isApproved, object providerUserKey,
              out MembershipCreateStatus status)
    {
        SqlConnection cnn = null;
        SqlCommand cmd = null;
        MembershipUser newUser = null;
        try
        {
            cnn = new SqlConnection();
            cnn.ConnectionString = "connection string for the existing data source";
            cnn.Open();
            string insertQry = "Prepare the Insert query...";
            cmd = new SqlCommand(insertQry, cnn);
            cmd.ExecuteNonQuery();
    
            // Right now I am giving default values for DateTime
            // in Membership constructor.
            newUser = new MembershipUser(
            "AspNetCustomMembershipProvider",
            username, null, String.Empty, String.Empty,
             String.Empty, true, false, DateTime.Now,
             DateTime.Now, DateTime.Now, DateTime.Now,
             DateTime.Now
            );
            status = MembershipCreateStatus.Success;
        }
        catch (Exception ex)
        {
            status = MembershipCreateStatus.ProviderError;
            newUser = null;
            throw ex;
        }
        finally
        {
            cmd.Dispose();
            cnn.Close();
        }
        return newUser;
    }
  11. The rest of the methods look like those given below. If you wish, you can implement any of them.
    // MembershipProvider Properties
    public override string ApplicationName
    {
        get { throw new NotSupportedException(); }
        set { throw new NotSupportedException(); }
    }
    
    public override bool EnablePasswordRetrieval
    {
        get { return false; }
    }
    
    public override bool EnablePasswordReset
    {
        get { return false; }
    }
    
    public override int MaxInvalidPasswordAttempts
    {
        get { throw new NotSupportedException(); }
    }
    
    public override int MinRequiredNonAlphanumericCharacters
    {
        get { throw new NotSupportedException(); }
    }
    
    public override int MinRequiredPasswordLength
    {
        get { throw new NotSupportedException(); }
    }
    
    public override int PasswordAttemptWindow
    {
        get { throw new NotSupportedException(); }
    }
    
    public override MembershipPasswordFormat PasswordFormat
    {
        get { throw new NotSupportedException(); }
    }
    
    public override string PasswordStrengthRegularExpression
    {
        get { throw new NotSupportedException(); }
    }
    
    public override bool RequiresQuestionAndAnswer
    {
        get { return false; }
    }
    
    public override bool RequiresUniqueEmail
    {
        get { return false; }
    }
    
    public override MembershipUser GetUser(string username,
        bool userIsOnline)
    {
        throw new NotSupportedException();
    }
    
    public override MembershipUserCollection GetAllUsers(int pageIndex,
        int pageSize, out int totalRecords)
    {
        throw new NotSupportedException();
    }
    
    public override int GetNumberOfUsersOnline()
    {
        throw new NotSupportedException();
    }
    
    public override bool ChangePassword(string username,
        string oldPassword, string newPassword)
    {
        throw new NotSupportedException();
    }
    
    public override bool
        ChangePasswordQuestionAndAnswer(string username,
        string password, string newPasswordQuestion,
        string newPasswordAnswer)
    {
        throw new NotSupportedException();
    }
    
    public override bool DeleteUser(string username,
        bool deleteAllRelatedData)
    {
        throw new NotSupportedException();
    }
    
    public override MembershipUserCollection
        FindUsersByEmail(string emailToMatch, int pageIndex,
        int pageSize, out int totalRecords)
    {
        throw new NotSupportedException();
    }
    
    public override MembershipUserCollection
        FindUsersByName(string usernameToMatch, int pageIndex,
        int pageSize, out int totalRecords)
    {
        throw new NotSupportedException();
    }
    
    public override string GetPassword(string username, string answer)
    {
        throw new NotSupportedException();
    }
    
    public override MembershipUser GetUser(object providerUserKey,
        bool userIsOnline)
    {
        throw new NotSupportedException();
    }
    
    public override string GetUserNameByEmail(string email)
    {
        throw new NotSupportedException();
    }
    
    public override string ResetPassword(string username,
        string answer)
    {
        throw new NotSupportedException();
    }
    
    public override bool UnlockUser(string userName)
    {
        throw new NotSupportedException();
    }
    
    public override void UpdateUser(MembershipUser user)
    {
        throw new NotSupportedException();
    }
  12. Compile the class library project. It will generate the DLL output.
  13. Open an existing Web site, or create a new Web site.
  14. Add the DLL reference in the Web site.
  15. Register the provider in the Web.config file as follows.
    <membership defaultProvider="AspNetCustomMembershipProvider">
      <providers>
        <clear />
        <add  name="AspNetCustomMembershipProvider"	type="CustomMembershipProvider"/>
      </providers>
    </membership>
  16. Add a Web Forms page named Login.aspx where the Login control can be used.
    <form id="Form1" runat="server">
      <div>
        <asp:Login ID="Login1" runat="server"></asp:Login>
      </div>
    </form>
  17. Add another Web Forms page named CreateUser.aspx where the CreateUserWizard control can be used.
    <form id="Form1" runat="server">
      <div>
        <asp:CreateUserWizard ID="CreateUserWizard1" runat="server"></asp:CreateUserWizard>
      </div>
    </form>
  18. Run both of the Web Forms pages, and you will see the output.
If you are not using Visual Studio, you can perform the following steps:
  1. Open any text editor.
  2. Create a file named CustomMembershipProvider.cs, and follow the instructions given in steps 5 through 17.
  3. Create a directory under the wwwroot folder.
  4. Start Microsoft Internet Information Services (IIS) Manager, and mark the new directory as the virtual root directory. Also, ensure that it is configured to run under the Microsoft .NET Framework 2.0 in case another version of the .NET Framework is installed on the computer.
  5. Copy the Web Forms pages and Web.config in that directory.
  6. Create an App_Code folder under the new directory.
  7. Copy the CustomMembershipProvider.cs file in the App_Code folder.
  8. Run the CreateUser.aspx Web Forms page from IIS Manager.

Conclusion

That's all for now on custom membership providers. I hope that this column will help you understand the basics of creating custom membership providers and how they provide abstraction to the end user.

Thank you for your time. We expect to write more about the providers that are provided by ASP.NET 2.0 and how we can extend them to customize their behavior according to our needs.

For more information about providers, visit the following Web sites:
Provider Toolkit
http://msdn2.microsoft.com/en-us/asp.net/aa336558.aspx

Provider Model Design Pattern and Specification, Part 1
http://msdn2.microsoft.com/en-us/library/ms972319.aspx

Provider Design Pattern, Part 2
http://msdn2.microsoft.com/en-us/library/ms972370.aspx
As always, feel free to submit ideas on topics you want addressed in future columns or in the Knowledge Base using the Ask For It form.

Properties

Article ID: 910440 - Last Review: November 22, 2007 - Revision: 1.4
APPLIES TO
  • Microsoft ASP.NET 2.0
Keywords: 
kbprogramming kbsecurity kbcode kbhowto KB910440
Retired KB Content Disclaimer
This article was written about products for which Microsoft no longer offers support. Therefore, this article is offered "as is" and will no longer be updated.

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com