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:
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:
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:
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:
Right now, the
Login control is shipped with two built-in membership providers that
use a specific data scheme/data structure:
- Active Directory
The associated provider class is System.Web.Security.ActiveDirectoryMembershipProvider. - Microsoft SQL Server
The associated provider class is System.Web.Security.SqlMembershipProvider.
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.
- Start Microsoft Visual Studio 2005.
- Create a class library project, and give it a name, for
example, CustomMembershipProviderLib.
- Add a source file to the project, for example,
CustomMembershipProvider.cs.
- Include System.Web and System.Configuration in the references section.
- 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;
- Inherit the CustomMembershipProvider class with the MembershipProvider class.
class CustomMembershipProvider :
MembershipProvider
- 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. - 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);
}
- 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;
}
- 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;
}
- 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();
}
- Compile the class library project. It will generate the DLL
output.
- Open an existing Web site, or create a new Web
site.
- Add the DLL reference in the Web site.
- Register the provider in the Web.config file as follows.
<membership defaultProvider="AspNetCustomMembershipProvider">
<providers>
<clear />
<add name="AspNetCustomMembershipProvider" type="CustomMembershipProvider"/>
</providers>
</membership>
- 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>
- 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>
- 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:
- Open any text editor.
- Create a file named CustomMembershipProvider.cs, and follow
the instructions given in steps 5 through 17.
- Create a directory under the wwwroot folder.
- 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.
- Copy the Web Forms pages and Web.config in that
directory.
- Create an App_Code folder under the new
directory.
- Copy the CustomMembershipProvider.cs file in the App_Code
folder.
- 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: