A Better ASP.Net Member/Role Management Page Pt. 9
Prologue
**Author Note – If you are bewildered by a line (perhaps two) that are red and bold, they are errors or corrections/additions. Please see the comments, after the article for information regarding the highlighted line(s).**
It's kind of funny how once you really get into using things that you find little (or not so little) improvements that can make code you previously did that much more effective and useful in your (and probably other) environments. This is the case with the ‘Better ASP.Net Member/Role Management Page’.
If you aren’t familiar with this series here’s the rundown. If you want to follow along with the other posts, 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 and part 8. Welcome to part 9.
Problem
In the process of creating a site here at work, I have been developing a user registration process for the site. One of the steps of the registration process is allowing the user to sign up for email notifications in different areas. The record of their selections is stored in a table that will be polled for recipients when a notification should be sent. It suddenly dawned on me that if a user signs up for notifications and later the administrator deletes the user, that the user’s notifications will not be removed (since our management control doesn’t have code to purge the notifications table). To solve this, I could put extra code into the UserControl to remove the notifications, but this is a poor solution because I may need to add further code in the future and breaks the usability of our control. This also brings up other points such as what if the administrator changes the user’s email or deactivates the user, then I may also need to modify the notifications list.
Suddenly the light bulb comes on and I realize that we never included any events in our control. For the control to be self-contained, we need to raise events so that we can code against specific events that take place in the control, such as when a user is created, deleted or changed (and the same goes for roles), without having to modify it’s code. This post is intended to add several events so that we can code against these events in our containing page.
Solution
First, let’s define the events that can happen and that we’ll be adding (I’m choosing to add all of them that I think could possibly someday be relevant to somebody). I won’t be using most of them, probably ever, but who knows. The list I made fell into 3 categories of events as shown in the table below. I chose the 3 categories mainly due to the EventArgs that we’ll be developing to pass with our events. These three categories will fit our EventArgs more nicely if divided as follows:
| User Events | Role Events | Role/User Events |
| Added | Added | User Un/Assigned to Role |
| Deleted | Deleted | User Un/Assigning to a Role |
| Changed Sec. Question | Changed | |
| Changed Sec. Answer | | |
| Reset Password | | |
| Changed Username | | |
| Changed Email | | |
| Activated | | |
| Deactivated | | |
| UserDeleting | | |
As I mentioned above, we’ll be creating EventArgs specifically for each of these types of events. Perhaps I’ll back up a little… When creating an event in our control, we can really define it any way we would like to (the parameters that it will pass are up to us to define in anyway we want). That being said though, best practice is to follow Microsoft’s pattern for creating events:
Public Event WhatHappened(ByVal sender As Object, ByVal e As EventArgs)
The thing we’ll notice though is that EventArgs is REALLY generic. There isn’t much there to work with (no properties etc.), so we’ll define or own custom EventArgs that inherit from the EventArgs class. This way we can tailor it to our needs. (Check your event handlers and you’ll see that many of the built in events do this). So, let’s start by making some different classes of EventArgs. First, we’ll create an EventArgs for the majority of our user events. I’ll be adding these class definitions after our UserControl’s class ends. Add the following:
Public Class UserEventArgs
Inherits EventArgs
Public Enum UserEventAction
NotSet
UserDeleted
UserDeleting
UserAdded
ChangeSecurityQuestion
ChangeSecurityAnswer
UserDeactivated
UesrActivated
PasswordChanged
PasswordReset
End Enum
Public Sub New()
End Sub
Public Sub New(ByVal action As UserEventAction, _
ByVal ActionArgument As String)
_Action = action
_ActionArgument = ActionArgument
End Sub
Private _ActionArgument As String = ""
Public Property ActionArgument() As String
Get
Return _ActionArgument
End Get
Set(ByVal value As String)
_ActionArgument = value
End Set
End Property
Private _Action As UserEventAction = UserEventAction.NotSet
Public Property Action() As UserEventAction
Get
Return _Action
End Get
Set(ByVal value As UserEventAction)
_Action = value
End Set
End Property
End Class
Our EventArgs for the user events will allow us to send information to the developer regarding our event, specifically, what action happened and then we’ll pass the username as the action argument. Some of the user events will require more than one argument to be passed, such as when the username is changed, we’ll need to pass both the old and new usernames. We’ll create a new EventArgs class(es) for these. Additionally, they may not need an action parameter because we already know what the action is by virtue that it is being called. Add the following to your project:
Public Class UsernameChangedEventArgs
Inherits EventArgs
Public Sub New()
End Sub
Public Sub New(ByVal oldUsername As String, _
ByVal newUsername As String)
_oldUsername = oldUsername
_newUsername = newUsername
End Sub
Private _oldUsername As String
Public Property OldUsername() As String
Get
Return _oldUsername
End Get
Set(ByVal value As String)
_oldUsername = value
End Set
End Property
Private _newUsername As String
Public Property NewUsername() As String
Get
Return _newUsername
End Get
Set(ByVal value As String)
_newUsername = value
End Set
End Property
End Class
We’ll also need to pass two parameters for email, the old and new email addresses so it will get its own class as well, very similar on the UsernameChangedEventArgs:
Public Class EmailChangedEventArgs
Inherits EventArgs
Public Sub New()
End Sub
Public Sub New(ByVal oldEmail As String, ByVal newEmail As String)
_oldEmail = oldEmail
_newEmail = newEmail
End Sub
Private _oldEmail As String
Public Property OldEmail() As String
Get
Return _oldEmail
End Get
Set(ByVal value As String)
_oldEmail = value
End Set
End Property
Private _newEmail As String
Public Property NewEmail() As String
Get
Return _newEmail
End Get
Set(ByVal value As String)
_newEmail = value
End Set
End Property
End Class
Next we’ll add the EventArg class for RoleEventArgs:
Public Class RoleEventArgs
Inherits EventArgs
Public Enum RoleEventActions
NotSet
Deleted
Added
Changed
End Enum
Public Sub New()
End Sub
Public Sub New(ByVal action As RoleEventActions, _
ByVal ActionArgument As String)
_Action = action
_ActionArgument = ActionArgument
End Sub
Private _ActionArgument As String = ""
Public Property ActionArgument() As String
Get
Return _ActionArgument
End Get
Set(ByVal value As String)
_ActionArgument = value
End Set
End Property
Private _Action As RoleEventActions = RoleEventActions.NotSet
Public Property Action() As RoleEventActions
Get
Return _Action
End Get
Set(ByVal value As RoleEventActions)
_Action = value
End Set
End Property
End Class
Our role changed event will need to return more information than just one string as it passes old AND new information, so it has a class as the argument. This argument class is defined within the EventArgs class itself. Add the following:
Public Class RoleChangedEventArgs
Inherits EventArgs
Public Enum RoleChangedEventActions
NotSet
Renamed
DescriptionChanged
End Enum
Public Sub New()
End Sub
Public Sub New(ByVal action As RoleChangedEventActions, _
ByVal ActionArguments As RoleChangedEventActionArgs)
_Action = action
_ActionArgument = ActionArguements
End Sub
Private _ActionArgument As RoleChangedEventActionArgs = Nothing
Public Property ActionArgument() As RoleChangedEventActionArgs
Get
Return _ActionArgument
End Get
Set(ByVal value As RoleChangedEventActionArgs)
_ActionArgument = value
End Set
End Property
Private _Action As RoleChangedEventActions = _
RoleChangedEventActions.NotSet
Public Property Action() As RoleChangedEventActions
Get
Return _Action
End Get
Set(ByVal value As RoleChangedEventActions)
_Action = value
End Set
End Property
Public Class RoleChangedEventActionArgs
Public Sub New()
End Sub
Public Sub New(ByVal oldRoleName As String, _
ByVal newRoleName As String, _
ByVal oldDescription As String, _
ByVal newDescription As String)
_oldRoleName = oldRoleName
_newRoleName = newRoleName
_oldDescription = oldDescription
_newDescription = newDescription
End Sub
Private _oldRoleName As String = ""
Public Property OldRoleName As String
Get
Return _oldRoleName
End Get
Set(ByVal value As String)
_oldRoleName = value
End Set
End Property
Private _newRoleName As String = ""
Public Property NewRoleName As String
Get
Return _newRoleName
End Get
Set(ByVal value As String)
_newRoleName = value
End Set
End Property
Private _oldDescription As String = ""
Public Property OldDescription As String
Get
Return _oldDescription
End Get
Set(ByVal value As String)
_oldDescription = value
End Set
End Property
Private _newDescription As String = ""
Public Property NewDescription As String
Get
Return _newDescription
End Get
Set(ByVal value As String)
_newDescription = value
End Set
End Property
End Class
End Class
Our final class will be our UserRoleAssignement arguments. These will pass arrays of strings for the roles that the user was added/removed from. Add the following class to our project:
Public Class RoleUserAssignmentArgs
Inherits EventArgs
Public Sub New()
End Sub
Public Sub New(ByVal addedroles As String(), _
ByVal removedRoles As String(), _
ByVal username As String)
_AddedRoles = addedroles
_RemovedRoles = removedRoles
_Username = username
End Sub
Private _RemovedRoles As String()
Public Property RemovedRoles() As String()
Get
Return _RemovedRoles
End Get
Set(ByVal value As String())
_RemovedRoles = value
End Set
End Property
Private _AddedRoles As String()
Public Property AddedRoles() As String()
Get
Return _AddedRoles
End Get
Set(ByVal value As String())
_AddedRoles = value
End Set
End Property
Private _Username As String = ""
Public Property Username() As String
Get
Return _Username
End Get
Set(ByVal value As String)
_Username = value
End Set
End Property
End Class
The following class was added later to facilitate the RoleUserAssignmentChanging event discussed in the comments (please see them for more details):
Public Class RoleUserAssignmentChangingArgs
Inherits EventArgs
Public Sub New()
End Sub
Public Sub New(ByVal addedroles As String(), _
ByVal removedRoles As String(), ByVal username As String)
_AddedRoles = addedroles
_RemovedRoles = removedRoles
_Username = username
End Sub
Private _RemovedRoles As String()
Public Property RemovedRoles() As String()
Get
Return _RemovedRoles
End Get
Set(ByVal value As String())
_RemovedRoles = value
End Set
End Property
Private _AddedRoles As String()
Public Property AddedRoles() As String()
Get
Return _AddedRoles
End Get
Set(ByVal value As String())
_AddedRoles = value
End Set
End Property
Private _Username As String = ""
Public Property Username() As String
Get
Return _Username
End Get
Set(ByVal value As String)
_Username = value
End Set
End Property
Private _Cancel As Boolean = False
Public Property Cancel() As Boolean
Get
Return _Cancel
End Get
Set(ByVal value As Boolean)
_Cancel = value
End Set
End Property
End Class
(As an afterthought I added constructors to all the classes so that it would be easy to create a new instance and pass it as a parameter when the event is raised, rather than creating, populating and then passing argument whenever an event is raised – we can do it all in one line rather than several in many cases).
Next, we’ll need to create the event definitions for each of our events. These I will put at the top of our control’s code, with the constants and other definitions. Add the following event definitions:
Public Event UserDeleting(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserDeleted(ByVal sender As Object, ByVal e As UserEventArgs)
Public Event UserAdded(ByVal sender As Object, ByVal e As UserEventArgs)
Public Event UserChangedSecurityQuestion(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserChangedSecurityAnswer(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserChangedUsername(ByVal sender As Object, _
ByVal e As Public Class UsernameChangedEventArgs)
Public Event UserDeactivated(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserActivated(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserPasswordReset(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserPasswordChanged(ByVal sender As Object, _
ByVal e As UserEventArgs)
Public Event UserEmailChanged(ByVal sender As Object, _
ByVal e As EmailChangedEventArgs)
Public Event RoleAdded(ByVal sender As Object, ByVal e As RoleEventArgs)
Public Event RoleDeleted(ByVal sender As Object, ByVal e As RoleEventArgs)
Public Event RoleChanged(ByVal sender As Object, _
ByVal e As RoleChangedEventArgs)
Public Event UserRoleAssignmentChanged(ByVal sender As Object, _
ByVal e As RoleUserAssignmentArgs)
Public Event UserRoleAssignmentChanging( _
ByVal sender As Object, _
ByVal e As RoleUserAssignmentChangingArgs)
We create nine events for things we could do with user accounts, notice that for most, the parameters are the same, they use UserEventArgs as the second parameter (only the Usernamechanged and UserEmailChanged use different ones). Next we add three events for Roles. Again, notice that all the parameters are the same (except RoleChanged), and that we’re using our RoleEventArgs class. Finally, we create our User/Role assignment event, and use our RoleUserAssignmetArgs class.
Now, all we need to do is raise these events at the proper time so that someone using our control can handle the events. We’ll work our way down the list of events we just created, starting with UserDeleting. We’ll add this to our btnDeleteUserYes_Click event handler in our UserControl. Add the following BEFORE our Membership.DeleteUser line:
RaiseEvent UserDeleting(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.UserDeleting, _
lblDeleteConfirmUserName.Text))
Then, add the following line directly AFTER the Membership.DeleteUser line:
RaiseEvent UserDeleted(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.UserDeleted, _
lblDeleteConfirmUserName.Text))
Here we raise the event that the user was deleted, we add the current element as the sender (me), and then attach event args stating that we deleted the user and passing the username.
Next we’ll call our UserAdded event. Add it to the cuwAddUser_CreatedUser event handler, directly after the UpdateRoleMembership line:
RaiseEvent UserAdded(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.UserAdded, _
cuwTheUser.UserName))
Now, we’ll raise the ChangedSecurityQuestion and ChangeSecurityAnswer events, they’ll go right next to each other and we’ll add these to the lnkChangeSecurityQASave_Click event handler:
RaiseEvent UserChangedSecurityQuestion(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.ChangeSecurityQuestion, _
lblUsernameEdit.Text))
RaiseEvent UserChangedSecurityAnswer(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.ChangeSecurityAnswer, _
lblUsernameEdit.Text))
The UserChangedUsername event will be raised in the lnkChangeUsernameSave_Click event handler. Add it as the first line in the ChangeUserName if/then statement as follows:
RaiseEvent UserChangedUsername(Me, New UsernameChangedEventArgs _
(lblCurrentUsername.Text, txtNewUsername.Text))
The UserDeactivated and UserActivated events will both reside in the ChangeUserIsApproved subroutine. We’ll need an if then statement to check if the user was activated, or deactivated and raise our events that way. Add this code after the membership.UpdateUser line (the last line):
If theCheckbox.Checked Then
RaiseEvent UserActivated(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.UesrActivated, sUserName))
Else
RaiseEvent UserDeactivated(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.UserDeactivated, sUserName))
End If
The PasswordReset event will be added to the btnResetPasswordYes_Click event handler, put it directly after the Membership.GetUser().ResetPassword() line as follows:
RaiseEvent UserPasswordReset(Me, New UserEventArgs _
(UserEventArgs.UserEventAction.PaswordReset, lblUsernameEdit.Text))
The PasswordChanged event will be added to the lnkChangePasswordSave_Click event handler, add it directly below the mu.ChangePassword line as follows:
RaiseEvent UserPasswordChanged(sender, New UserEventArgs _
(UserEventArgs.UserEventAction.PasswordChanged, _
lblUsernameEdit.Text))
And finally, the EmailChanged event will be added to the lnkEditUserSave_Click, we need to add the following two lines so that we can tell if the email has been changed. Add them right after the Dim theEditedUser as MemberhipUser line:
Dim bChangedEmail As Boolean = If(theEditedUser.Email _
= txtEmail.Text, False, True)
Dim oldEmail As String = theEditedUser.Email
Then, we’ll add our RaiseEvent line right after the Membership.UpdateUser line, right before the UpdateRoleMembership line:
If bChangedEmail Then
RaiseEvent UserEmailChanged(Me, _
New EmailChangedEventArgs(oldEmail, txtEmail.Text))
End If
Notice that calling it is conditional on the password being different (we check this earlier in our code).
Now, let’s concentrate on the role events. We’ll start with RoleDeleted. We’ll add this to the btnDeleteRoleYes_Click event handler. Add it directly after the Roles.DeleteRole line as follows:
RaiseEvent RoleDeleted(Me, New RoleEventArgs _
(RoleEventArgs.RoleEventActions.Deleted, lblRoleToDelete.Text))
Next, we’ll do the RoleAdded event. We’ll add it to the btnAddRole_Click event handler. Add it directly after the Roles.CreateRole line as follows:
RaiseEvent RoleAdded(Me, New RoleEventArgs _
(RoleEventArgs.RoleEventActions.Added, txtNewRoleName .Text))
Finally, we’ll add the RoleChanged event. There are a couple different times that we can raise this event that we’ll have manage. We can rename the role, or we can change the description. Due to this, we’ll use the RoleChangedEventArgs class for EventArgs so we can pass old and new data as we’ll as role name when it is the description that changes.
We’ll start with renaming the role. We’ll raise our event in the btnRoleRenameYes_Click event handler. Raise our event directly after the Role.DeleteRole() line as follows:
RaiseEvent RoleChanged(Me, New RoleChangedEventArgs _
(RoleChangedEventArgs.RoleChangedEventActions.Renamed, _
New RoleChangedEventArgs.RoleChangedEventActionArgs _
(lblRoleToRename.Text, txtNewEditRoleName.Text, "", "")))
We’ll have to do a little work before we can raise our event for role description changed. We currently don’t keep our old role name so we’ll need to add a label to our HMTL code to store this. In the code for our Change Role Description Dialog, add the following line below the instructions line:
<asp:Label ID="lblOldRoleDescription" runat="server" Visible="false" />
Next, we’ll populate this label with our old name, to do this we’ll add a line to our RoleLinkButtonClick event handler. We’ll add this to the if/then block that equals “EditRoleDescription”. We’ll add this right after we populate our textbox with the role description and use the textbox’s text rather than using the GetRoleDescription function and round-tripping the database again. Add the following line:
lblOldRoleDescription.Text = txtRoleDescription.Text
Finally, we’ll raise our Role Description Changed event in the lnkRoleDescriptionSave_Click event handler. We’ll add it inside our if/then statement that checks if our rename was successful. If we’re in the first half of the if/then statement, we know that we were successful, so as the first line in the true block, raise our event as follows:
RaiseEvent RoleChanged(Me, New RoleChangedEventArgs _
(RoleChangedEventArgs.RoleChangedEventActions.DescriptionChanged, _
New RoleChangedEventArgs.RoleChangedEventActionArgs _
(lblRoleDescriptionName.Text, "", lblOldRoleDescription.Text, _
txtRoleDescription.Text)))
Lastly, we’ll raise our event for user being assigned/unassigned from a role. Some of our role manipulation is done in our in private subroutine UpdateRoleMembership. We’ll have to do a little more than just raise our event, we’ll want to store the roles as they are added and removed, so we’ll add two variables just outside our for/next loop defined as lists of string as follows:
Dim ojbAddedRoles As New List(Of String)
Dim objRemovedRoles As New List(Of String)
We’ll then add a line to each block of the if/then statements, one for when a role is added (in the added block):
objAddedRoles.Add(theCheckbox.Text)
and one to the removed block:
objRemovedRoles.Add(theCheckbox.Text)
After the end of the for/next loop, we’ll raise our event. Between the end sub and the next, add our call:
RaiseEvent UserRoleAssignmentChanged(Me, _
New RoleUserAssignmentArgs(objAddedRoles.ToArray(), _
objRemovedRoles.ToArray(), theUser))
We have to convert our list (of) variables to arrays since that’s what we’re passing. I chose to pass arrays rather than lists so any person using the control won’t have to worry about importing the System.Collections.Generic namespace.
We’ll also need to raise our events in our ChangeUserIsAssignedToRole subroutine. This is where we will assign the user to a particular role rather than assigning roles to users (When editing assignments from the role side rather than the user side). We’ll need to add two calls, one in our if/then block’s true block as follows, just after the Role.AddUserToRole call:
RaiseEvent UserRoleAssignmentChanged(Me, _
New RoleUserAssignmentArgs(New String() _
{lblRoleToAddRemoveUsersTo.Text}, Nothing, theLabel.Text))
The other we’ll add in the else block, just after the RemoveUserFromRole call:
RaiseEvent UserRoleAssignmentChanged(Me, _
New RoleUserAssignmentArgs(Nothing, New String() _
{lblRoleToAddRemoveUsersTo.Text}, theLabel.Text))
You’ll notice that our EventArgs is expecting an array, however, we are passing a single role rather than multiple so, we have to create an array and populate it with our single role. The other array that isn’t being used is set to nothing.
NOTE: the functionality of the UpdateRoleMembership subroutine needs to be changed a little to facilitate the UserRoleAssignmentChanging event. Please see these modifications in the comments.
Epilogue
There we go. Now when we perform specific actions using our control, we can report to the developer using it that the event happened and the developer can take extra actions without having to delve into the UserControl’s code and ripping it apart. Besides, it’s better encapsulation to do it this way – best practices and all.
Hopefully, we won’t run into any more things that need added ;-)