Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
738 C H A P T E R 2 1 ■ M E M B E R S H I P
</font>
</span>
</QuestionTemplate>
<SuccessTemplate>
Your password has been sent to your email address <asp:Label ID="EmailLabel" runat="server" />!
</SuccessTemplate>
</asp:PasswordRecovery>
Again, if you use controls with the appropriate ID values and use the appropriate CommandName values for buttons, you don’t have to write any code for the control to work, as in the previous examples where you didn’t use templates. In the previous code, these special controls are in bold. Some of these controls are required for the templates, and others are optional. Table 21-12 lists the controls for PasswordRecovery templates.
Table 21-12. Special Controls for PasswordRecovery Templates
|
|
|
|
Additional |
Template |
ID |
Control Type |
Required? |
Comments |
UsernameTemplate |
UserName |
System.Web.UI.Web- |
Yes |
|
|
|
Controls.TextBox |
|
|
UsernameTemplate |
SubmitButton |
All controls that support |
No |
CommandName |
|
|
event bubbling |
|
must be set to |
|
|
|
|
Submit. |
UsernameTemplate |
FailureText |
System.Web.UI.Web- |
No |
|
|
|
Controls.Literal |
|
|
QuestionTemplate |
UserName |
System.Web.UI.Web- |
No |
|
|
|
Controls.Literal |
|
|
QuestionTemplate |
Question |
System.Web.UI.Web- |
No |
|
|
|
Controls.Literal |
|
|
QuestionTemplate |
Answer |
System.Web.UI.Web- |
Yes |
|
|
|
Controls.TextBox |
|
|
QuestionTemplate |
SubmitButton |
All controls that support |
No |
CommandName |
|
|
event bubbling |
|
must be set to |
|
|
|
|
Submit. |
QuestionTemplate |
FailureText |
System.Web.UI.Web- |
No |
|
|
|
Controls.Literal |
|
|
|
|
|
|
|
Again, the submit button can be any control that supports event bubbling and a CommandName property. Typically you can use the controls Button, ImageButton, or LinkButton for this purpose. The CommandName must be set to Submit; otherwise, the command is not recognized by the control (the ID is not evaluated and can therefore be set to any value). The SuccessTemplate doesn’t require any type of control with any special IDs. Therefore, you can add any control you want there; it’s just for displaying the confirmation. In the previous example, it includes a Literal control that should display the e-mail address to which the password has been sent. You can set this Literal control through the SendingEmail event procedure. Again, you can use the FindControl method for finding the control (which is actually a child control of the password control) in the appropriate template, as follows:
protected void PasswordTemplateCtrl_SendingMail(object sender, MailMessageEventArgs e)
{
Label lbl =
C H A P T E R 2 1 ■ M E M B E R S H I P |
739 |
(Label)PasswordTemplateCtrl.SuccessTemplateContainer.FindControl(
"EmailLabel");
lbl.Text = e.Message.To[0].Address;
}
Because the control includes more than one template, you cannot call the FindControl method directly on the PasswordRecovery control instance. You have to select the appropriate template container (UserNameTemplateContainer, QuestionTemplateContainer, or SuccessTemplateContainer). Afterward, you can work with the control as usual. In the previous example, you just
set the text of the label to the first e-mail recipient. Of course, usually for a password recovery, the list has only one mail recipient.
The ChangePassword Control
You can use this control as a standard control for allowing the user to change her password. The control simply queries the user name as well as the old password from the user. Then it requires the user to enter the new password and confirm the new password. If the user is already logged on, the control automatically hides the text field for the user name and uses the name of the authenticated user. You can use the control on a secured page as follows:
<asp:ChangePassword ID="ChangePwdCtrl" runat="server" BorderStyle="groove" BackColor="aliceblue">
<MailDefinition From="pwd@apress.com" Subject="Changes in your profile" Priority="high" />
<TitleTextStyle Font-Bold="true" Font-Underline="true" Font-Names="Verdana" ForeColor="blue" />
</asp:ChangePassword>
Again, the control includes a MailDefinition property with the same settings as the PasswordRecovery control. This is because after the password has been changed successfully, the control automatically can send an e-mail to the user’s e-mail address if a mail server is configured for the web application. As all the other controls, this control is customizable through both properties and styles and a template-based approach. But this time two templates are required when customizing the control:
•The ChangePasswordTemplate displays the fields for entering the old user name and password as well as the new password including the password confirmation field.
•In the SuccessTemplate the success message is displayed.
The ChangePasswordTemplate requires you to add some special controls with special IDs and CommandName property values. You can find these control ID values and CommandName values in bold in the following code snippet:
<asp:ChangePassword ID="ChangePwdCtrl" runat="server">
<ChangePasswordTemplate>
Old Password:
<asp:TextBox ID="CurrentPassword" runat="server" TextMode="Password" /><br />
New Password:
<asp:TextBox ID="NewPassword" runat="server" TextMode="Password" /><br />
Confirmation:
<asp:TextBox ID="ConfirmNewPassword" runat="server" TextMode="Password" /><br />
<asp:Button ID="ChangePasswordPushButton" CommandName="ChangePassword"
740 C H A P T E R 2 1 ■ M E M B E R S H I P
runat="server" Text="Change Password" /> <asp:Button ID="CancelPushButton" CommandName="Cancel"
runat="server" Text="Cancel" /><br /> <asp:Literal ID="FailureText" runat="server"
EnableViewState="False" />
</ChangePasswordTemplate>
<SuccessTemplate>
Your password has been changed!</td>
<asp:Button ID="ContinuePushButton" CommandName="Continue" runat="server" Text="Continue" />
</SuccessTemplate>
</asp:ChangePassword>
Basically, the text box controls of the ChangePasswordTemplate are all required. The other controls are optional. If you select the ID properties and the CommandName properties for the buttons appropriately, you don’t have to write any additional code.
The CreateUserWizard Control
The CreateUserWizard control is the most powerful control of the login controls. It enables you to create registration pages within a couple of minutes. This control is a wizard control with two
default steps: one for querying general user information and one for displaying a confirmation message. Of course, as the CreateUserWizard inherits from the base Wizard control, you can add as many wizard steps as you want. But when you just add a CreateUserWizard control to your page as follows, the result is really amazing, as shown in Figure 21-16.
<asp:CreateUserWizard ID="RegisterUser" runat="server" BorderStyle="ridge" BackColor="aquamarine">
<TitleTextStyle Font-Bold="true" Font-Names="Verdana" /> <WizardSteps>
<asp:CreateUserWizardStep runat="server"> </asp:CreateUserWizardStep> <asp:CompleteWizardStep runat="server"> </asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
The default appearance of the control is, again, customizable through properties and styles. The control offers lots of styles, but basically the meaning of the styles is similar to the styles covered for the previous controls. In fact, this control includes the most complete list of styles, as it includes most of the fields presented in the previous controls as well. When you use the CreateUserWizard control as shown previously, you don’t need to perform any special configuration. It automatically uses the configured Membership provider for creating the user, and it includes two steps: the default CreateUserWizardStep that creates controls for gathering the necessary information and the CompleteWizardStep for displaying a confirmation message. Both steps are customizable through styles and properties or through templates. Although you can customize these two steps, you cannot remove them. If you use templates, you are responsible for creating the necessary controls, as follows:
<asp:CreateUserWizard ID="RegisterUser" runat="server" BorderStyle="ridge" BackColor="aquamarine">
<TitleTextStyle Font-Bold="True" Font-Names="Verdana" /> <WizardSteps>
<asp:CreateUserWizardStep runat="server">
<ContentTemplate>
<div align="right">
742 C H A P T E R 2 1 ■ M E M B E R S H I P
Because the control is a wizard control, the first step doesn’t require any buttons because a Next button is automatically displayed by the hosting wizard control. Depending on the configuration of the Membership provider, some of the controls are required, and others are not, as listed in Table 21-13.
Table 21-13. Required Controls and Optional Controls
ID |
Type |
Required? |
Comments |
UserName |
System.Web.UI.Web- |
|
|
|
Controls.TextBox |
Yes |
Always required |
Password |
System.Web.UI.Web- |
|
|
|
Controls.TextBox |
Yes |
Always required |
ConfirmPassword |
System.Web.UI.Web- |
|
|
|
Controls.TextBox |
Yes |
Always required |
System.Web.UI.Web- |
|
|
|
|
Controls.TextBox |
No |
Required only if the |
|
|
|
RequireEmail property of |
|
|
|
the control is set to true |
Question |
System.Web.UI.Web- |
|
|
|
Controls.TextBox |
No |
Required only if the underlying |
|
|
|
Membership provider requires a |
|
|
|
password question |
Answer |
System.Web.UI.Web- |
|
|
|
Controls.TextBox |
No |
Required only if the underlying |
|
|
|
Membership provider requires a |
|
|
|
password question |
ContinueButton |
Any control that supports |
No |
Not required at all, but if |
|
bubbling |
|
present you need to set the |
|
|
|
CommandName to Continue |
|
|
|
|
As soon as you start creating additional wizard steps, you will need to catch events and perform some actions within the event procedures. For example, if you collect additional information from the user with the wizard, you will have to store this information somewhere and therefore will need to execute some SQL statements against your database. Table 21-14 lists the events specific to the CreateUserWizard control. The control also inherits all the events you already know from the Wizard control.
Table 21-14. The CreateUserWizard Events
Event |
Description |
ContinueButtonClick |
Raised when the user clicks the Continue button in the last wizard step. |
CreatingUser |
Raised by the wizard before it creates the new user through the |
|
Membership API. |
CreatedUser |
After the control has been created successfully, the control raises this |
|
event. |
CreateUserError |
If the creation of the user was not successful, this event is raised. |
SendingEmail |
The control can send an e-mail to the created user if a mail server is |
|
configured. This event is raised by the control before the e-mail is sent |
|
so that you can modify the contents of the mail message. |
SendMailError |
If the control was unable to send the message—for example, because |
|
the mail server was unavailable—it raises this event. |
|
|
C H A P T E R 2 1 ■ M E M B E R S H I P |
743 |
Now you can just add a wizard step for querying additional user information, such as the first name and the last name, and automatically save this information to a custom database table. A valid point might be storing the information in the profile. But when running through the wizard, the user is not authenticated yet; therefore, you cannot store the information into the profile, as this is available for authenticated users only. Therefore, you either have to store it in a custom database table or include a possibility for the user to edit the profile after the registration process.
Furthermore, the CreatedUser event is raised immediately after the CreateUserWizardStep has been completed successfully. Therefore, if you want to save additional data within this event, you have to collect this information in previous steps. For this purpose, it’s sufficient to place other wizard steps prior to the <asp:CreateUserWizardStep> tag. In any other case you have to save the information in one of the other events (for example, the FinishButtonClick event). But because you cannot make sure that the user really runs through the whole wizard and clicks the Finish button, it makes sense to collect all the required information prior to the CreateUserWizardStep and then save any additional information through the CreatedUser event.
<asp:CreateUserWizard ID="RegisterUser" runat="server" BorderStyle="ridge" BackColor="aquamarine" OnCreatedUser="RegisterUser_CreatedUser"
<TitleTextStyle Font-Bold="True" Font-Names="Verdana" /> <WizardSteps>
<asp:WizardStep ID="NameStep" AllowReturn="true"> Firstname:
<asp:TextBox ID="FirstnameText" runat="server" /><br /> Lastname:
<asp:TextBox ID="LastnameText" runat="server" /><br /> Age:
<asp:TextBox ID="AgeText" runat="server" /> </asp:WizardStep>
<asp:CreateUserWizardStep runat="server">
...
</asp:CreateUserWizardStep> <asp:CompleteWizardStep runat="server">
...
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
With the previous wizard step alignment, you now can store additional information in your data store when the CreatedUser event is raised by the control, as follows:
private short _Age;
private string _Firstname, _Lastname;
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
_Age = -1;
_Firstname = _Lastname = string.Empty;
}
}
protected void RegisterUser_CreatedUser(object sender, EventArgs e)
{
// Find the correct wizard step WizardStepBase step = null;
for (int i = 0; i < RegisterUser.WizardSteps.Count; i++)
744 C H A P T E R 2 1 ■ M E M B E R S H I P
{
if (RegisterUser.WizardSteps[i].ID == "NameStep")
{
step = RegisterUser.WizardSteps[i]; break;
}
}
if (step != null)
{
_Firstname = ((TextBox)step.FindControl("FirstnameText")).Text; _Lastname = ((TextBox)step.FindControl("LastnameText")).Text; _Age = short.Parse(((TextBox)step.FindControl("AgeTExt")).Text);
// Store the information
Debug.WriteLine(string.Format("{0} {1} {2}", _Firstname, _Lastname, _Age));
}
}
In the CreatedUser event, the code just looks for the wizard step with the ID set to NameStep. Then it uses the FindControl method several times for getting the controls with the actual content. As soon as you have retrieved the controls, you can access their properties and perform any action you want with them.
In summary, the CreateUserWizard control is a powerful control based on top of the Membership API and is customizable, just as the other login controls that ship with ASP.NET 2.0. With template controls, you have complete flexibility and control over the appearance of the login controls, and the controls still perform lots of work—especially interaction with Membership—for you. And if you still want to perform actions yourself, you can catch several events of the controls.
Using the Membership Class
In the following sections of this chapter, you will learn how you can use the underlying Membership programming interface that is used by all the controls and the whole Membership API infrastructure you just used. You will see that the programming interface is simple. It consists of a class called Membership with a couple of properties and methods and a class called MembershipUser that encapsulates the properties for a single user. The methods of the Membership class perform fundamental operations:
•Creating new users
•Deleting existing users
•Updating existing users
•Retrieving lists of users
•Retrieving details for one user
Many methods of the Membership class accept an instance of MembershipUser as a parameter or return one or even a collection of MembershipUser instances. For example, by retrieving a user through the Membership.GetUser method, setting properties on this instance, and then passing it to the UpdateUser method of the Membership class, you can simply update user properties. The Membership class and the MembershipUser class both provide the necessary abstraction layer between the actual provider and your application. Everything you do with the Membership class depends on your provider. This means if you exchange the underlying Membership provider, this
C H A P T E R 2 1 ■ M E M B E R S H I P |
745 |
will not affect your application if the implementation of the Membership provider is complete and supports all features propagated by the MembershipProvider base class.
All classes used for the Membership API are defined in the System.Web.Security namespace. The Membership class is just a class with lots of static methods and properties. You will now walk through the different types of tasks you can perform with the Membership class and related classes such as the MembershipUser.
Retrieving Users from the Store
The first task you will do is retrieve a single user and a list of users through the Membership class from the Membership store. For this purpose, you just create a simple page with a GridView control for binding the users to the grid, as follows:
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">
<title>Untitled Page</title> </head>
<body>
<form id="form1" runat="server"> <div>
<asp:GridView ID="UsersGridView" runat="server"
DataKeyNames="UserName"
AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="UserName" HeaderText="Username" /> <asp:BoundField DataField="Email" HeaderText="Email" /> <asp:BoundField DataField="CreationDate"
HeaderText="Creation Date" />
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
As you can see, the GridView defines the UserName field as DataKeyName. This enables you to access the UserName value of the currently selected user directly through the grid’s SelectedValue property. As most of the methods require the user name for retrieving more details, this is definitely useful. With this page in place, you can now add the following code to the Page_Load event procedure for loading the users from the Membership store and binding them to the grid:
public partial class _Default : System.Web.UI.Page
{
MembershipUserCollection _MyUsers;
protected void Page_Load(object sender, EventArgs e)
{
_MyUsers = Membership.GetAllUsers();
UsersGridView.DataSource = _MyUsers;
if (!this.IsPostBack)
{
UsersGridView.DataBind();
}
}
}
746 C H A P T E R 2 1 ■ M E M B E R S H I P
Figure 21-17 shows the application in action.
Figure 21-17. The custom user management application in action
As you can see, the Membership class includes a GetAllUsers method, which returns an instance of type MembershipUserCollection. You can use this collection just like any other collection. Every entry contains all the properties of a single user. Therefore, if you want to display the details of a selected user, you just need to add a couple of controls for displaying the contents of the selected user in the previously created page, as follows:
Selected User:<br />
<table border="1" bordercolor="blue"> <tr>
<td>User Name:</td>
<td><asp:Label ID="UsernameLabel" runat="server" /></td> </tr>
<tr>
<td>Email:</td>
<td><asp:TextBox ID="EmailText" runat="server" /></td> </tr>
<tr>
<td>Password Question:</td>
<td><asp:Label ID="PwdQuestionLabel" runat="server" /></td> </tr>
<tr>
<td>Last Login Date:</td>
<td><asp:Label ID="LastLoginLabel" runat="server" /></td> </tr>
<tr>
<td>Comment:</td>
<td><asp:TextBox ID="CommentTextBox" runat="server" TextMode="multiline" /></td>
</tr>
<tr>
<td>
<asp:CheckBox ID="IsApprovedCheck" runat="server" Text="Approved" /> </td>
C H A P T E R 2 1 ■ M E M B E R S H I P |
747 |
<td>
<asp:CheckBox ID="IsLockedOutCheck" runat="Server" Text="Locked Out" /> </td>
</tr>
</table>
You can then catch the SelectedIndexChanged event of the previously added GridView control for filling these fields with the appropriate values, as follows:
protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)
{
if (UsersGridView.SelectedIndex >= 0)
{
MembershipUser Current = _MyUsers[(string)UsersGridView.SelectedValue];
UsernameLabel.Text = Current.UserName; PwdQuestionLabel.Text = Current.PasswordQuestion;
LastLoginLabel.Text = Current.LastLoginDate.ToShortDateString(); EmailText.Text = Current.Email;
CommentTextBox.Text = Current.Comment; IsApprovedCheck.Checked = Current.IsApproved; IsLockedOutCheck.Checked = Current.IsLockedOut;
}
}
As you can see, the MembershipCollection object requires the user name for accessing users directly. Methods from the Membership class such as GetUser require the user name as well. Therefore, you used the UserName field as content for the DataKeyNames property in the GridView previously. With an instance of the MembershipUser in your hands, you can access the properties of the user as usual.
Updating Users in the Store
Updating a user in the Membership store is nearly as easy as retrieving the user from the store. As soon as you have an instance of MembershipUser in your hands, you can update properties such as the e-mail and comments as usual. Then you just call the UpdateUser method of the Membership class. You can do that by extending the previous code by adding a button to your page and inserting the following code in the button’s Click event-handling routine:
protected void ActionUpdateUser_Click(object sender, EventArgs e)
{
if (UsersGridView.SelectedIndex >= 0)
{
MembershipUser Current = _MyUsers[(string)UsersGridView.SelectedValue];
Current.Email = EmailText.Text;
Current.Comment = CommentText.Text;
Current.IsApproved = IsApprovedCheck.Checked;
Membership.UpdateUser(Current);
// Refresh the grids view UsersGridView.DataBind();
}
}
