Welcome to AspAdvice Sign in | Join | Help

.Net Discoveries

An attempt to pass along some answers I have discovered in my .Net coding.
Filling in the Gaps - Problem Design Solution 3.5 - Part 3

Prologue

In a couple previous posts (here and  here), we looked in depth at the code in my ASP.Net Website Programming: Problem – Design – Solution book attempting to fill in any of the gaps that I found (such as where does the code in the book go – what file – and adding code that is missing from the book). This post continues this series by addressing Chapter 4 regarding membership. While I found this chapter much less discombobulated than chapter 3, there are still a few things that need additional attention.

Problem

In this post I will attempt to address these missing pieces (or at least what I deem to be the missing pieces):

  • Location for the OpenID class.
  • Some missing methods of the OpenID class.
  • Adding data to the userProfile control’s country and state DropDownLists.
  • The missing GetProfile() method of the userProfile control.
  • The missing GetAvatarUrl() method.
  • Login Box – where does it go?
  • Registration Page - “Name Email not declared”.
  • Password Recovery Page
  • Location for the AJAX Login Dialog
  • Code corrections for AJAX Login Dialog
  • Code corrections for persisting the Theme
  • Location for Profile_MigrateAnonymous
  • Location for the AdminMenuItems Class
  • The AdminMenuItem Class (singular)
  • Location for BindNavItems
  • Guts of the ManageUsers page
  • AddEditUsers Page
  • ManageRoles Page
  • AddEditRole Page

Solution

With no further ado, let's get started...

Where Do I Put the OpenID Class

On page 189, in the last code block, there is a reference to OpenID.IsOpenIdRequest(). This references the OpenID class that hasn’t been defined yet (ok, pet peeve, if a custom class is used in a code sample, it’s nice to define the custom class first so I can understand what’s going on in the custom class and that will help me understand better what’s going on in the code… plus then I can do Intellisense and not mess up spellings. – Sorry for the tangent). This OpenID class begins to be defined in on page 191 as part of the ‘Implementing OpenId Authentication’ section started on page 190.

So the question still stands, where do we put this code? In the BLL code project, within a ‘Security’ folder. The file is named OpenId.vb.

The OpenId Missing Methods

Continuing in the chapter, we come to the OpenId’s Login function.

This method calls the GetIdentityServer function that is, unfortunately, undefined in the book. This function can be found in the code download and is defined:

Private Shared ReadOnly REGEX_LINK As New Regex _
  ("<link[^>]*/?>", RegexOptions.IgnoreCase Or RegexOptions.Compiled)
Private Shared ReadOnly REGEX_HREF As New Regex _
  ("href\s*=\s*(?:""(?<1>[^""]*)""|(?<1>\S+))", _
           RegexOptions.IgnoreCase Or RegexOptions.Compiled)

Public Shared Function GetIdentityServer(ByVal identity As String) _
                                                                               As StringDictionary
    Using client As New WebClient()
        Dim html As String = client.DownloadString(identity)
        Dim col As New StringDictionary() 

        For Each regMatch As Match In REGEX_LINK.Matches(html)
            AssignValue(regMatch, col, "openid.server")
            AssignValue(regMatch, col, "openid.delegate")
        Next 

        Return col
    End Using
End Function

You’ll also notice that we need to define a Regex variable outside our function as well, (actually there are two, one we won’t use till later). The GetIndentityServer function also calls another subroutine that is not defined in the book, AssignValue:

Private Shared Sub AssignValue(ByVal linkMatch As Match, _
               ByVal col As StringDictionary, ByVal name As String)

    If linkMatch.Value.IndexOf(name) > 0 Then
        Dim hrefMatch As Match = REGEX_HREF.Match(linkMatch.Value)
        If hrefMatch.Success Then
            If Not col.ContainsKey(name) Then
                col.Add(name, hrefMatch.Groups(1).Value)
            End If
        End If
    End If
End Sub

Another Method the Login function calls is the CreateRedirectUrl method. Again, pulling from the code in the download, we can create this method as follows:

Private Shared Function CreateRedirectUrl(_
           ByVal requiredParameters As String, _
           ByVal optionalParameters As String, _
           ByVal delgate As String, _
           ByVal identity As String) As String

    Dim sb As New StringBuilder()
    sb.Append("?openid.ns=" + HttpUtility.UrlEncode _
            ("http://specs.openid.net/auth/2.0"))
    sb.Append("&openid.mode=checkid_setup")
    sb.Append("&openid.identity=" + HttpUtility.UrlEncode(delgate))
    sb.Append("&openid.claimed_id=" + HttpUtility.UrlEncode(identity))
    sb.Append("&openid.return_to=" + HttpUtility.UrlEncode _
           (HttpContext.Current.Request.Url.ToString())) 

    If Not String.IsNullOrEmpty(requiredParameters) OrElse _
               Not String.IsNullOrEmpty(optionalParameters) Then
        sb.Append("&openid.ns.sreg=" + HttpUtility.UrlEncode _
                ("http://openid.net/extensions/sreg/1.1"))
        If Not String.IsNullOrEmpty(requiredParameters) Then
            sb.Append("&openid.sreg.required=" + HttpUtility.UrlEncode _
                  (requiredParameters))
        End If
        If Not String.IsNullOrEmpty(optionalParameters) Then
            sb.Append("&openid.sreg.optional=" & HttpUtility.UrlEncode _
                  (optionalParameters))
        End If
    End If
    Return sb.ToString()
