A Better ASP.Net Member/Role Management Page Pt. 4
Prologue
**Author Note – If you are bewildered by a line (perhaps two) that are red and bold, don’t they that they’re errors, they are corrections/additions. Please see the comments, after the article for comments regarding the highlighted line(s).** (If you want to follow along with the rest of the series, you can create the management page yourself by reviewing all the posts for part 1, part 2, part 3, part 4, part 5, part 6, part 7, part 8 and part 9).
Previously, we started work on “A better ASP.Net Member/Role Management Page”. So far, we looked at requirements in Part 1, we then added functionality in Part 2 to display, filter and search existing user and finally, in Part 3, we added functionality to create and delete users from the system. In this post, we’ll be adding functionality to edit a user’s account information.
Problem
In this post, we’ll be creating the functionality we need to edit users. This is perhaps one of the most convoluted lengthy parts of our series. We’ll run into a number of places where the functionality isn’t as simple as it seems or that we have limits based on how we’ve configured our provider. Once we’re finished with this post, we should have completed all of our requirements to complete #3 on our criteria list, with the exception of renaming (which will be addressing in the next post).
Solution
Let's get started by creating all the front end markup for our Edit User dialog. We’re going to introduce a lot of functionality in this post, and this markup will encapsulate most of it. The front-end code is kind of unwieldy, but here it is. I’ll point out a couple of noteworthy items after the listing. Add the following after your last section, but before the closing tag for our formatEverything panel:
<%-- Edit User Dialog –%>
<asp:UpdatePanel ID="upnlEditUser" runat="server"
ChildrenAsTriggers="true">
<ContentTemplate>
<asp:Panel ID="pnlEditUser" runat="server" CssClass="modalPopup"
style="display:none;">
<asp:Table runat="server">
<asp:TableRow>
<asp:TableCell Width="75%">
<asp:Table runat="server">
<asp:TableRow>
<asp:TableCell ColumnSpan="3">
<b>Edit User Details:</b>
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Username:
</asp:TableCell>
<asp:TableCell>
<asp:Label ID="lblUsernameEdit"
runat="server" Text="" />
</asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lnkChangeUsername"
runat="server" text="Change Username" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Email:
</asp:TableCell>
<asp:TableCell>
<asp:TextBox ID="txtEmail" runat="server" />
<cc1:TextBoxWatermarkExtender ID="tweEmail"
runat="server" TargetControlID="txtEmail”
WatermarkCssClass="watermarked"
WatermarkText="Enter eMail">
</cc1:TextBoxWatermarkExtender>
</asp:TableCell>
<asp:TableCell>
<asp:RegularExpressionValidator ID="revEmail"
runat="server" ErrorMessage="eMail not valid."
ControlToValidate="txtEmail" Display="Dynamic"
ValidationExpression="\S+@\S+\.\S+" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Description:
</asp:TableCell>
<asp:TableCell>
<asp:TextBox ID="txtDescription" runat="server" />
<cc1:TextBoxWatermarkExtender ID="tweDescription"
TargetControlID="txtDescription"
WatermarkCssClass="watermarked"
WatermarkText="Enter description"
runat="server">
</cc1:TextBoxWatermarkExtender>
</asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lnkResetPassword"
runat="server" Text="Reset Password" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Password:
</asp:TableCell>
<asp:TableCell>
<asp:Label ID="lblPassword" runat="server"
Text="********" />
</asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lnkChangePassword"
runat="server" Text="Change Password" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Security Question:
</asp:TableCell>
<asp:TableCell>
<asp:Label ID="lblSecurityQuestion"
runat="server" Text="" />
</asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lnkChangeSecurityQuestion"
runat="server" Text="Change Security Q/A" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Active User:
</asp:TableCell>
<asp:TableCell>
<asp:CheckBox ID="chkActiveUser"
runat="server" Text="" Checked="true" />
</asp:TableCell>
<asp:TableCell></asp:TableCell>
</asp:TableRow>
</asp:Table>
</asp:TableCell>
<asp:TableCell>
<b>
<asp:Label ID="lblEditUserSelectRoles"
runat="server" Text=”Roles” />
</b>
<br />
<asp:Label ID="lblEditAddUserToRole"
runat="server" Text="" />
<br />
<asp:Repeater ID="rptEditUserRoles" runat="server">
<ItemTemplate>
<asp:CheckBox ID="chkRole" runat="server"
Text="<%# Container.DataItem.ToString() %>" />
<br />
</ItemTemplate>
</asp:Repeater>
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell ColumnSpan="2" HorizontalAlign="Center">
<asp:LinkButton ID="lnkEditUserSave" runat="server"
Text="Save" />
<asp:LinkButton ID="lnkEditUserCancel" runat="server"
Text="Cancel" CausesValidation=”False” />
</asp:TableCell>
</asp:TableRow>
</asp:Table>
</asp:Panel>
<asp:Button ID="btnFakeMPEEditUser" runat="server"
style="display:none;" />
<cc1:ModalPopupExtender ID="mpeEditUser" runat="server"
TargetControlID="btnFakeMPEEditUser"
PopupControlID="pnlEditUser"
BackgroundCssClass="modalBackground">
</cc1:ModalPopupExtender>
</ContentTemplate>
</asp:UpdatePanel>
I’m going against my anti-table-as-layout leanings again, and doubling up on that breach of protocol by nesting tables. In our code, we create a two row table, in the first row, we add almost everything except the buttons that will save or cancel. In the second row we add the save and cancel buttons. The first row has 2 cells one that holds a table with all the user information, and another with the role information. You’ll notice that we have a couple of WatermarkExtenders for some of the text boxes.
We also have one minor change to our cancel button over other’s we’ve used. You’ll notice that we add CausesValidation=”false”. This will allow our button to close the dialog even if the entries in our form are not valid (otherwise we’d have to get everything valid before we can even cancel).
You’ll also notice that we have a number of items that we’ll link to yet other dialogs that provide the actual functionality (i.e. ‘change password’, ‘change username’ and ‘change security Q/A’). These are a little more tricky to implement. For this reason, the actual functionality for these will not be directly effected by the ‘Edit User’ dialog, rather they will work semi-independently of the dialog (i.e. they will be available from the ‘Edit User’ dialog, but will not function based on clicking save/cancel on the ‘Edit User’ dialog).
With the front-end markup code finished, we’re ready to get started on the back-end coding. Flip over to your code behind and let’s get started. First we’ll want to hookup our ‘Edit User’ link to open our new dialog. If you remember, our Delete User link’s event handler was the GridUsersClick subroutine. We’ll need to add another if statement to our GridUsersClick event handler to catch the ‘Edit User’ click. Add the following to your GridUsersClick subroutine, it doesn’t matter if it is before or after the DeleteUser’s if statement:
If e.CommandName.Equals("EditUser") Then
SetupEditUser(e.CommandArgument)
SetUI(SetUIModes.EditUser)
End If
We’ll call a helper function to do a number of little things that it takes to populate our ‘Edit User’ dialog. We are also passing the CommandArgument from the GridUsersClick to the routine, this will pass our username over to the setup routine so we know which user’s information to pull. Create our SetupEditUser subroutine as follows:
Private Sub SetupEditUser(ByVal sUsername As String)
lblUsernameEdit.Text = sUsername
Dim theUser As MembershipUser = Membership.GetUser(sUsername)
If IsNothing(theUser) Then Exit Sub
txtEmail.Text = theUser.Email
txtDescription.Text = theUser.Comment
chkActiveUser.Checked = theUser.IsApproved
lblSecurityQuestion.Text = theUser.PasswordQuestion
SetDataSource(Roles.GetAllRoles, ROLES_DATA_SOURCE)
BindRepeater(rptEditUserRoles, ROLES_DATA_SOURCE)
SetupEditUserRoles(sUsername)
End Sub
First we grab the username and use it to retrieve the membership information for the user. The object that is returned has all the user’s accessible information. If a valid object is returned, then we’ll process the remainder of the routine. We pull each piece of information and display it. Then we bind the repeater for Roles with all the roles, and call another helper function SetupEditUserRoles. We’ll use this to iterate through our repeater items and mark the roles that our user is currently assigned to. Finally we SetUI so that our ‘Edit User’ dialog will appear. To make this helper routine function we need to add a couple things. First, let’s add an entry to the SetUIModes Enumeration for ‘EditUser’ and add the following case to our SetUI subroutine:
Case SetUIModes.EditUser
mpeEditUser.Show()
We also need to create our SetupEditUserRoles helper function so let’s define it as follows:
Private Sub SetupEditUserRoles(ByVal sUserName As String)
If rptEditUserRoles.Items.Count > 0 Then
lblEditAddUserToRole.Text = "Select Roles for user:"
For Each theItem As RepeaterItem In rptEditUserRoles.Items
Dim chkRole As CheckBox = _
CType(theItem.FindControl("chkRole"), CheckBox)
chkRole.Checked = Roles.IsUserInRole(sUserName, chkRole.Text)
Next
Else
lblEditAddUserToRole.Text = "No roles defined."
End If
End Sub
If our repeater has any items, then we’ll add administrator directions to our ‘Edit User’ dialog, and then iterate through each of the items in the repeater. We pull out the checkbox from the repeater’s item and if see if the user is in the role associated with the checkbox. If they are, then we’ll mark it checked. If there are not any roles, we’ll also notify the administrator of such.
Running your application at this point should allow you to select ‘Edit User’ and get all the details about our user. If we have roles, they’ll show up and if we have any assigned to the user, they’ll be indicated as such. Now we need to hookup our ‘Save’ button so that our changes are saved. (‘Cancel’ already automatically closes the dialog without saving). Add the following within our lnkEditUserSave_Click event handler:
If Not Page.IsValid() Then Return
Dim sUserName As String = lblUsernameEdit.Text
Try
Dim theEditedUser As MembershipUser = _
Membership.GetUser(sUserName)
theEditedUser.Email = txtEmail.Text
theEditedUser.Comment = txtDescription.Text
theEditedUser.IsApproved = chkActiveUser.Checked
Membership.UpdateUser(theEditedUser)
UpdateRoleMembership(rptEditUserRoles, sUserName)
Catch ex As Exception
lblMessageDialog.Text = "Error updating user and/or roles."
SetUI(SetUIModes.MessageDialog)
Return
End Try
First we check if the page is valid (our validation controls will set this). If it isn’t we’ll exit, if it is we’ll retrieve the username and use that to retrieve their MembershipUser object. From there we’ll update the pieces of the User’s information and then save the changes to the database. Finally, we update the role membership. We already created the helper subroutine to assign roles, so it should perform without any trouble. We wrap this in a try catch block so that if it fails we can notify the administrator that update failed. You may also notice that if update fails, we reuse the dialog that we created earlier to notify the administrator.
Now that we’ve saved the user’s new information we need to update our GridView’s datasource (and thus the GridView). We’ll also notify our administrator that the changes were successful. Add the following to our lnkEditUserSave_Click event handler. Below our End Try statement:
SetDataSource(Membership.GetAllUsers(), USERS_DATA_SOURCE)
BindGrid(gvManageUsers, USERS_DATA_SOURCE, True)
lblMessageDialog.Text = "You have successfully updated the user <b>" _
& sUserName & "</b>."
SetUI(SetUIModes.MessageDialog)
ResetEditUserDialog()
Here we reset our data source and then rebind our GridView. We set our message dialog and then show it. Finally we call a new helper subroutine, ResetEditUserDialog. This will clear out all the information that we put into our ‘Edit User’ dialog. We can also call this when we click the cancel button if desired. This just makes sure that we don’t have any stray information hanging around. Create the new helper subroutine as follows:
Private Sub ResetEditUserDialog()
lblUsernameEdit.Text = String.Empty
txtDescription.Text = String.Empty
txtEmail.Text = String.Empty
lblSecurityQuestion.Text = String.Empty
End Sub
Really all we are doing is flushing out all the data in our dialog box. Now if you run your application and edit a user’s information, we’ll be able to successfully change and update the user’s information.
You may have noticed, but we still have a checkbox in the main GridView that we haven’t wired up with anything but a stub. (the checkbox in front of the user’s name). We can wire that up pretty easily. If you remember back, to the gridUsersClick portion, we’ll need to define some parameters to receive information from the actual event (since currently our stub doesn’t have them). From there we’ll be able to work our magic. Modify the ChangeUserIsApproved stub as follows:
Public Sub ChangeUserIsApproved(ByVal sender As Object, _
ByVal e As EventArgs)
With these parameters, we can then access the properties that we need to change the user’s approved status. Add the following as the body of your subroutine and we’ll be good to go there:
Dim theCheckbox As CheckBox = CType(sender, CheckBox)
Dim theItemRow As GridViewRow = CType(theCheckbox.Parent.Parent, _
GridViewRow)
Dim theUserNameLabel As Label = _
CType(theItemRow.FindControl("lblUsername"), Label)
Dim sUserName As String = theUserNameLabel.Text
Dim theUser As MembershipUser = _
Membership.GetUser(sUserName)
theUser.IsApproved = theCheckbox.Checked
Membership.UpdateUser(theUser)
First we get the checkbox. Then we retrieve the GridViewRow containing the checkbox so that we can get the user’s name. Finally we pull the label with the username from the GridViewRow and use that to retrieve the user from the Membership database. We change the user’s IsApproved status and then the update the user.
That leaves us with 4 LinkButtons on this page that we haven’t yet addressed, ‘Change Username’, ‘Reset Password’, ‘Change Password’ and ‘Change Security Q/A’. I saved these till last because your ability as an administrator to change/reset a user’s password and security Q/A depends on how the Membership provider is configured.
If you aren’t familiar with the setup of membership providers in ASP.Net, I found an excellent article that covers the provider settings and how they affect what you are allowed to do with passwords. Basically if we have set our provider up as the default (HASHED), then we don’t have the option as administrators to change the user’s password, we can only reset it. Also, even if we set our password as encrypted, then we’ll not be able to change the user’s password if our provider sets requiresQuestionAndAnswer to true, because we’d have to have the user’s security answer.
The point is this, if we want to have the ability as an administrator to change the user’s password, we’ll either need to have the user’s password and/or security question or configure our provider in such a way that we can retrieve the password. Here’s the rub, we may not want to configure our provider that way for the users, however it may be appropriate for the administrator. One suggestion I found in my searching revolves around having 2 Membership providers and then using one for the users and one for the administrator. The two providers would both go to the same Membership database, but the difference would be in how you setup your provider details (such as enablePasswordRetrieval etc).
In our case, what we’ll do is examine the provider being used and determine if it meets the requirements necessary to allow us to perform the functions we’d like, if not then we’ll disable the feature. You may decide to try changing your membership provider around a little to test it in each of the scenarios. We’ll use the flowchart on the right to determine what options we present to the administrator.
We’ll want to enable or disable the links on our ‘Edit User’ dialog before it is shown, based on the provider’s settings. While we could do this in the page_load, we’ll create a subroutine that has all our functionality in it and call that subroutine from our editUserClick subroutine. Define our helper subroutine as follows:
Private Const FUNCTIONALITY_NOT_ENABLED As String = _
"Provider not configured to allow this function."
Public Sub SetupEditPasswordLinks()
Dim bResetEnabled As Boolean = Membership.Provider.EnablePasswordReset
Dim bRetrievalEnabled As Boolean = Membership.Provider.EnablePasswordRetrieval
Dim bQnARequired As Boolean = Membership.Provider.RequiresQuestionAndAnswer
If Membership.Provider.PasswordFormat = MembershipPasswordFormat.Hashed Then
lnkChangePassword.Enabled = False
lnkChangePassword.ToolTip = FUNCTIONALITY_NOT_ENABLED
lnkChangeSecurityQuestion.Enabled = False
lnkChangeSecurityQuestion.ToolTip = FUNCTIONALITY_NOT_ENABLED
If bResetEnabled = False OrElse _
(bResetEnabled = True AndAlso bQnARequired = True) Then
lnkResetPassword.Enabled = False
lnkResetPassword.ToolTip = FUNCTIONALITY_NOT_ENABLED
End If
Else
If bRetrievalEnabled = False OrElse _
(bRetrievalEnabled = True AndAlso bQnARequired = True) Then
lnkChangePassword.Enabled = False
lnkChangePassword.ToolTip = FUNCTIONALITY_NOT_ENABLED
lnkChangeSecurityQuestion.Enabled = False
lnkChangeSecurityQuestion.ToolTip = _
FUNCTIONALITY_NOT_ENABLED
End If
If bResetEnabled = False OrElse _
(bResetEnabled = True AndAlso bQnARequired = True) Then
lnkResetPassword.Enabled = False
lnkResetPassword.ToolTip = FUNCTIONALITY_NOT_ENABLED
lnkChangeSecurityQuestion.Enabled = False
lnkChangeSecurityQuestion.ToolTip = _
FUNCTIONALITY_NOT_ENABLED
End If
If bRetrievalEnabled = False Then
lnkChangeSecurityQuestion.Enabled = False
lnkChangeSecurityQuestion.ToolTip = FUNCTIONALITY_NOT_ENABLED
End If
End If
If bRetrievalEnabled = False Then
lnkChangeSecurityQuestion.Enabled = False
lnkChangeSecurityQuestion.ToolTip = FUNCTIONALITY_NOT_ENABLED
End If
End Sub
In this subroutine, we run through the scenarios in our flowchart and enable/disable our links for changing things dependant on our provider’s settings. We also add an if statement to enable/disable our Change Security Q/A link if password retrieval is not enabled (as the password needs to be passed to the method to perform the change). Now we just need to call our helper subroutine. Add the following line to our gridUsersClick subroutine, just after the SetupEditUser call:
SetupEditPasswordLinks()
You should now have your links enabled or disabled as appropriate to your provider configuration. Now we just need to make all the links functional. First, we’ll need to create dialog boxes that allow us to enter the information for each of our functions. Add the following to our front-end code, following our last section, but before our formatEverything panel end tag:
<%-- Change Password Dialog --%>
<asp:UpdatePanel ID="upnlChangePassword" runat="server"
ChildrenAsTriggers="true">
<ContentTemplate>
<asp:Panel ID="pnlChangePassword" runat="server"
Style="display: none" CssClass="modalPopup">
<asp:Table runat="server">
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
New Password:
</asp:TableCell>
<asp:TableCell HorizontalAlign="Left">
<asp:TextBox ID="txtNewPassword"
runat="server" TextMode="Password" />
</asp:TableCell>
<asp:TableCell>
<asp:RequiredFieldValidator ID="rfvNewPassword"
runat="server" ControlToValidate="txtNewPassword"
Text="*" Display="Dynamic"
ErrorMessage="New password cannot be blank."
ValidationGroup="ChangePassword" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
Confirm Password:
</asp:TableCell>
<asp:TableCell HorizontalAlign="Left">
<asp:TextBox ID="txtConfirmNewPassword"
runat="server" TextMode="Password" />
</asp:TableCell>
<asp:TableCell>
<asp:CompareValidator ID="cvConfirmPassword"
runat="server"
ControlToValidate="txtConfirmNewPassword"
ControlToCompare="txtNewPassword"
ValidationGroup="ChangePassword"
ErrorMessage="The passwords do not match."
Display="Dynamic" Text="*" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell ColumnSpan="3">
<asp:ValidationSummary id="vsChangePassword"
runat="server" ValidationGroup="ChangePassword"
DisplayMode="List" ShowSummary="true"
EnableClientScript="true" />
</asp:TableCell>
</asp:TableRow>
</asp:Table>
<br /><br />
<asp:LinkButton ID="lnkChangePasswordSave" runat="server"
Text="Save" CausesValidation="true" />
<asp:LinkButton ID="lnkChangePasswordCancel"
runat="server" />
</asp:Panel>
<asp:Button ID="btnFakeShowMPEChangePassword"
runat="server" style="display: none" />
<cc1:ModalPopupExtender ID="mpeChangePassword"
runat="server"
TargetControlID="btnFakeShowMPEChangePassword"
PopupControlID="pnlChangePassword"
BackgroundCssClass="modalBackground">
</cc1:ModalPopupExtender>
</ContentTemplate>
</asp:UpdatePanel>
<%-- Confirm Reset Password Dialog --%>
<asp:UpdatePanel ID="upnlConfirmReset" runat="server"
ChildrenAsTriggers="true">
<ContentTemplate>
<asp:Panel ID="pnlConfirmResetPassword" runat="server"
style="display:none; text-align:center" CssClass="modalPopup">
<asp:Label ID="lblConfirmResetPasswordInstructions"
runat="server" />
<asp:Button id="btnResetPasswordYes"
runat="server" Text="Yes" />
<asp:Button ID="btnResetPasswordNo"
runat="server" Text="No" />
</asp:Panel>
<asp:Button id="btnFakeMPEConfirmResetPassword"
runat="server" Style="display:none;" />
<cc1:ModalPopupExtender ID="mpeConfirmResetPassword"
runat="server"
TargetControlID="btnFakeMPEConfirmResetPassword"
PopupControlID="pnlConfirmResetPassword"
CancelControlID="btnResetPasswordNo"
BackgroundCssClass="modalBackground">
</cc1:ModalPopupExtender>
</ContentTemplate>
</asp:UpdatePanel>
<%-- Change Security Question Dialog --%>
<asp:UpdatePanel ID="upnlChangeSecurityQA" runat="server"
ChildrenAsTriggers="true">
<ContentTemplate>
<asp:Panel ID="pnlChangeSecurityQA" runat="server"
Style="display:none;" CssClass="modalPopup" >
<asp:Label ID="lblChangeSecurityQAInstructions" runat="server"
Text="Change your Security Question and/or Answer: " />
<br /><br />
<asp:Table runat="server">
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
<asp:Label ID="lblChangeSecurityQuestion"
Text="Security Question:"
runat="server" />
</asp:TableCell>
<asp:TableCell HorizontalAlign="Left">
<asp:TextBox ID="txtSecurityQuestion" runat="server" />
</asp:TableCell>
<asp:TableCell>
<asp:RequiredFieldValidator runat="server"
Display="Dynamic" ID="rfvSecurityQuestion"
ControlToValidate="txtSecurityQuestion" Text="*"
ErrorMessage="Security Question cannot be blank."
ValidationGroup="SecurityQuestion" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell HorizontalAlign="Right">
<asp:Label ID="lblChangeSecurityAnswer"
Text="Security Answer:" runat="server" />
</asp:TableCell>
<asp:TableCell HorizontalAlign="Left">
<asp:TextBox ID="txtSecurityAnswer" runat="server" />
<cc1:TextBoxWatermarkExtender
ID="tbeSecurityAnswer" runat="server"
TargetControlID="txtSecurityAnswer"
WatermarkText="Enter Security Answer"
WatermarkCssClass="watermarked">
</cc1:TextBoxWatermarkExtender>
</asp:TableCell>
<asp:TableCell>
<asp:RequiredFieldValidator runat="server"
Display="Dynamic" ID="rfvSecurityAnswer"
ControlToValidate="txtSecurityAnswer" Text="*"
ErrorMessage="Security Answer cannot be blank."
ValidationGroup="SecurityQuestion" />
</asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell ColumnSpan="3">
<asp:ValidationSummary ID="vsSecurityQuestion"
runat="server" DisplayMode="List"
ShowSummary="true" EnableClientScript="true"
ValidationGroup="SecurityQuestion" />
</asp:TableCell>
</asp:TableRow>
</asp:Table>
<asp:LinkButton ID="lnkChangeSecurityQASave" runat="server"
CausesValidation="true" Text="Save" />
<asp:LinkButton ID="lnkChangeSecurityQACancel" runat="server"
Text="Cancel" />
</asp:Panel>
<asp:Button id="btnFakeMPEChangeSecurityQuestion"
runat="server" Style="display:none;" />
<cc1:ModalPopupExtender ID="mpeChangeSecurityQuestion"
runat="server"
TargetControlID="btnFakeMPEChangeSecurityQuestion"
PopupControlID="pnlChangeSecurityQA"
CancelControlID="lnkChangeSecurityQACancel"
BackgroundCssClass="modalBackground">
</cc1:ModalPopupExtender>
</ContentTemplate>
</asp:UpdatePanel>
Now let’s add functionality to use all the stuff we just added. First we need to add some to our SetUI helper subroutine. Add the following lines to our SetUIModes Enumeration:
ChangePassword
ResetPassword
ChangeSecurityQuestion
and then add the following cases to our SetUI helper Subroutine:
Case SetUIModes.ChangePassword
mpeChangePassword.Show()
Case SetUIModes.ResetPassword
mpeConfirmResetPassword.Show()
Case SetUIModes.ChangeSecurityQuestion
mpeChangeSecurityQuestion.Show()
Finally, we can add functionality to make our links work. For our ‘Change Password’ link add the following:
Protected Sub lnkChangePassword_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangePassword.Click
SetUI(SetUIModes.ChangePassword)
End Sub
Protected Sub lnkChangePasswordSave_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangePasswordSave.Click
Dim mu As MembershipUser = Membership.GetUser(lblUsernameEdit.Text)
Dim sOldPassword As String = mu.GetPassword()
Try
mu.ChangePassword(sOldPassword, txtNewPassword.Text)
lblMessageDialog.Text = "Password successfully changed."
Catch ex As Exception
lblMessageDialog.Text = ex.Message.ToString()
End Try
SetUI(SetUIModes.MessageDialog)
End Sub
Protected Sub lnkChangePasswordCancel_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangePasswordCancel.Click
SetUI(SetUIModes.EditUser)
End Sub
First when we click our ‘Change Password’ link, we use our SetUI function to show the modal popup for changing the password. We also define functionality for the two buttons in the change password dialog (save and cancel). When we click Cancel, we just need to set our UI back to the ‘Edit User’ dialog. When we click the save button, we want to pull the username, get the MembershipUser object associated with it and use that to change the password. If we run into an error (it’s likely due to improper password specifications – i.e. the password is not long enough, or doesn’t have enough non-alphanumeric characters etc.), we’ll just display an error message that tells the administrator the reason for failure and go back to our GridView.
Note: that the error handling here isn’t really all that clean. We’re just telling the administrator the problem and then dumping them back to the GridView rather than to the ‘Edit User’ dialog. For sake of ease, I haven’t gone into a lot of depth in the error handling, but we could devise a means of dumping the administrator back to the ‘Edit User’ dialog after the message dialog instead and create a cleaner way of checking the password’s characteristics and reporting the error to the user.
Next, we’ll handle the ‘Reset Password’' link. Add the following to your code:
Protected Sub lnkResetPassword_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkResetPassword.Click
lblConfirmResetPasswordInstructions.Text = _
"Are you sure you want to reset user: " & lblUsernameEdit.Text & _
"'s password? This cannot be undone."
SetUI(SetUIModes.ResetPassword)
End Sub
Protected Sub btnResetPasswordYes_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles btnResetPasswordYes.Click
Membership.GetUser(lblUsernameEdit.Text).ResetPassword()
lblMessageDialog.Text = "Password successfully reset."
SetUI(SetUIModes.MessageDialog)
End Sub
Protected Sub btnResetPasswordNo_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles btnResetPasswordNo.Click
SetUI(SetUIModes.EditUser)
End Sub
With the reset button, we don’t actually have a dialog to enter anything, rather we have a dialog that confirms our decision to reset the password. When we click the ‘Reset Password’ link we’ll setup our dialog with a message that’s customized with the username to be reset, and then display our confirmation message. If the administrator clicks ‘No’, then we just go back to the ‘Edit User’ dialog. If the admin clicks ‘Yes’, then we reset the user’s password and then notify the administrator that the password was successfully reset. Again, success will dump our administrator back to the GridView. We could add additional functionality to change that but I chose not to.
Lastly for this post, we’ll add functionality for changing the user’s security question. Add the following to your code:
Protected Sub lnkChangeSecurityQuestion_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangeSecurityQuestion.Click
txtSecurityQuestion.Text = String.Empty
txtSecurityAnswer.Text = String.Empty
SetUI(SetUIModes.ChangeSecurityQuestion)
End Sub
Protected Sub lnkChangeSecurityQASave_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangeSecurityQASave.Click
Dim mu As MembershipUser = Membership.GetUser(lblUsernameEdit.Text)
mu.ChangePasswordQuestionAndAnswer(mu.GetPassword(), _
txtSecurityQuestion.Text, txtSecurityAnswer.Text)
lblMessageDialog.Text = "Security Question and Answer successfully changed."
SetUI(SetUIModes.MessageDialog)
End Sub
Protected Sub lnkChangeSecurityQACancel_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lnkChangeSecurityQACancel.Click
SetUI(SetUIModes.EditUser)
End Sub
When the administrator clicks the ‘Change Security Q/A’ link, we’ll blank out fields in the Security Question form (to reset it if was used previously), and then display the dialog. If the administrator clicks ‘Cancel’, we’ll pop back to the ‘Edit User’ dialog. If the administrator clicks ‘Save’, we’ll pull the MembershipUser object for the user and then change their security question. When that is successful, we’ll display a message of success to the administrator and then pop back to the GridView.
Run you application and test out your links. If you play with your provider’s settings you’ll see that the relevant links will be enabled/disabled depending on your settings and you should be able to perform password changes and resets depending on your configurations.
Epilogue
Phew! We’ve covered a lot of ground today. We added nearly all the functionality needed to edit all aspects of our user’s account. This added a lot of code to both our front-end and back-end files (we’re not done yet). In the next segment, I’ll be talking about the one edit user piece we didn’t implement in this section, changing a username. This gets a little more complicated as it requires us to add some functionality calling directly to the database. Probably this segment was the longest code wise, from here out our code should be a little shorter, as we won’t be adding quite so much functionality at once.