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. 5

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

In previous posts we started creating “A Better ASP.Net Member/Role Management Page.” In part 1, we set criteria, and in part 2, part 3 and part 4 we created functionality to add, delete and edit users. We’ve finished nearly all the functionality for editing users with one exception, changing usernames. We’ll be addressing that in this post.

Problem

Changing a username is functionality that isn’t included in the default Microsoft membership provider. The reason for this, in my understanding, is that changing usernames is considered somewhat of a security issue. Really,i don’t understand why it isn’t included, changing a username could be put together in such a way that it is reasonably secure, you know require passwords and or security questions etc. like practically everything else that you try to change using the membership provider does. Although the functionality isn’t included in the Microsoft provider, we can add the functionality on our own (and it’s actually relatively easy).

Solution

Unfortunately, this functionality will be the one part that we won’t be able to contain fully in our page, we’ll have to add a stored procedure to our database. Well, I guess we could put all our SQL stuff in a subroutine, but that’s leaves us with a security problem, accepting user in put and just inserting into a SQL statement and running it directly form our page leaves us open for SQL injection attack. The most secure thing would be to create a stored procedure in the database, and like I said that means that we don’t have a fully self contained page.

To workaround this limitation, what we’ll do is add a property to the page so that we can indicate that we have created the stored procedure and we can change the username. This will turn on/off the ‘change username’ link on our page.

Let’s start with the SQL stored procedure. If you’ve ever looked at a database that’s enabled for the ASP.net membership provider you will notice that enabling a database for ASP.Net membership provider adds a number of stored procedures. We’ll add another stored procedure and we’ll use the same naming convention. To add the stored procedure execute the following SQL code:

CREATE PROCEDURE aspnet_Membership_ChangeUsername
    @currentUsername nvarchar(255),
    @newUsername nvarchar(255),
    @results bit OUTPUT
AS
    UPDATE aspnet_Users
    SET UserName = @newUsername,
        LoweredUserName = LOWER(@newUsername)
    WHERE LOWER(UserName) = LOWER(@currentUsername)
    set @results = @@ROWCOUNT

The stored procedure here will take three parameters, one for each of the usernames, the new and the old, and one parameter to output the success of the operation. It will return 0 on failure and 1 on success (as one row was changed on success). The actual username data is actually only stored in one location, in the user table. Rather than using the username as the ID to link with all other tables, there’s a unique UserID in the user table that is used. Changing the username is easy, we have two places to change it in one table and it doesn’t take much.

Once that’s done, we need to add the functionality to our page. We don’t have much to add to the front-end code, just the change username dialog and a new style. Add the following to your style section:

.taken
{
    color: Red;
}

and the following to your front end code. Place it at the end of the page, but before our closing tag for our pnlFormatEverything panel:

<%-- Change Username Dialog --%>
<asp:UpdatePanel id="upnlChangeUsername" runat="server" ChildrenAsTriggers="true">
    <ContentTemplate>
        <asp:Panel ID="pnlChangeUsername" runat="server"
                style="display: none;
                text-align:center;" CssClass="modalPopup">
            <b>Enter a New Username</b><br /><br />
            <asp:Table runat="server" ID="tblChangeUsername">
                <asp:TableRow>
                    <asp:TableCell HorizontalAlign="Right">
                       Current Username:
                    </asp:TableCell>
                    <asp:TableCell HorizontalAlign="Left">
                        <asp:Label ID="lblCurrentUsername"
                             runat="server" Text="" />
                    </asp:TableCell>
                    <asp:TableCell></asp:TableCell>
                </asp:TableRow>
                <asp:Tablerow>
                    <asp:TableCell HorizontalAlign="Right">
                       New Username:
                    </asp:TableCell>
                    <asp:TableCell HorizontalAlign="Left">
                        <asp:TextBox ID="txtNewUsername" runat="server" />
                        <div runat="server" id="divUserAvailablity"
                             class="taken"></div>

                        <cc1:TextBoxWatermarkExtender
                        ID="tbeNewUsername" runat="server"
                        TargetControlID="txtNewUsername"
                        WatermarkText="Enter New Username"
                        WatermarkCssClass="watermarked">
                        </cc1:TextBoxWatermarkExtender>
                    </asp:TableCell>
                    <asp:TableCell>
                        <asp:RequiredFieldValidator runat="server"
                                ID="rfvNewUsername"
                                ControlToValidate="txtNewUsername"
                                Display="Dynamic"
                                ValidationGroup="ChangeUsername"
                                ErrorMessage="You must enter a new username"
                                Text="*" />
                    </asp:TableCell>
                </asp:Tablerow>
                <asp:TableRow>
                    <asp:TableCell ColumnSpan="3">
                        <asp:ValidationSummary ID="vsNewUsername"
                               ValidationGroup="ChangeUsername" runat="server"
                               DisplayMode="List" ShowSummary="true"
                               EnableClientScript="true" />
                    </asp:TableCell>
                </asp:TableRow>
            </asp:Table>
            <asp:LinkButton ID="lnkChangeUsernameSave"
                  runat="server" Text="Save"
                  CausesValidation="true" ValidationGroup="ChangeUsername" />
                  &nbsp;&nbsp;&nbsp;
            <asp:LinkButton ID="lnkChangeUsernameCancel"
                 runat="server" Text="Cancel" />
        </asp:Panel>
        <asp:Button ID="btnFakeShoeMPEChangeUsernameDialogButton"
               runat="server" style="display:none;" />
        <cc1:ModalPopupExtender ID="mpeChangeUsername" runat="server"
        TargetControlID="btnFakeShoeMPEChangeUsernameDialogButton"
        PopupControlID="pnlChangeUsername"
        BackgroundCssClass="modalBackground">
        </cc1:ModalPopupExtender>
    </ContentTemplate>
</asp:UpdatePanel>

Basically we’re just creating a label to put in the existing username, a textbox for the new username and then save and cancel buttons. We also add a div so that we can tell the user if the new username is already being used.

Now let’s add the back end code. First, we’ll add another entry to our SetUI enumeration, and another case to our SetUI case statement. Add the following line to our enumeration:

ChangeUsername

and add the following case statement to our SetUI Case statement:

Case SetUIModes.ChangeUsername
   mpeChangeUsername.Show()

Next, we’ll hook up the ‘Change Username’ LinkButton. It’s functionality is pretty easy. Add the following to the our lnkChangeUsername_Click event handler:

lblCurrentUsername.Text = lblUsernameEdit.Text
SetUI(SetUIModes.ChangeUsername)

We set the current username and then show the dialog.

We don’t need to do anything with the cancel button as it will function correctly without help, however we need to hook up the functionality for the Save button. We’ll want to do a little checking to see if the username is being used before we actually do any changing. We’ll want to display a message that it’s taken if it is. We’ll also want to make sure that the username was successfully changed in the database when we actually execute the SQL. Well add two helper functions, the first to check username availability. Add the following function:

Private Function CheckUsernameIsAvailable( _
          ByVal sUsernameToCheck As String) As Boolean

    If IsNothing(Membership.GetUser(sUsernameToCheck)) = False Then
        Return False
    Else
        Return True
    End If
End Function

Basically, we receive the new username and then check to see if we can retrieve a MembershipUser object from the database. If we can, then the username is NOT available and we return false.

We’ll also want to create a helper function to perform the actual database work, since our membership provider doesn’t do this for us. Add the following helper function (I apologize in advance for dividing of the lines in such a terrible manner…):

Private Function ChangeUsername(ByVal sCurrentUsername As String, _
         ByVal sNewUsername As String, _
         Optional ByVal iOrdinalForConnectionString As Integer = 0) As Boolean 

   Using cn As New SqlConnection( _
      ConfigurationManager.ConnectionStrings( _
        iOrdinalForConnectionString).ConnectionString) 
        
      Dim cmd As New SqlCommand _
             ("aspnet_Membership_ChangeUsername", cn) 
      cmd.CommandType = Data.CommandType.StoredProcedure 
      cmd.Parameters.Add _
        ("@currentUsername", Data.SqlDbType.NVarChar).Value _
         = sCurrentUsername 
      cmd.Parameters.Add _
        ("@newUsername", Data.SqlDbType.NVarChar).Value _
           = sNewUsername 
      cmd.Parameters.Add("@Results", Data.SqlDbType.Bit).Direction _
          = Data.ParameterDirection.Output  
      cn.Open()
      Try
         cn.Open() 
         cmd.ExecuteNonQuery() 
      Catch ex As Exception 
         Return False 
      Finally 
         cn.Close() 
      End Try 
      Return cmd.Parameters("@Results").Value 
   End Using
End Function

This function is easily the least flexible in the entire object. Notice we pass in both the old and the new usernames. We also pass in an ordinal for the connectionString to use. Unfortunately we are unable to determine the connectionString currently being used in the membership provider, so we have to pass in the ordinal of the connection string we are using. In my case, I don’t even have a connectionString as I’m using the ASP.Net default sqlexpress database, so an ordinal of 0 works for me. You may need to play with what you pass in depending on your connectionstring setup. We use this connectionString to create a connection. With this connection, we create a command object, load it up with our parameters, including an output parameter. We then attempt to execute the stored procedure. If it fails we return false, otherwise we return the success/failure results from the actual database execution. You’ll notice that we use some SQL Data client objects so we’ll need to import the following namespace (or fully qualify all your SQL objects). Add the following import statement to the top of your document:

Imports System.Data.SqlClient

With our helper functions out of the way, we can now concentrate on our save routine. Add the following to your lnkChangeUsernameSave_Click event handler:

If CheckUsernameIsAvailable(txtNewUsername.Text) = False Then
    divUserAvailablity.InnerText = "Username taken, sorry."
    SetUI(SetUIModes.ChangeUsername)
    Exit Sub
End If
If ChangeUsername(lblCurrentUsername.Text, txtNewUsername.Text) = True Then
    SetDataSource(Membership.GetAllUsers(), USERS_DATA_SOURCE)
    BindGrid(gvManageUsers, USERS_DATA_SOURCE, False)
    lblMessageDialog.Text = "Username successfully changed."
Else
    lblMessageDialog.Text = "Username change failed."
End If
txtNewUsername.Text = String.Empty
divUserAvailablity.InnerText = String.Empty
SetUI(SetUIModes.MessageDialog)

First we check to make sure the username is available. If it isn’t, then we’ll put up a message much like our validation letting the administrator know that it’s taken. We’ll make sure our change username dialog is still up and then exit the sub. If it’s valid, we attempt to perform the actual change. If the change is successful, we’ll refresh our data source, and then update our GridView with the new username information. Whether it fails or is successful, we’ll set the message dialog, and then when all is said and done, show the message. We’ll also reset our new username TextBox and our availability div so that they are both blank and ready to use next time.

Ok, so we’ve got all our functionality working, but what if a user of our control didn’t put the stored procedure into the database? We’ll need to disable the functionality in that case. Actually, what we’ll do is disable the functionality be default, and then add it back in if the control’s user specifically enables it. We’ll add a property to our user control that will control the ‘Change username’ link so that the functionality to call the DB is only available if the control’s user specifically configures it to say that the DB has been setup. Add the following to define our property:

Public Enum ChangeUsernameSettings
    DbIsSetup
    DbIsNotSetup
    DisableLink
End Enum

Private _ChangeUsernameFunctionality As ChangeUsernameSettings _
                = ChangeUsernameSettings.DbIsNotSetup

Public Property ChangeUsernameFunctionality()
    Get
        Return _ChangeUsernameFunctionality
    End Get
    Set(ByVal value)
        _ChangeUsernameFunctionality = value
    End Set
End Property

Here, we create a enumeration for our different settings. Next we create a private variable to hold our property’s current setting, and set it by default to be DbIsNotSetup. Finally we create our property.

Now we’ll need to implement some things so that the settings are used. Add the following line to your SetupEditUser subroutine (anywhere will work):

If _ChangeUsernameFunctionality = ChangeUsernameSettings.DisableLink _
       Then lnkChangeUsername.Enabled = False

If disableLink is selected, then we’ll disable it during our EditUser dialog setup. We could add a tooltip if desired, letting the administrator using the page what needs to be done to add the functionality. Next add the following to your lnkChangeUsername_Click event handler (apologies for the big block of text in the middle that has no link breaks (for readability)):

If _ChangeUsernameFunctionality  =  _ 
              ChangeUsernameSettings.DbIsNotSetup Then
    lblMessageDialog.Text = _
"For this fundtion to be available, the following must be met: <br> 1. The database needs to have a stored procedure created for this functionality and <br> 2. the administrator must explicitly control to specify that the functinality is in the database.<br><br> Please see the documentation for more information."

    SetUI(SetUIModes.MessageDialog)
    Exit Sub
End If

If our page creator doesn’t specifically enable on this functionality, the administrator using our control will receive a message letting them know what needs to happen for the functionality to be enabled. And of course, if we set the property to DbIsSetup, then we’ll have our functionality back.

Epilogue

Ugh… as in ugly. Truly, this isn’t the cleanest portion of our functionality, we are completely disregarding just about every one of our rules about keeping this control self contained and trying to make it useful across the board. But, in our defense, we’re making up for functionality limitations built into the the .Net framework (or is it limitations NOT built into…?). We could of course extend, or rewrite our membership provider, but I choose not to do it that way.

This should fulfill all the requirements we had previously for our membership portion of our management page. Now we just need to work on the roles administration portion. That will be the subject of the next post(s).

Sponsor
Posted: Thursday, December 04, 2008 12:37 AM by Yougotiger

Comments

Sunil Punjabi said:

Excellent code and functionality.
# January 2, 2009 6:58 AM

Yougotiger said:

** Author Comment** - It was pointed out that I somehow missed putting in the front-end code for the divUserAvailablity section. Oops... Please note that I added it and it is now available, highlighted in RED BOLD. - Thanks to user 'daneck' for pointing that out.

# February 2, 2009 2:01 PM

Yougotiger said:

** Author Comment** - Wow, I can't believe that nobody pointed out an obvious oversite in the change username functionality. I neglected to target our application with our stored procedure. The Users table can hold more than one entry with the same username, as long as the applicationId is different. Since we're not using that here, changing one username will change them ALL. To see the solution to this problem, tune in to part 7 (out soon) for the fix.

# March 27, 2009 2:56 AM

Yougotiger said:

** Author Comment** - If you read comments for pt 7, you'll see that I found that I was getting an error in my GetRoleDescription function at the cn.Open line. Since it wasn't in the Try Catch block, when the line caused an error, the error was unhandled. So I moved the cn.Open line into the Try Catch block so that it would be handled.

# April 3, 2009 1:02 AM

Yougotiger said:

** Author Comment ** - While the code for ChangeUsername DOES work, I found that if you try to change a username to one that already exists, that you fail in a bad way. I modified the code of this stored procedure a little to return success or failure from the stored procedure. It doesn't pass details for the failure, but it will return that it failed. The modified stored procedure is listed here:

CREATE PROCEDURE aspnet_Custom_Membership_ChangeUsername

   @currentUsername nvarchar(255),

   @newUsername nvarchar(255),

   @applicationName nvarchar(255),

   @results bit OUTPUT

AS

DECLARE @ApplicationID nvarchar(255)

SELECT @ApplicationID = ApplicationID FROM aspnet_Applications

  WHERE ApplicationName = @ApplicationName  

  IF EXISTS (SELECT * FROM aspnet_Users WHERE UserName = @newUsername)

      SET @results=0

  ELSE

      BEGIN

          UPDATE aspnet_Users

           SET UserName = @newUsername,

             LoweredUserName = LOWER(@newUsername)

           WHERE LOWER(UserName) = LOWER(@currentUsername)

               AND ApplicationID = @ApplicationID

           set @results = @@ROWCOUNT

      END

Basically, it just adds an if then that checks for the new username's prior existence in the DB. If it already exists, it returns a 0 which would equate to false. If it doesn't exist, then it completes the procedure as oulined previously.

# March 22, 2011 3:44 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