End Function

Finally, we have a number of places in the book (including our Login function) that use a datatype of OpenIdData. This class is also missing, but defined in the download code. To create this class, add the following definition below the OpenId class’ end class statement:

Public Class OpenIdData
    Public Sub New(ByVal identity__1 As String)
        Identity = identity__1
    End Sub
    Public IsSuccess As Boolean
    Public Identity As String
    Public Parameters As New NameValueCollection()
End Class

Adding Data to userProfile Control’s Country and State DropDownLists

On page 196, we find the code that we add a TextBox for State and a DropDownList for Country to our userProfile Control. But the interesting thing is that in the very same section, we define custom controls that inherit from the DropDownList control and will do all the populating for our controls. Why we go through the hassle of creating them if we don’t use them? I don’t know either..

The Missing GetProfile() Method of the userProfile Control

On page 201, we start loading the profile, this uses the GetProfile() function that isn’t defined in the book. This I also pulled from the code download:

Private Function GetProfile() As ProfileBase
    Dim profile As ProfileBase = Helpers.GetUserProfile(_userName)
    If IsNothing(profile) Then
        profile = ProfileBase.Create(_userName, False)
    End If
    Return profile
End Function

You’ll notice that this function calls the Helpers.GetUserProfile function that also isn’t declared in the book. Again from the code download, we have the following overloaded functions:

Public Shared Function GetUserProfile() As ProfileBase
    Return ProfileBase.Create(CurrentUserName, _
            CurrentUser.Identity.IsAuthenticated)
End Function

Public Shared Function GetUserProfile(ByVal sUserName As String) _
                                                                                  As ProfileBase
    Return ProfileBase.Create(sUserName, _
               CurrentUser.Identity.IsAuthenticated)
End Function

Public Shared Function GetUserProfile(ByVal sUsername As String, _
                                   ByVal isAuthenticated As Boolean) As ProfileBase
    Return ProfileBase.Create(sUsername, isAuthenticated)
End Function

You’ll also notice towards the end of our Page_Load subroutine that our code will enter information for our Signature and our AvatarUrl. This isn’t added to the profile until a later chapter so doing it now will not work, but if you want to add the setting now, you can look it up on page 407, the chapter on forums. We run into this again when we attempt to save the signature back to the profile in the SaveProfile routine as well.

The Missing GetAvatarUrl() Method

I struggled a little trying to decide whether to put this here or into the Forum chapter, since it technically isn’t USED here. But I decided that since it was referenced here, I’d add it and it will be all setup for the forums chapter later. I did look in the forum chapter to see if it was defined there and I couldn’t find it, I had to go to the code download. So here is the GetAvatarUrl routine. This also contains the GetGravatarHash routine that is called by our GetAvatarUrl method (these both go into the userProfile control by the way):

Private Function GetAvatarUrl() As String
    If String.IsNullOrEmpty(Profile.Forum.AvatarUrl) Then
        Dim mu As MembershipUser = Membership.GetUser(_userName)
        If Not IsNothing(mu) AndAlso Not String.IsNullOrEmpty(mu.Email) Then
            Return String.Format _
                ("http://www.gravatar.com/avatar/{0}.jpg?d=wavatar&s=32", _
                GetGravatarHash(mu.Email))
        Else
            Return String.Format("{0}/images/user.gif", Helpers.webroot())
        End If
    Else
        Return Profile.Forum.AvatarUrl
    End If
End Function

Public Function GetGravatarHash(ByVal sEmail As String) As String
    Dim md5Hasher As MD5 = MD5.Create()
    Dim data As Byte() = md5Hasher.ComputeHash _
                     (Encoding.Default.GetBytes(sEmail))
    Dim sBuilder As New StringBuilder()
    Dim i As Integer
    For i = 0 To data.Length - 1
        sBuilder.Append(data(i).ToString("x2"))
    Next
    Return sBuilder.ToString()
End Function

Registration Page - “Name Email not declared”

On page 207, 2nd paragraph, we learn that the “code-behind is impressively short” (and it is), however it is NOT true that we only have to add code to handle the FinishButtonClick event. We also need to add some properties or the code we created for the textbox on page 205 for the email textbox won’t work properly. In the book’s code, we add our text attribute, and add a value of ‘<%# Email %>’ . Email, however, is undefined. We must add the following (from the code download) to our code behind for the page to display correctly:

Public ReadOnly Property Email() As String
    Get
        Return Me.Request.QueryString("Email")
    End Get
End Property

There are a number of other properties that are listed in the code download, however since I don’t know where they are used, I opted not to include them. I will add them later if required.

Password Recovery Page

On page 208, there is a slight error in the code, the page declaration sets the masterpage to “Template.master”, this according to the code download and other similiar pages should be the “CRMaster.master” page. There are a number of small code errors in this passage as well, like “<p></p>if you forgot your email…<p></p>” rather than “<p>if you forgot your email…</p>”. I’d submit it for errata, but I’ve had poor luck getting errors listed in the errata. All in all, this page seemed to be pretty well in order.

Location for the AJAX Login Dialog Stuff

On page 214, we’re introduced to an AJAX Login Dialog. While it mentions that this should be added to the bottom of the page. The page for which to add-this-to-the-bottom isn’t specified, however the code download shows this to be the TBHMain.Master page. The code should go between the end of the footer copyright Div section and the end </form> tag.

There does seem to be one typo that hasn’t been corrected, that is in the TextBox for the OpenID, there is an additional attribute that doesn’t belong there: ‘Type’. This doesn’t appear in the code download and should be be removed.

Also, on page 216, we’re introduced to some javascript code, which again is unclear where it goes. This is placed in an external .js file named TBH.js located in the scripts folder in our website project (although further code on the next page is referred to this .js file as though we should know it already exists…). Continuing the page, we also find reference to the ‘Client'-side’ functions, which are ALSO in the TBH.js file.

So, to sum up, all the javascript on pages 216-220 goes in this TBH.js file.

Code Corrections for the AJAX Login Dialog

While we’re at it, we might as well correct code in this section.

In the profile section, we’re missing a little code from the code download.

In the LoadProfile function, the code download also adds an array and populates it with the names of our properties, the following is before our call to Sys.Services…:

propertyNames[0] = "FirstName";
propertyNames[1] = "LastName";

In the OnLoadCompleted function, the last line before the closing bracket should call our ShowProfileInfo() function:

ShowProfileInfo();

Also, there is a type-o in the code for ShowProfileInfo(). In the nested If statement, there is a reference to ProfileEdit.aspx in one of our link tags, it should be EditProfile.aspx.

You may also notice that in our LoadProfile function, we pass a reference to OnProfileFailed, which isn’t in the book. The code download defines it as follows:

function OnProfileFailed(error_object, userContext, methodname) {
    alert("Profile service failed with message: " +
                error_object.get_message());
}

In the onLoadRolesCompleted function, the code download modifies the book’s code to a single line:

isAdministrator();

This runs a function that isn’t defined in the book, that checks the user’s roles to see if they are an administrator and then show’s hides certain functionality. The isAdministrator() function also calls ShowElement and HideElement. They should be defined as follows:

function isAdministrator() {
    if (Sys.Services.RoleService.isUserInRole('Administrators')) {
        ShowElement('liAdmin');
    } else {
        HideElement('liAdmin');
    }
}

function ShowElement(ElementName) {
    if (null != $get(ElementName))
        $get(ElementName).style.display = 'block';
}

function HideElement(ElementName) {
    if (null != $get(ElementName))
        $get(ElementName).style.display = 'none';
}

Looking through the code download, I’d also add the following functionality that seems(?) relevant, but isn’t in the book:

function ValidateLoginValues() {
    if (event.which || event.keyCode){
        if ((event.which == 13) || (event.keyCode == 13)) {
            Username = $get('txtUsername');
            Password = $get('pwdPassword');
            if (Username.value.length > 0 && Password.value.length > 0) {
                ProcessEnter('btnLogin');
            }
            else {
                alert('You must enter both a username and a password to authenticate.');
            }
        }
    }
}

This function seems to disable the login button until the user has entered at least SOME text into both the username and password fields. It isn’t in the book, but I added it here.

Also, whether or not it all works, I don’t know, I’m not planning on testing the AJAX login stuff.

Code Corrections for Persisting the Theme

The code we previously created for the theme application should change a little here to use the profile’s theme setting. The code in the book passes a profile variable into Helpers.SetProfileTheme() however this is undefined. We can retrieve this using a function in the Helpers class GetUserProfile. Change the Helpers.SetProfileTheme as so it reads:

Helpers.SetProfileTheme(Helpers.GetUserProfile(), Me.Theme)

We pull the profile and then pass it into the class’ method. We also need to modify the line that retrieves the theme. Change your Helpers.GetProfileThem so it reads:

Me.Theme = Helpers.GetProfileTheme(Helpers.GetUserProfile())

Location for Profile_MigrateAnonymous

On page 221, we add handling for Profile’s MigrateAnonymous event. The book is unclear that this should go into our Global.asax file (oh, and you may have to create it. It goes in the root of you web directory).

Location for the AdminMenuItems Class

On page 222, we’re introduced to the AdminMenuItems class, that we’ll be using as we create the admin menu. This should be placed in the Navigation folder in the BLL portion of the project.

The AdminMenuItem Class (singular)

Also on page 222, it mentions that we’ll call the AdminMenuItems class (plural) and that will give us an arraylist of AdminMenuItem (singular). This (the singular) isn’t defined in the book, so we’ll define it here. This is placed in the AdminMenuItems.vb class file after the AdminMenuItems class ends. It’s actually pretty simple, it is defined as follows:

Public Class AdminMenuItem
    Public Sub New(ByVal vName As String, ByVal vUrl As String)
        MenuName = vName
        Url = vUrl
    End Sub 

    Public Sub New(ByVal vName As String, ByVal vImageUrl As String, _
                               ByVal vUrl As String)
        MenuName = vName
        Url = vUrl
        ImageUrl = vImageUrl
    End Sub 

    Private _name As String
    Public Property MenuName() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property
    Private _imageUrl As String
    Public Property ImageUrl() As String
        Get
            Return _imageUrl
        End Get
        Set(ByVal value As String)
            _imageUrl = value
        End Set
    End Property
    Private _url As String
    Public Property Url() As String
        Get
            Return _url
        End Get
        Set(ByVal value As String)
            _url = value
        End Set
    End Property
End Class

Location for BindNavItems

This should be added to the code behind for the Admin.master page.

Guts of the ManageUsers page

I didn’t want to recreate the wheel, so I simply used the ‘Better ASP.Net Member/Role Management’ control that I build (see part 9 here for details) instead. I did use the stuff about total users registered, and users online now, and then added my control under that. I did modify the binding code some, in the book it calls BindAlphabet(), I changed my to BindTotals() and all it does is enter values into the lblTotalUsers and lblOnlineUsers controls. I removed all the alphabet stuff.

Interestingly enough, the book has two code listings for BindUsers(). I believe that the second one is more accurate, as the first calls the BindAlphabet() method which I don’t believe it should.

AddEditUsers page

Looking at the code for the AddEditUser page as well, I believe the ‘Better ASP.Net Member/Role Management’ control can handle all this as well. I DO miss a little of the statistical information such as when the user last logged in and last activity etc. AND I do miss the section that allows the administrator to edit the user’s profile. Also, I don’t provide a means of unlocking the user’s account if it is locked. Maybe I’ll look into a 10th part that adds a way to handle these missing pieces?

I did find on page 239, there seems to be an error in the text that give us the list<string> definition in C# rather than in VB (and the entire explanation is a little hard to follow, since by this point you ought to be able to do list(of String) variables). Again, I’d submit to errata, but it doesn’t seem to be very helpful.

ManageRoles Page

Again, the “Better ASP.Net Member/Role Management” control can handle all the role management I need, so I chose to ditch this page as well.

AddEditRole Page

Again, this functionality is addressed in the control, however I thought to comment on one piece of code the last one on page 245. Specifically, I might have done it a little differently. Instead of using a string collection (Usernames) and a string array (saUsers) I would have used a list(of String) generic collection, and then iterated through the MembershipCollection returned form Membership.FindUsersByName() and added the usernames to the list via the list.add method. Once the list is complete, I would have returned list.ToArray() – That’s what I’m doing in another project I’m working on.

Also, the book doesn’t delineate where this function should go. In the code download, you can see find it in the App_Code folder in a file called MemberService.vb.

Epilogue

I did make a list as I went through some of the last bits of this site, and found some functionality that I will add to my user/role management control. I did find however that there was a lot of pieces in this chapter that created in the book, but never seem to be quite tied in (i.e. ajax login and openid login).

I did like the suggestion to use a UserProfile control, and have incorporated the idea into a project I am currently working on, and even referenced the code in the book for ideas on how to code it.

I hope that some of what’s here can make things more clear. I apologize for being so long between posts on this topic, and likely it’ll be long again. I have read some in chapter 5 on newsletters etc and have a list of things to start posting about, but that doesn’t mean I’ll get to it any faster, but I’ll do what I can.

Sponsor
Posted: Monday, March 14, 2011 12:53 PM by Yougotiger
Filed under:

Comments

Kellsie said:

Super informative wriitng; keep it up.
# July 14, 2011 2:50 AM

Yougotiger said:

Glad to be of help @Kellsie, comments like yours really make it worth it.

# July 14, 2011 9:54 AM
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