Welcome to AspAdvice Sign in | Join | Help

.Net Discoveries

An attempt to pass along some answers I have discovered in my .Net coding.
A Better ASP.Net Member/Role Management Page Pt. 6

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).**

In previous posts, we started creating “A Better ASP.Net Member/Role Management Page.” In part 1, we defined our criteria, and in part 2, part 3, part 4 and part 5 we created functionality to do all our user maintenance. In this section, we work on our role maintenance functionality.

Problem

We actually have a fair amount of things to do to maintain roles. We’ll create a grid much like the one for the user list, for the roles. We’ll also create a user GridView, so when we select a role, we can search the users and assign them to the given role (much like the user search functionality that we’ve already created. We’ll also need to create a number of dialog boxes that we can popup to get information or confirmation from the administrator. Once we’re finished, we’ll have functionality to add/remove, assign and change role names.

Solution

First things first. We need to create some user interface portions. Add the following two sections to your front-end code somewhere between our pnlFormatEverything tags:

<%-- Manage Roles --%> 
   <asp:UpdatePanel ID="upnlManageRoles" runat="server"
              ChildrenAsTriggers="true"> 
       <ContentTemplate> 
           <asp:Panel ID="pnlManageRoles" runat="server" Visible="false"> 
               <asp:Label ID="lblManageRolesTitle"
                  runat="server" Text="" /><br /> 
               <asp:Label ID="lblManageRolesInstructions" runat="server"
                  Text="Select <b>Edit Role</b> to view or change the
                  settings for the roles." /> 
               <br /><br /> 
               <asp:Table ID="tblCreateRole"
                  CssClass="tableBorders" runat="server"> 
                   <asp:TableRow> 
                       <asp:TableCell CssClass="tableHeadersNTitles"> 
                           <asp:Literal ID="lblCreateRoleTile" runat="server"
                              Text="Create New Role:" /> 
                       </asp:TableCell> 
                   </asp:TableRow> 
                   <asp:TableRow> 
                       <asp:TableCell> 
                           <asp:Label ID="lblCreateRole" runat="server"
                              Text="New Role Name:" /> 
                           <asp:TextBox runat="server" ID="txtNewRoleName"/> 
                           <cc1:TextBoxWatermarkExtender ID="tweNewRole"
                              TargetControlID="txtNewRoleName"
                              WatermarkCssClass="watermarked"
                              WatermarkText="Enter new role name"
                              runat="server" />
                           <asp:Button id="btnAddRole" runat="server"
                              Text="Add Role"/> 
                       </asp:TableCell> 
                   </asp:TableRow> 
               </asp:Table> 
               <br /> 
               <asp:GridView runat="server" CellPadding="3" 
                      GridLines="Horizontal" 
                      ID="gvRoles" 
                      CssClass="userGridView" 
                      AutoGenerateColumns="false" 
                     AllowPaging="true" 
                      PageSize="7" 
                      UseAccessibleHeader="true"> 
                   <RowStyle CssClass="gridRowStyle" /> 
                   <AlternatingRowStyle CssClass="gridAlternatingRowStyle" /> 
                   <PagerStyle CssClass="gridPagerStyle" /> 
                   <PagerSettings mode="Numeric" /> 
                   <HeaderStyle CssClass="tableHeadersNTitles"
                        Font-Bold="true" /> 
                   <Columns> 
                      <asp:TemplateField HeaderText="Role Name"> 
                         <HeaderStyle HorizontalAlign="Left" /> 
                         <ItemTemplate> 
                            <asp:Label runat="server" ID="lblRoleName"
                              ForeColor="Black"
                              Text='<%# Container.DataItem.ToString() %>' /> 
                          </ItemTemplate> 
                      </asp:TemplateField> 
                      <asp:TemplateField> 
                         <HeaderStyle HorizontalAlign="Center" /> 
                         <ItemTemplate> 
                            <asp:LinkButton ID="lnkManageUsersInRole"
                                 runat="server" Text="Assign Users"
                                 CommandName="EditUsersInRole"
                                 CommandArgument='<%#
                        Container.DataItem.ToString() %>' ForeColor="Black"
                                  OnCommand="RoleLinkButtonClick" /> 
                         </ItemTemplate> 
                      </asp:TemplateField> 
                      <asp:TemplateField> 
                         <ItemTemplate> 
                            <asp:LinkButton ID="lnkDeleteRole" runat="server"
                              Text="Delete Role" CommandName="DeleteRole" 
                    CommandArgument='<%# Container.DataItem.ToString() %>'
                              ForeColor="Black"
                              OnCommand="RoleLinkButtonClick" /> 
                         </ItemTemplate> 
                      </asp:TemplateField> 
                      <asp:TemplateField> 
                         <HeaderStyle HorizontalAlign="Left" /> 
                         <ItemTemplate> 
                             <asp:LinkButton ID="lnkEditRoleName" runat="server"
                                Text="Change Role Name"
                                CommandName="EditRoleName" 
                    CommandArgument='<%# Container.DataItem.ToString() %>'
                                 ForeColor="Black"
                                 OnCommand="RoleLinkButtonClick" /> 
                         </ItemTemplate> 
                      </asp:TemplateField> 
                   </Columns>
                </asp:GridView>
                <asp:Label runat="server" ID="lblNoRoles" Visible="false"
                     Text="No roles have been created." />
                <br /><br />
                <asp:LinkButton ID="lnkManagerUsers"  runat="server"
                     Text="Manager Users" />
            </asp:Panel>
        </ContentTemplate>
    </asp:UpdatePanel> 


   <%-- Assign/Remove Users from Role --%> 
   <asp:UpdatePanel ID="upnlAddRemoveUsersFromRole"
            runat="server" ChildrenAsTriggers="true"> 
      <ContentTemplate> 
         <asp:Panel ID="pnlAddRemoveUsersFromRole" runat="server"
                  Visible="false"> 
            <asp:Label ID="lblAddRemoveUsersFromRoleTitle" runat="server"
                  Text="" /><br /> 
            <asp:Label ID="lblAddRemoveUsersFromRoleInstructions"
               runat="server" 
               Text="Use this page to manage the members in the specified role. To add a user to the role, search for the user name and then select User Is In Role for that user. " /> 
            <br /><br /> 
            Role:
            <b>
               <asp:Literal runat="server" ID="lblRoleToAddRemoveUsersTo" />
            </b> 
            <asp:Table id="tblSearchForUsersRole" runat="server"
                  CssClass="tableBorders">
               <asp:TableRow> 
                  <asp:TableCell>
                     <asp:Literal ID="lblSearchForUsersRole" runat="server"
                        Text="Search For Users" />
                     </asp:TableCell> 
                  </asp:TableRow> 
                  <asp:TableRow>
                     <asp:TableCell CssClass="tableBody">
                        <asp:Label id="lblSearchForUsersRoleSearchBy"
                           runat="server" Text="Search By: " /> 
                        <asp:DropDownList ID="ddlSearchForUsersRole"
                              runat="server"> 
                           <asp:ListItem id="item3" Text="Username"
                              runat="server" /> 
                           <asp:ListItem id="item4" Text="Email" runat="server" /> 
                        </asp:DropDownList> 
                        &nbsp;
                        <asp:Label ID="lblSearchForRoles"
                           runat="server" Text="for: " /> 
                        <asp:TextBox id="txtSearchUsersForRoles"
                           runat="server" />&nbsp; 
                        <cc1:TextBoxWatermarkExtender
                           ID="tweSearchUsersForRoles"
                           TargetControlID="txtSearchUsersForRoles"
                           WatermarkCssClass="watermarked"
                           WatermarkText="Enter criteria" runat="server" />
                        <asp:Button id="btnSearchUsersForRoles"
                           runat="server" Text="Search For" /> 
                        <br /> 
                        <asp:Label runat="server" ID="lblWildcardAllowed"
                           Text="Wildcards * and ? are permitted." />
                        <br /> 
                        <asp:Repeater runat="server"
                           ID="rptAlphabetRepeaterRoles" >
                           <ItemTemplate>
                              <asp:LinkButton runat="server" id="lnkLetterRoles"
                                 CommandName="Display"
                                 CommandArgument="<%# Container.DataItem %>"
                                 Text="<%# Container.DataItem %>" />&nbsp; 
                            </ItemTemplate> 
                        </asp:Repeater> 
                     </asp:TableCell> 
                 </asp:TableRow> 
             </asp:Table> 
             <br /> 
             <asp:GridView runat="server" CellPadding="3"
               GridLines="Horizontal" ID="gvUsersForRole"
               CssClass="userGridView" AutoGenerateColumns="false"
               AllowPaging="true" PageSize="7" UseAccessibleHeader="true">
                  <RowStyle CssClass="gridRowStyle" /> 
                  <AlternatingRowStyle CssClass="gridAlternatingRowStyle" /> 
                  <PagerStyle CssClass="gridPagerStyle" /> 
                  <PagerSettings mode="Numeric" /> 
                  <HeaderStyle CssClass="tableHeadersNTitles"
                        Font-Bold="true" /> 
                  <Columns> 
                     <asp:TemplateField HeaderText="Username"> 
                        <HeaderStyle HorizontalAlign="Left" /> 
                        <ItemTemplate> 
                           <asp:Label ID="lblUsernameRole" runat="server"
                              ForeColor="Black"
                              Text="<%# Container.DataItem %>" /> 
                        </ItemTemplate> 
                     </asp:TemplateField> 
                     <asp:TemplateField HeaderText="User is in role"> 
                        <HeaderStyle HorizontalAlign="Center" /> 
                        <ItemStyle HorizontalAlign="Center" /> 
                        <ItemTemplate> 
                           <asp:CheckBox runat="server" ID="chkUserInRole"
                              OnCheckedChanged="ChangeUserIsAssignedToRole"
                              AutoPostBack="true" 
                            Checked='<%# IsUserInRole(Container.DataItem) %>' />
                            </ItemTemplate> 
                      </asp:TemplateField> 
                  </Columns> 
             </asp:GridView> 
             <asp:Label runat="server" ID="lblNoUsersFoundRoles"
               visible="false" EnableViewState="false"
               Text="No users found for this search." /> 
             <br /><br /> 
             <asp:LinkButton runat="server" ID="lnkManageAllRoles"
                  Text="Manage All Roles" />&nbsp;&nbsp; 
             <asp:LinkButton runat="server" ID="lnkManageUsersFromRoles"
                  Text="Manage Users" /> 
          </asp:Panel> 
      </ContentTemplate> 
  </asp:UpdatePanel>

You may want to put it towards the top, because putting it to the bottom will cause it to actually display too far toward the bottom. This is due to the fact that a lot of the other sections are styled with display:none. Doing this will leave a section of the rendered page for the sections’ outputs (even if they’re hidden) and push our role management stuff down so it looks weird. Since the other sections are mostly modal dialogs, we can put them at the bottom and they’ll still work as designed.

Before we get to far into it, we’ll also want to add some stuff to our SetUI code block. We’ll be managing a number of panels and UI pieces on various clicks, so let’s add the following to our SetUIModes enumeration:

EditUsersInRole
ManageAllRoles
DeleteRoleConfirm
RenameRoleConfirm
RenameRole

and the following case statements to our SetUI case statement:

Case SetUIModes.EditUsersInRole
    pnlManageRoles.Visible = False
    pnlAddRemoveUsersFromRole.Visible = True
Case SetUIModes.ManageUsers ‘this line may exist already in your code…
    pnlManageRoles.Visible = False
    pnlAddRemoveUsersFromRole.Visible = False
    pnlManageUsers.Visible = True
Case SetUIModes.ManageAllRoles
    pnlManageRoles.Visible = True
    pnlManageUsers.Visible = False
    pnlAddRemoveUsersFromRole.Visible = False
Case SetUIModes.DeleteRoleConfirm
    mpeDeleteRoleConfirm.Show()
Case SetUIModes.RenameRoleConfirm
    mpeRoleRenameConfirm.Show()
Case SetUIModes.RenameRole
    mpeChangeRoleName.Show()

You may have noticed that we previously (in part 3) created a case statement for ManageUsers, we just never populated the code for the case. We can now add this functionality, so just add the code for that case as follows:

pnlManageRoles.Visible = False
pnlAddRemoveUsersFromRole.Visible = False
pnlManageUsers.Visible = True

Now we can get down to coding the pieces we need to make it all functional. First we’ll want to enable the ‘Manage Roles’ link so that we can access our manage role information. This is pretty easy, add the following to your lnkManageRoles_Click event handler:

SetUI(SetUIModes.ManageAllRoles)
SetDataSource(Roles.GetAllRoles, ROLES_DATA_SOURCE)
BindGrid(gvRoles, ROLES_DATA_SOURCE, True)

gvRoles.DataSource = Roles.GetAllRoles()
gvRoles.DataBind()

This will hide our user GridView, and display our Roles GridView. In addition, it will store our datasource and then populate our Roles GridView with all the roles in the database.

Continuing with the easy stuff, once we switch over to the Manage Roles side, we’ll have a link that takes us back to managing users, we should also hook that up. Add the following to your lnkManageUsers_Click event handler:

SetUI(SetUIModes.ManageUsers)

Really all we’re doing is hiding one and showing the other, not much to it.

Next, let’s hook up our Add Role functionality. Add the following to your btnAddRole_Click event handler:

Try
    Roles.CreateRole(txtNewRoleName.Text)
    txtNewRoleName.Text = String.Empty
    gvRoles.DataSource = Roles.GetAllRoles()
    gvRoles.DataBind()
Catch ex As Exception
    lblMessageDialog.Text = ex.Message
    SetUI(SetUIModes.MessageDialog)
End Try

This is a little more complex, but still pretty simple. We put our attempt to add the role within a try catch block so that if something goes wrong: A. it doesn’t crash, and B. we can let the administrator know what the matter is. We’ll first attempt to create the role, if we get an error, this is where it will happen. If adding the role is successful, then we’ll clear the textbox, and then rebind our GridView with fresh data. If adding the role fails, we’ll display our message dialog box, and show the administrator the error that was returned in the exception.

Next, we’ll concentrate on the links in each row of the GridView. Let’s start with the ‘Delete Role’ link. If you notice in our front-end code, each or our links in the row of our GridView calls ‘RoleLinkButtonClick’ when they are clicked. This means that we’ll need to create a RoleLinkButtonClick event handler in our code. We’ll need to include information typical to an event handler as far as parameters are concerned. Since all the links call the same event handler, we’ll need to  determine which was selected, we’ll use a if then statements for that. Create your event handler as follows:

Public Sub RoleLinkButtonClick(ByVal sender As Object, _
    ByVal e As CommandEventArgs) 
   
   ‘If e.CommandArgument.Equals("DeleteRole") Then
   If e.CommandName.Equals("DeleteRole") Then

   End If
End Sub

We’ve just created a shell for our event handler and for our delete role section of it. Before we actually delete anything we’ll want to confirm with the administrator that they want to delete the role. We’ll need to create a confirmation dialog in our front-end so that we can display it. Add the following to your front-end code. It is a dialog, so it can go towards the bottom if you so desire:

<%-- Delete Role Confirmation Dialog --%>
<asp:UpdatePanel ID="upnlDeleteRoleConfirmation" runat="server"
       ChildrenAsTriggers="true">
   <ContentTemplate>
      <asp:Panel ID="pnlDeleteRoleConfirmation" runat="server"
               CssClass="modalPopup" style="display:none; text-align:center;">
         <asp:Literal runat="server" ID="lblDeleteRoleConfirmMessage"
            Text="" />
         <asp:Literal runat="server" ID="lblRoleToDelete" Visible=”False” />
         <br><br />
         <asp:Button runat="server" ID="btnDeleteRoleYes" Text="Yes" />
            &nbsp; 
         <asp:Button runat="server" ID="btnDeleteRoleNo" Text="No" /> 
      </asp:Panel>
      <asp:Button runat="server"
         ID="btnFakeShowMPEDeleteRoleConfirmButton"
         style="display: none;" />
      <cc1:ModalPopupExtender ID="mpeDeleteRoleConfirm" runat="server"
         TargetControlID="btnFakeShowMPEDeleteRoleConfirmButton"
         PopupControlID="pnlDeleteRoleConfirmation"
         BackgroundCssClass="modalBackground" />
   </ContentTemplate>
</asp:UpdatePanel>

We create a dialog, not much of note, a yes and a no button. The one exception is a hidden literal control that we can use to later pull the associated username. With the dialog complete, we can add the innards to our event handler. Add the following to the If statement for ‘DeleteRole’:

lblRoleToDelete.Text = CStr(e.CommandArgument)
lblDeleteRoleConfirmMessage.Text = "Are you sure you want to delete role: "
   & lblRoleToDelete.Text
SetUI(SetUIModes.DeleteRoleConfirm)

This just loads up our dialog, and then show it. Now we need to hookup our ‘Yes’ and ‘No’ buttons. First, let’s hookup our ‘No’ button, add the following to our btnDeleteRoleNo_Click event handler:

mpeDeleteRoleConfirm.Hide()

Basically, we just hide the dialog when the user clicks ‘No’. Now, for our ‘Yes’ button, add the following to our btnDeleteRoleYes_Click event handler:

Try
    Roles.DeleteRole(lblRoleToDelete.Text)
    gvRoles.DataSource = Roles.GetAllRoles()
    gvRoles.DataBind()
Catch ex As Exception
    lblMessageDialog.Text = ex.Message()
    SetUI(SetUIModes.MessageDialog)
End Try

Again, we’ve got a try catch block in case something terrible happens. We’ll attempt to delete the role, if it fails, we’ll jump to the catch portion of the bock and display the failure message. If it is successful, we’ll refresh the GridView and display the new data.

Next, well hookup the functionality for assigning users to the role. First, we’ll want to setup a constant for our Users_In_Role datasource add the following constant to your back-end code:

Private Const USERS_IN_ROLE_DATA_SOURCE As String = _ 
    "USERS_IN_ROLE_DATA_SOURCE"

Then, we’ll setup a helper function to handle binding the users in role grid and accounting for an empty dataset. Add the following helper subroutine to your code:

Public Sub BindUsersForRoleGrid() 
  gvUsersForRole.DataSource = theSession(ROLES_DATA_SOURCE
  gvUsersForRole.DataSource = _
        theSession(USERS_IN_ROLE_DATA_SOURCE)

  gvUsersForRole.DataBind() 
  If gvUsersForRole.Rows.Count = 0 Then 
      lblNoUsersFoundRoles.Visible = True 
  End If
End Sub

We’re creating a subroutine that will populate our GridView’s data source and then examine the GridView. If it is empty, then we’ll show the message about no users being assigned to to the role.

Now we’re ready to add the actual functionality for when we click the ‘Assign Users’ link in the role’s row. Add the following to your RoleLinkButtonClick event handler, after the End If for DeleteRole:

If e.CommandName.Equals("EditUsersInRole") Then
    lblRoleToAddRemoveUsersTo.Text = CStr(e.CommandArgument)
    PopulateAlphabetRepeater(rptAlphabetRepeaterRoles)
    SetDataSource(Roles.GetUsersInRole _ 
             (lblRoleToAddRemoveUsersTo.Text), ROLES_DATA_SOURCE)
    SetDataSource(Roles.GetUsersInRole _
        (lblRoleToAddRemoveUsersTo.Text), _
         USERS_IN_ROLE_DATA_SOURCE)
    BindUsersForRoleGrid() 
    SetUI(SetUIModes.EditUsersInRole)
End If

With that added, we should be able to click our ‘Assign Users’ link and have our ‘Users in Role’ GridView displayed (if there are users in the role). You’ll notice though, that there are a couple of links at the bottom to allow us to navigate back to other editing functions, but they don’t currently work. Let’s hook them up so we can return. Add the following to our lnkManageAllRoles_Click event handler:

SetUI(SetUIModes.ManageAllRoles)

and add the following to our lnkManageUsersFromRoles_Click event handler:

SetUI(SetUIModes.ManageUsers)

Now we should be able to return to previous screens without problem. If you run our application, select to manage roles and then select to assign users to a role, only the users that are currently assigned to our role will be displayed in the GridView. To see other users and assign them to the role, we’ll need to hookup the functionality for the alphabet repeater and search portion at the top. First, let’s create a helper function. This function will take a MembershipUserCollection and convert it to a string array containing usernames. We’ll do this because when we pull Roles.GetUsersInRole, we’ll get a string array, but when we pull from the Membership.FindUser functions, we’ll get a MembershipUserCollection back. Since we’re binding both lists to the same GridView (depending on how we search), it’s a lot easier if they both go to our BindGrid as the same data types. So, add the helper function as follows:

Private Function ConvertMUCollToStringArray( _
  ByVal theMembershipCollection As MembershipUserCollection) As String()

    Dim usersInRole As New List(Of String)
    For Each user As MembershipUser In theMembershipCollection
        usersInRole.Add(user.UserName)
    Next
    Return usersInRole.ToArray()
End Function

This will iterate through the collection, and put the users in a format that we can return as an array. Next, we’ll add functionality for clicking on a letter. Add the following to the rptAlphabetRepeaterRoles_ItemCommand event handler:

gvUsersForRole.PageIndex = 0
Dim arg As String = e.CommandArgument.ToString()

Dim users As MembershipUserCollection = Nothing
If arg.ToLower() = "all" Then
    users = Membership.GetAllUsers()
Else
    users = Membership.FindUsersByName(arg + "%")
End If
SetDataSource(ConvertMUCollToStringArray(users), _ 
  
ROLES_DATA_SOURCE)
BindGrid(gvUsersForRole, ROLES_DATA_SOURCE, True)

SetDataSource(ConvertMUCollToStringArray(users), _
     USERS_IN_ROLE_DATA_SOURCE)
BindGrid(gvUsersForRole, USERS_IN_ROLE_DATA_SOURCE, True)

If gvUsersForRole.Rows.Count = 0 Then
    lblNoUsersFoundRoles.Visible = True
End If

First, we’ll reset the PageIndex on our Gridview, and the pull the argument that was passed in (the letter that was clicked). Next, we’ll create an object to hold the the users found when searching for our argument, and then pull the appropriate subset of users. Next, we set our datasource (notice that in the process we convert our users object through our helper function to a string array), and then bind our GridView. Finally, we’ll check to see if we have an empty dataset and if so, display a message that no users were found.

Finally, in relation to our search portion, we’ll hookup the search button for the text entry portion. Add the following to our btnSearchUsersForRoles_Click event handler:

Dim users As MembershipUserCollection = _
    SearchForUsers(ddlSearchForUsersRole, txtSearchUsersForRoles)
SetDataSource(ConvertMUCollToStringArray(users), ROLES_DATA_SOURCE)
BindGrid(gvUsersForRole, ROLES_DATA_SOURCE, True)
If gvUsersForRole.Rows.Count = 0 Then
    lblNoUsersFoundRoles.Visible = True
End If

When the user clicks the search button, we’ll retrieve a collection of MembershipUser objects, meeting our criteria. Then we’ll set our datasource, once again converting to string array, and then bind our GridView with the results. Finally, we’ll check for empty dataset, and display a message if no users are found.

Next, let’s add functionality for the ‘User is in Role’ checkbox. When we check or uncheck it, it will update the database adding or removing the user from the role. In our front-end code, we specify that the checkbox’s onCheckChange event calls the ‘ChangeUserIsAssignedToRole’ subroutine. Define it as follows:

Public Sub ChangeUserIsAssignedToRole(ByVal sender As Object, _
        ByVal e As EventArgs)
    Dim theCheckbox As CheckBox = CType(sender, CheckBox)
    Dim theItem As GridViewRow = _
           CType(theCheckbox.Parent.Parent, GridViewRow)
    Dim theLabel As Label = _ 
           CType(theItem.FindControl("lblUsernameRole"), Label)
    If theCheckbox.Checked = True Then
        Roles.AddUserToRole(theLabel.Text, _ 
            lblRoleToAddRemoveUsersTo.Text)
    Else
        Roles.RemoveUserFromRole(theLabel.Text, _
            lblRoleToAddRemoveUsersTo.Text)
    End If
End Sub

First, we retrieve checkbox that was clicked (it’s passed in as a parameter), and then use that to retrieve the GridViewRow containing the checkbox (the checkbox’s parent’s parent). From that we’ll get the username. If the checkbox is now checked, we’ll add the user to the role, if not, we’ll remove the user from the role.

That finishes all the functionality for the ‘Assign Users’ link. Next, we’ll concentrate on the ‘Change Role Name’ functionality. First, we’ll need to create some front-end dialogs to support this functionality. We’ll need to create a confirmation dialog, and we’ll need to create a dialog that allows us to input the new name. We also need a dialog to confirm the name change. Add the following to your front-end code anywhere inside our pnlFormatEverything panel:

<%-- Change Role Name Dialog --%>
<asp:UpdatePanel id="upnlChangeRoleName" runat="server" ChildrenAsTriggers="true">
    <ContentTemplate>
        <asp:Panel ID="pnlChangeRoleName" runat="server"
                 style="display: none;" CssClass="modalPopup"
                HorizontalAlign=”center”>
            Enter a new role name.<br /><br />
            <asp:Table runat="server">
                <asp:TableRow>
                    <asp:TableCell HorizontalAlign="Right">
                         Current role name:
                    </asp:TableCell>
                    <asp:TableCell HorizontalAlign="Left">
                        <asp:Label runat="server" ID="lblCurrentRoleName" />
                    </asp:TableCell>
                    <asp:TableCell></asp:TableCell>
                </asp:TableRow>
                <asp:TableRow>
                    <asp:TableCell HorizontalAlign="Right">
                         New role name:
                    </asp:TableCell>
                    <asp:TableCell HorizontalAlign="Left">
                        <asp:TextBox ID="txtNewEditRoleName" runat="server" />
                        <cc1:TextBoxWatermarkExtender ID="tweNewRoleName"
                           TargetControlID="txtNewEditRoleName"
                           WatermarkText="Enter new role name"
                           WatermarkCssClass="watermarked" runat="server" />
                    </asp:TableCell>
                    <asp:TableCell>
                        <asp:RequiredFieldValidator ID="rfvNewRoleName"
                           runat="server" Display="Dynamic" text="*"
                           ControlToValidate="txtNewEditRoleName"  
                           ValidationGroup="ChangeRoleName"
                           ErrorMessage="You must enter a new role name."  />
                    </asp:TableCell>
                </asp:TableRow>
                <asp:TableRow>
                    <asp:TableCell ColumnSpan="3">
                        <asp:ValidationSummary ID="vsNewRoleName"
                           runat="server" EnableClientScript="true"
                           DisplayMode="List" ShowSummary="true" 
                           ValidationGroup="ChangeRoleName" />
                    </asp:TableCell>
                </asp:TableRow>
            </asp:Table>
            <br />
            <asp:LinkButton ID="lnkRenameRoleSave"
                CausesValidation="true" runat="server" Text="Save" 
                ValidationGroup="ChangeRoleName" />&nbsp;
            <asp:LinkButton ID="lnkRenameRoleCancel" runat="server"
                Text="Cancel" />
        </asp:Panel>
        <asp:Button ID="btnFakeShowMPEChangeRoleName" runat="server"
             style="display: none;" />
        <cc1:ModalPopupExtender id="mpeChangeRoleName" runat="server"
            TargetControlID="btnFakeShowMPEChangeRoleName"
            PopupControlID="pnlChangeRoleName"
            BackgroundCssClass="modalBackground" />
    </ContentTemplate>
</asp:UpdatePanel>

<%-- Role rename confirm dialog --%>
<asp:UpdatePanel ID="upnlChangeRoleNameConfirm" runat="server">
    <ContentTemplate>
        <asp:Panel ID="pnlChangeRoleNameConfirm" runat="server"
              style="display:none;" CssClass="modalPopup"
              HorizontalAlign="Center">
            <asp:Literal ID="lblRoleToRename" runat="server" isible="false" />
            <asp:Literal ID="lblConfirmRoleRename" runat="server" Text="" />
            <br /><br />
            <asp:Button ID="btnRoleRenameYes" runat="server" Text="Yes" />
            <asp:Button ID="btnRoleRenameNo" runat="server" Text="No" />
        </asp:Panel>
        <asp:Button ID="btnFakeShowMPERoleRenameConfirm" runat="server"
             style="display:none;" />
        <cc1:ModalPopupExtender runat="server"
            ID="mpeRoleRenameConfirm"
            TargetControlID="btnFakeShowMPERoleRenameConfirm"
            PopupControlID="pnlChangeRoleNameConfirm"
            BackgroundCssClass="modalBackground" />
    </ContentTemplate>
</asp:UpdatePanel>

Now, we just need to add the functionality. We’ll need to add another block to our RoleLinkButtonClicked event handler. Add the following If Then statement to the subroutine:

If e.CommandName.Equals("EditRoleName") Then
    lblCurrentRoleName.Text = CStr(e.CommandArgument)
    mpeChangeRoleName.Show()
End If

Basically, we’re just displaying our change role name dialog. We do populate a label in the dialog so that the administrator knows what role is being changed. Next, we need to add functionality to our ‘Save’ and ‘Cancel’ buttons. Add the following to our lnkRenameRoleSave_Click event handler:

lblRoleToRename.Text = lblCurrentRoleName.Text

lblConfirmRoleRename.Text = "Warning, if the role name you are changing is used in your code, renaming a role may break your code. Please proceed with caution!</br><br>Are you sure you want to rename '" & lblRoleToRename.Text & "' to '" & txtNewRoleName.Text & "'?"

SetUI(SetUIModes.RenameRoleConfirm)

We store our role name so that we have it available when we click ‘Yes’, and set a long warning to the administrator letting them know that if they rename a role, they could possibly break their code. Renaming roles is another one of those features that is unsupported by Microsoft. In Microsoft’s defense, it really is a questionable practice though. If you have specified anything in your application calling the role by name, when you rename it here, it will no longer trigger the appropriate functionality in your application, thus breaking your code. (USE THIS FEATURE WISELY…) Add the following to our lnkRenameRoleCancel_Click event handler:

txtNewEditRoleName.Text = String.Empty
lblCurrentRoleName.Text = String.Empty

This just clears out anything hanging around when we cancel the dialog.

Finally, we’ll want to hook up our ‘Yes’ and ‘No’ buttons on our confirmation dialog. We’ll start with ‘No’. Add the following to our btnRoleRenameNo_Click event handler:

SetUI(SetUIModes.RenameRole)

We will just go back to the change role dialog if the administrator doesn’t confirm. Next, add the following to our btnRoleRenameYes_Click event handler:

Dim usersInRole As String() = Roles.GetUsersInRole(lblRoleToRename.Text)
Roles.CreateRole(txtNewEditRoleName.Text)
If usersInRole.Length > 0 Then
    Roles.AddUsersToRole(usersInRole, txtNewEditRoleName.Text) 
    Roles.RemoveUsersFromRole(usersInRole, lblRoleToRename.Text)
End If
Roles.DeleteRole(lblRoleToRename.Text)
txtNewEditRoleName.Text = String.Empty
lblRoleToRename.Text = String.Empty
SetUI(SetUIModes.ManageAllRoles)
gvRoles.DataSource = Roles.GetAllRoles()
gvRoles.DataBind()

You’ll notice, that we can’t just rename the role, the functionality isn’t included in the Microsoft Role provider, so we have to work around it. What we’ll do, is retrieve the users in the role that we want to rename, we’ll check that string array that we retrieve, and if it has any elements, we know that there are users in the role. We’ll add these users to our new role (after we create it) and then remove those users from the old role, and then delete the old role (since we can’t delete a role with users in it). (That’s our work around, add all the users from our old role to the new role, remove them from the old role, and then delete the old role). Once we are done, we’ll clear our dialog and then close it. Finally, we’ll rebind our GridView so that it displays our new role name.

**Author’s Note** – I found one more small problem, paging for the Role’s grids don’t work. Unfortunately in my development, I didn’t put more than one page worth of anything into my database (users or roles). Thus, I never checked to make sure my paging worked, I never needed to page. As a result, paging for both gvRoles and gvUsersForRole will return a JavaScript error. This is because we don’t handle their PageIndexChanging event handler. Since we’re using AJAX, the JavaScript will call the back-end, but cause an error because the event handler doesn’t exist. So to remedy this problem we’ll add the following lines to our gvRoles_PageIndexChanging event handler:

gvRoles.PageIndex = e.NewPageIndex
BindGrid(gvRoles, ROLES_DATA_SOURCE, False)

and the following to our gvUsersForRole_PageIndexChanging event handler:

UsersForRole.PageIndex = e.NewPageIndex
BindGrid(gvUsersForRole, ROLES_DATA_SOURCE, False)
BindGrid(gvUsersForRole, USERS_IN_ROLE_DATA_SOURCE, False)

You’ll also notice that I made a little change in our lnkManageRoles_click event handler. To be able to use paging, we need to store our datasource, so rather than binding our gvRoles directly from our Roles provider (Roles.GetAllRoles()), we’ll set our DataSource and then use it later when we call our PageIndexChanging event handler.

Now paging works. Funny what you find when you do some real testing…

Epilogue

And there we have it. The final pieces of our functionality. All we need to manage all aspects of our Roles. We can now Add, Delete, Rename, and assign/unassign users from our roles.

Technically, we’re done with our control, we’ve done everything we need to do to successfully use it, however, I think that I’ll do one more post in this series offering some optional enhancements that I may someday use if I feel like it, but that may come in useful none the less.

Posted: Wednesday, January 21, 2009 12:47 AM by Yougotiger

Comments

daneck said:

Thanks for the series. I had to make a few tweaks, but overall it was easy to follow, easy to understand, easy to implement, and best of all written in VB. I had to do some research on how to work with InnerText, but that was easy enough. As a side note... divUserAvailablity is missing from the frontend writeup.

For anyone out there that plans on just trying to cut-n-paste watch out for for quote marks, and there are a few areas that names are changed between the code and narration. Personally, I'd recommend typing it in line-by-line to have time to actually understand what all is happening.

A few 'modifications' that I hope to be able to implement/figure out are:

1. Converting all the tables to Divs.
2. Add Auto-Complete to the User Search to have the listing update automatically.
3. Figure out how to expose the styles used in the control's properties to make it easier to blend in with existing sites.
4. figure out how to do some form of hierarchy user admin, as I currently have several branches that need to be restricted to employees for their specific store and/or department. Thanks again for a wonderful series. I've tried several over the past few years, and this by far is the best solution (in my humble opinion).

daneck

# February 1, 2009 8:42 PM

Yougotiger said:

 **Author Comment**

 Daneck,

Thanks for the comments, I fixed the missing divUserAvailability on the page... see comment on part #5.

I would also encourage everyone to do the typing so they understand it, but I had hoped they didnt' NEED to. If you know of any inconsistencies, please let me know I'll correct them. I'll see what I can find. I apologize the narrative doesn't always match :-(

As for modifications:

1. I don't really relish using tables for layout, however this time I opted for ease over usability (naughty me).

2. hmmm.. that didn't occur to me? Maybe I'll toy with that in post #7

3. I was planning on exposing the CssClass Properties for the elements as part of post #7, easier in my opinion than exposing all the controls properties individually... easy to do too. I think I touched on how to do it when we talked about enabling the username change...

4. hmmm. that didn't occur to me either, however I don't have a need for that at the moment would probably be why. good luck with it. If you figure it out, let us know.

Finally, thanks for the pat on the back. In all my searching, I couldn't find anything that I thought came close to filling my needs in very usable format. So I think it's the best solution as well... (course I'm biased ;-)  ... and I have to give Microsoft a hand, I pretty much mimic'd a lot of what they already did.

# February 2, 2009 2:16 PM

Yougotiger said:

**Author Comment**

Due to some fat fingering that wasn't fixed, in our RoleLinkButtonClick event handler, you'll want to make sure you use CommandName rather than CommandArgument. (Thanks again Ace).

# February 5, 2009 2:37 PM

Yougotiger said:

**Author Comment**

Ok, one more correction. Apparently I didn't get my paging put together for gvRoles and gvUsersForRoles. Since I didn't add many roles or users, I never paged in my development, well I did in gvUsers, and it works, but I forgot to do it for the other GridViews. You'll find I addressed it in the lnkManageRoles_click event handler snippet above and that I added a couple paragraphs towards the end of the post that handle the rest.

# April 7, 2009 2:45 AM

Yougotiger said:

**Author Comment**

You'd think I'd get it right by now, but I haven't (I gotta stop doing this that late at night). I found that if I went to manage roles, and then to assign users, then return to the manage all Roles grid, that paging would insert the dataset from the Users_in_role dataset. This was becuase I was using the same dataset for gvRoles and gvUsersInRole - I shouldn't have.

Therefore I added a constant above for the Users_In_Role_Data_Source and then changed all locations where I used that data source from the Roles_Data_Source to the Users_In_Role_Data_Source. This meant chaning it in a number of places, you'll see the lines that have the strike through above and a line below with the correct constant.

Sorry for the inconvenience.

# April 7, 2009 7:11 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Enter the code you see below

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS