A Better ASP.Net Member/Role Management Page Pt. 3
**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).
In previous posts, we outlined criteria in Part 1 for creating a “Better ASP.Net Member/Role Management Page”, and in Part 2 created functionality for displaying the users in our database as well as searching and filtering them. We want to continue developing our page.
In this post, we’ll be hooking up the functionality that we need to create a user in our system. Once we’ve got this functionality working, we won’t have to look outside our own page to insert users for testing purposes. We’ll use some AJAX to create a “popup” that will display a form to create our user and then use the same AJAX functionality to display a message to the administrator that the user was created successfully. Finally, we’ll add functionality to delete a user. We’ll popup a confirmation dialog for the administrator so they have to confirm user deletion.
To create a user, we have a couple of options, we can call the Membership.CreateUser method (and we create the user form-which gives us more flexibility) or we can use a CreateUserWizard control and let it do most of the work for us (including creating the form elements and performing the validation, even validating against our provider’s definition stipulations such as password strength etc.). Since Microsoft’s Web Site Admin Tool (WSAT) uses the CreateUserWizard, that’s what I used too, it reduces the amount of work that I have to do.
During this series, we’ll be creating a number of distinct pieces that we’ll be wrapping in UpdatePanels/Panel combos. The UpdatePanel so that we can AJAX-ify them and then Panels inside the UpdatePanel so that we can hide and reveal the information easily. Let’s start our create user section by adding an UpdatePanel and nesting within it a Panel control that will contain our user creation elements. At the bottom of our page (but before the end tag for our formatEverything Panel), add the following:
<%-- Create User Dialog –%>
<asp:UpdatePanel ID="upnlCreateUser" runat="server"
<asp:Panel ID="pnlCreateUser" runat="server" style="display: none;"
I’m again going to use a table to do formatting (oh, I have issues with myself…) and we’ll layout our controls within that table so that our formatting is easily done. Well create a 2x2 table and use a colspan on the first row so that it becomes a header. Then we’ll use the two sides of the bottom row to display the CreateUserWizard in one, and the roles the user can be assigned to on the other side. Add the following table to your code, within our pnlCreateuser Panel:
<asp:Table runat="server" ID="tblCreateUser">
Add a user: Enter the user's information and
select their assigned roles:
Here we create our table, we added a label to the header row, again so we can target it programmatically if we desire to do so. Now we’re ready to add with our CreateUserWizard control. In our first empty TableCell, add the following:
<asp:CreateUserWizard ID="cuwAddUser" runat="server"
ID=”cuwStep1” Title="Add a new User:" />
<asp:CompleteWizardStep runat="server" />
<asp:CheckBox ID="chkNewUserActive" runat="server"
Checked="true" text="Active User"/>
In our CreateUserWizard, we specify that all our buttons will be links (for continuity’s sake) and that we won’t log in our user after we create them. We could use our CreateUserWizard without specifying anything in regards to the steps, but I wanted to customize the message at the top of the step, and I couldn’t do that without defining the step explicitly. Also we don’t seem to be allowed so skip the CompleteWizardStep, so it’s also included. We won’t be using it, and in fact we’ll probably have to do a workaround so that we can do things our own way (i.e. so we can skip it entirely). Also notice that the CreateWizardStep has an ID assigned. Technically we don’t need to include that, but we will so that we can use it later in the “skip CompleteWizardStep” workaround.
You’ll also notice that a checkbox is included just below our CreateUserWizard. This allows us to create a user and assign their active state all at the same time. We could modify the step’s template to include it, or add another step, but this will work just as well and we can then let the CreateUserWizard do most of the work. The one drawback is that the checkbox will end up below our Create User and Cancel links, but that’s livable in my book.
In the other TableCell, we’ll create a repeater containing roles that we can assign to our new user. Unfortunately the repeater doesn’t have any EmptyDataTemplate like our GridView does so we have to approximate one. We’ll add a label that can double as our instructions and do some handling on backside for it. Add the following between our other set of empty TableCell tags:
<asp:Label ID="lblCreateUserRolesTile" runat="server" Text="Roles" /><br>
<asp:Label id="lblCreateUserRoles" runat="server" Text="Select Roles:" />
<asp:Repeater ID="rptCreateUserRoles" runat="server">
<asp:CheckBox ID="chkRole" runat="server"
text=”<%# Container.DataItem.ToString() %>” /><br />
Here we create two labels, one for a title, and one that we’ll use either for instructions or to notify the user that there are no roles defined to add to their user. The Repeater is pretty simple, just a checkbox in the ItemTemplate. When we access the page, the text property is bound to the Data (the role name).
Finally, we want to add what we need to make this a modal popup dialog. For this, we need to add a ModalPopupExtender. We also need to tie one of it’s parameters to a button. Since we want to control everything from code, we’ll make a “fake” button and make it invisible. Add the following just below our pnlCreateUser panel’s end tag but still within the ContentTemplate for our UpdatePanel:
<asp:Button ID="btnFakeMPECreateUser" runat="server"
Style="display: none;" />
<cc1:ModalPopupExtender ID="mpeCreateUser" runat="server"
Ok, with our front-end work done, we can get started on the back-end coding. Let’s start by hooking up our Create User link. Since we’re going to change elements on our page a number of times depending on what we are going to display, we’ll create a function that allows us to control the UI all in one place. We’ll create a SetUI subroutine and use it to do our UI shuffling. We’ll create an enumeration for it as well and use that when we call our routine. Add the following to your code behind:
Private Enum SetUIModes
Private Sub SetUI(ByVal modeToChoose As SetUIModes)
Select Case modeToChoose
Basically when we create a new user, we just show our ModalPopupExtennder and viola, we have a Popup. We don’t necessarily need to worry about closing the Popup, it tends to close itself pretty easily. Next, we want to call SetUI when we click to create a user. Add the following to your lnkCreateUser_Click event handler:
Try it out and you’ll see that it works. If you click Cancel, the Popup goes away, and if you click Create user you’ll get validation and the popup doesn’t go away. Next we’ll want to populate our roles list. (You may want to use the WSAT to add a role or two for the time being so that you can test things in this section). We’ll want to define a constant to target our roles data source, much like we did for the users. Add the this global constant to your code:
Private Const ROLES_DATA_SOURCE As String = "ROLES_DATA_SOURCE"
Next, we’ll create a helper routine to bind a repeater to a our roles data source, much like we did with the BindGrid routine above. We’ll may use this again so creating a BindRepeater subroutine makes sense. Add the following helper routine to your code:
Private Sub BindRepeater(ByVal theRepeater As Repeater, _
ByVal dataSourceConstant As String)
theRepeater.DataSource = theSession(dataSourceConstant)
You’ll notice that is is basically just like our BindGrid only that it accepts a repeater instead. We pull our data and bind it to grid, not much to it.
Back in our lnkCreateUser_Click event handler, we want to set our data source and then call our BindRepeater. Add the following BEFORE your SetUI statement:
If rptCreateUserRoles.Items.Count = 0 Then
lblCreateUserRoles.Text = "No roles defined."
chkNewUserActive.Checked = True
Run your app and you’ll see that it works. We create and then bind our data source and then we add a check to see if our data set is empty. If it is, then we want to set our instructions label to indicate that there are no roles defined. We also set the active user checkbox to checked (in case it has been unchecked previously). Now we just need to hook it up so that when the user is created, we assign the roles and stuff. First we’ll create a helper routine that allows us to pass in a repeater and have it update the user’s roles (we’ll use it again later). Add the following helper subroutine to your code:
Private Sub UpdateRoleMembership(ByVal theRepeater As Repeater, _
byVal theUser As String)
If String.IsNullOrEmpty(theUser) Then Exit Sub
For Each rptItem As RepeaterItem In theRepeater.Items
Dim theCheckbox As CheckBox = _
If theCheckbox.Checked = True AndAlso _
Roles.IsUserInRole(theUser, theCheckbox.Text) = False Then
If theCheckbox.Checked = False AndAlso _
Roles.IsUserInRole(theUser, theCheckbox.Text) = True Then
First we check that the username isn’t invalid (a null string). Then we cycle through the repeater grabbing the checkbox from the RepeaterItem each time around. If the user is already in a role, we don’t want to add it again (not a problem in this context (creating user), but may be other times we use this routine and it would give us an error) so we check that the checkbox is checked AND that the user isn’t already in the role before adding them. The same goes for removing them (again later), if the user is to be removed AND the user is currently in the role, then we remove them.
We’ll call our helper routine from our
cuwCreateUser_CreatedUser cuwAddUser_CreatedUser event handler (note, created, not creating), this way we are sure not to add our roles to a user that wasn’t successfully created. Add the following inside your event handler:
Dim cuwTheUser As CreateUserWizard = CType(sender, CreateUserWizard)
First we retrieve the CreateUserControl so we can access the elements within it. From it we use the username element so that we can call our UpdateRoleMembership subroutine.
We also want to set the user to active or not active depending on the user selection. You’ll remember that it isn’t part of the CreateUserWizard, but actually outside of it so we have to handle this ourselves. Add the following to your
cuwCreateUser_CreatingUser cuwAddUser_CreatingUser event handler:
cuwAddUser.DisableCreatedUser = Not chkNewUserActive.Checked
Notice that we’re negating what was entered before assigning it. DisableCreatedUser is the negative property of our entry.
Now that the user has been entered, we need to update our GridView with the new user. So we reset our SetDataSource and then Bind it again. Add the following to the end of our cuwAddUser_CreatedUser event handler (after the UpdateRoleMembership call):
BindGrid(gvManageUsers, USERS_DATA_SOURCE, True)
This simply resets our Data Source to all members, and then rebinds our data. Finally, we want to display a notification to the user that the user was created successfully. We’ll create a panel that we can display as a modal popup and then in our code we’ll set it’s properties and display it. For this we’ll need to flip back to our front end code and add the following at the bottom, but still within our formatEverything Panel:
<%-- Message Dialog --%>
<asp:UpdatePanel ID="upnlMessageDialog" runat="server" ChildrenAsTriggers="true">
<asp:Panel id="pnlMessageDialog" runat="server"
CssClass="modalPopup" style="display:none; text-align: center;" >
<asp:Label ID="lblMessageDialog" runat="server" /> <br /> <br />
<asp:Button ID="btnMessageDialogOk" runat="server" Text="OK" />
<asp:Button ID="btnFakeMPEMessageDialog" runat="server"
<cc1:ModalPopupExtender ID="mpeMessageDialog" runat="server"
We’re basically just creating a panel with a label and a button. Then we’re adding a fake button for the extender and the extender and configuring it. We added one extra parameter to the extender: CancelControlID. This becomes an automatic way for us to close the modal popup without having to code it. Next, we want to go back to our code-behind and add in functionality to configure and call it.
Let’s create a helper function for setting the message as follows:
Private Sub SetMessageDialog(ByVal theMessage As String)
lblMessageDialog.Text = theMessage
Now we just need to call it, so add to the bottom of our cuwAddUser_CreatedUser event handler:
SetMessageDialog("User successfully created.")
We also need to implement the appropriate functionality in the SetUI routine. We need to add a case for the Message Dialog. Add ‘MessageDialog’ to your enumeration, and then add the following case to your SetUI’s case statement:
Try your page, it should work: it should successfully create a user (if you successfully enter valid information) and add the user to the appropriate roles. It should also assign the user as active or disabled based on our selection. Cool. Once the user is created, you should get a message letting you know it was successfully created. You may even be able to see our new user in the GridView behind our dialog box if you’re lucky. When we click Ok, our dialog goes and we’re back to the GridView. Go ahead and click to create another user and Oi’ Vey’ what’s that?
It seems that the wonderful automation of our CreateUserWizard has managed to add a message dialog of it’s own. When we finish adding our user, it will display the final WizardStep (CompleteWizardStep). Clicking the continue button may make our dialog close (well, it doesn’t for me), but there’ isn’t anything wired up for it to do when the continue button is clicked. Usually, this leads to another page, in our case we don’t want it to, we want it to stay on our page, but we want to somehow reset the CreateUserWizard so it can be used again, and do it behind the scenes so that the administrator using it doesn’t have to deal with it. Sounds easy right?
Unfortunately, the CreateUserWizard doesn’t have a .Reset or a .Clear method, and there doesn’t seem to be a way to just click continue and have it reset (this is probably due to the control being created for regular people to enter their own information and not for administrators to enter information repeatedly), so we’ll be forced to do it the round about way and push our Wizard back a step and then clear it’s contents. (PS, if anyone can find a better way, I’d like to know about it). So, let’s create another helper subroutine to clear our Grid’s data and set back to the first step:
Private Sub cuwAddUser_Reset(ByVal theWizard As CreateUserWizard, _
ByVal theStep As WizardStepBase)
theWizard.UserName = ""
theWizard.Email = ""
theWizard.Question = ""
We first set the Wizard back to the first step (that’s why we assigned it an ID). Then we clear all the fields that won’t automatically clear themselves when we step back. Now we just need to call it. In my testing, I’ve found that it needs to be in the SetUI’s CreateUser block to be successful (thus it will reset just before we use it). Add the following line just before the mpeCreateUser.show() line:
We call our Routine and pass in our CreateUserWizard and the OBJECT, not the string for our CreateUserStep. Now run your application and create two users in a row. The confirmation from the CreateUserWizard should no longer be a problem.
Finally, let’s wire up the Delete User functionality. You’ve probably created enough users that you’re ready to start deleting a few of them, so let’s get started.
First we’ll want to create the confirmation dialog for our deletion. This dialog will then kick off the actual deletion of the user, depending on if yes or no was selected. So go back to our front-end markup code and add the following at the bottom:
<%-- Delete User Confirm Dialog --%>
<asp:UpdatePanel ID="upnlDeleteUserConfirm" runat="server"
<asp:Panel ID="pnlDeleteUserConfirm" runat="server"
Style="display:none; text-align: center;" CssClass="modalPopup">
runat="server" visible="false" />
<asp:Label ID="lblDeleteUserConfirmMessage" runat="server" />
<br /><br />
<asp:Button id="btnDeleteUserYes" runat="server" Text="Yes" />
<asp:Button ID="btnDeleteUserNo" runat="server" Text="No" />
<asp:Button id="btnFakeMPEDeleteUserConfirm" runat="server"
<cc1:ModalPopupExtender ID="mpeDeleteUserConfirm" runat="server"
Here we create a dialog containing our confirmation message and two buttons. You may notice that there are two labels present. The one that is hidden, allows us to pass the username to be deleted around, since it has to survive two trips to the server (once when delete is clicked and once when yes is clicked). This will be our means of state preservation. By now, you’re probably getting pretty familiar with the ModalPopupExtender, so I won’t point out much except that our CancelControlID is our No button. Much like our OK button in the message dialog, we can rely on it to close the popup and we don’t even need to clean up.
Let’s jump back to our Code-behind and wire up our events there. First we need to catch the event signifying that our Delete User link has been clicked. Our front-end defines that as gridUsersClick subroutine. We already created a stub for it earlier so that our page would work. However, before we can use it as we’d like to, we need to make some changes to the subroutine’s definition. We need to allow it to receive the normal event handler parameters. Modify your subroutine definition as follows:
Public Sub gridUsersClick(ByVal sender As Object, _
ByVal e As CommandEventArgs)
Now we can add some functionality for deleting users. Add the following to our gridUsersClick subroutine:
If e.CommandName.Equals("DeleteUser") Then
lblDeleteConfirmUserName.Text = CStr(e.CommandArgument)
lblDeleteUserConfirmMessage.Text = "Are you sure you want to delete '" _
& lblDeleteConfirmUserName.Text & "'? This cannot be undone."
We examine our CommandEventArgs for the CommandName and if it is DeleteUser, we setup our confirmation box with the username and message. When we defined our rows link, we defined that the CommandArgument should contain the username, so we’ll pull it from there. We’ll then show the confirmation using the SetUI subroutine. This means that we need to add some to our SetUI subroutine. Add an entry to your Enumeration: ‘DeleteUserConfirm’, then add the following case to our SetUI case statement:
Now we just need to add some functionality to our YES button so that when the administrator selects it, our user will be deleted. Add the following to our btnDeleteUserYes_Click event handler:
BindGrid(gvManageUsers, USERS_DATA_SOURCE, False)
Once again, after we make the changes, we’ll recreate our data source and then rebind our GridView to show the changes. Run your application and test it out. You can now create and delete users at will!
Well, today we’ve added functionality for creating and deleting users. Currently we should have fulfilled all 6 our initial requirements except #3 and #4 (where we defined ALL the functionality). Ok, what that really means is that we’ve only finished two small pieces of our functionality from requirement #3 and haven’t even started #4. However, we do have a working control that displays all the users, filters them and we can successfully create new users and delete existing users. In the next segment, we’ll look at editing an existing user.