JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

The Talk .NET Peers

The final step is to modify the Talk .NET peer application to use the discovery service instead of the well-known Remoting server. Thanks to the well-encapsulated design of the Talk .NET client, you won't need to modify the main form code. Instead, almost all of the changes are confined to the remotable ClientProcess class.

The ClientProcess class is used to send and receive messages with .NET Remoting. In the revised version, it will also have the additional responsibility of interacting with the discovery web service. To support this design, we need to add two member variables, as shown here:

Public Class ClientProcess
    Inherits MarshalByRefObject
    Implements ITalkClient

    ' Holds a reference to the web-server proxy.
    Private DiscoveryService As New localhost.DiscoveryService()

    ' Tracks the GUID for the current session.
    Private SessionID As Guid

    ' (Other code omitted.)

End Class

The ClientProcess constructor accepts a Boolean parameter that indicates whether a new record needs to be created for this user. If the user hasn't registered before, the ClientProcess class calls the RegisterNewUser() web method.

Public Sub New(ByVal userEmailAddress As String, ByVal createUser As Boolean)

    Me.[Alias] = userEmailAddress
    If createUser Then
        DiscoveryService.RegisterNewUser(userEmailAddress)
    End If

End Sub

The Login() method registers ClientProcess to receive messages from other peers. It also retrieves the ObjRef for the current instance using the Remoting Services.Marshal() method, and submits it to the sever.


Public Sub Login()

    ' Configure the client channel for sending messages and receiving
    ' the server callback.
    RemotingConfiguration.Configure("TalkClient.exe.config")

    ' Retrieve the ObjRef for this class.
    Dim Obj As ObjRef = RemotingServices.Marshal(Me)

    ' Serialize the ObjRef to a memory stream.
    Dim ObjStream As New MemoryStream()
    Dim f As New BinaryFormatter()

    f.Serialize(ObjStream, Obj)

    ' Start a new session and record the session GUID.
    Me.SessionID = DiscoveryService.StartSession(ObjStream.ToArray())

End Sub

The GetUsers() method now calls the discovery web service to retrieve the list of peer e-mail addresses:

Public Function GetUsers() As ICollection
    Return DiscoveryService.GetPeers(Me.SessionID)
End Function

The SendMessage() method calls the discovery service to retrieve the appropriate ObjRef, deserializes it, converts it to a proxy, and then invokes the ITalkClient.ReceiveMessage() method.

Public Sub SendMessage(ByVal emailAddress As String, ByVal messageBody As String)

    ' Retrieve the peer information.
    Dim PeerInfo As localhost.PeerInfo
    PeerInfo = DiscoveryService.GetPeerInfo(emailAddress)

    ' Deserialize the proxy.
    Dim ObjStream As New MemoryStream(PeerInfo.ObjRef)
    Dim f As New BinaryFormatter()
    Dim Obj As Object = f.Deserialize(ObjStream)
    Dim Peer As ITalkClient = CType(Obj, ITalkClient)
    ' Send the message to this peer.
    Try
        Peer.ReceiveMessage(messageBody, Me.Alias)
    Catch
        ' Ignore connectivity errors.
        ' Alternatively, you could raise an event or throw an error that the main
        ' form could respond to and use to update the form display.
    End Try

End Sub

The LogOut() method ends the session:

Public Sub LogOut()
    DiscoveryService.EndSession(Me.SessionID)
End Sub

Finally, the Login window is modified to include a check box that the user can select to create the account for the first time, as shown in Figure 10-2.

Click To expand
Figure 10-2: Logging in with a new or existing account

The startup code can retrieve the user's check box selection from the readonly CreateNew property:

Public ReadOnly Property CreateNew() As Boolean
    Get
        Return chkCreateNew.Checked
    End Get
End Property

This information is passed to the ClientProcess constructor, which then determines whether or not it needs to call the RegisterNewUser() web method.

Dim Client As New ClientProcess(frmLogin.UserName, frmLogin.CreateNew)

The new Talk .NET client is now fully functional. The next two sections describe some enhancements you can implement.

Adding Caching

Currently, the Talk .NET client contacts the discovery service every time it sends a message. You could improve upon this situation by increasing the amount of information the client keeps locally. For example, the client might keep a cache with peer-connectivity information in it. That way, if one user sends several messages to another, it will only need to contact the server once, when the first message is sent.

To add caching, you must first add a Hashtable collection to the ClientProcess class. This collection will store all the PeerInfo objects for recently contacted clients, indexed by the e-mail address.

' Contains all recently contacted clients.
Private RecentClients As New Hashtable()

Whenever a message is sent, the code will check the RecentClients collection. If it finds the corresponding user, it will use the stored ObjRef. Otherwise, it will retrieve the ObjRef from the server and add it to the hashtable.

Public Sub SendMessage(ByVal emailAddress As String, ByVal messageBody As String)

    Dim PeerInfo As localhost.PeerInfo

    ' Check if the peer-connectivity information is cached.
    If RecentClients.Contains(emailAddress) Then
        PeerInfo = CType(RecentClients(emailAddress), localhost.PeerInfo)
    Else
        PeerInfo = DiscoveryService.GetPeerInfo(emailAddress, Me.SessionID)
        RecentClients.Add(PeerInfo.EmailAddress, PeerInfo)
    End If
    ' Deserialize the proxy.
    Dim ObjStream As New MemoryStream(PeerInfo.ObjRef)
    Dim f As New BinaryFormatter()
    Dim Obj As Object = f.Deserialize(ObjStream)
    Dim Peer As ITalkClient = CType(Obj, ITalkClient)

    ' Send the message to this peer.
    Try
        Peer.ReceiveMessage(messageBody, Me.Alias)
    Catch
        RecentClients.Remove(PeerInfo)
        ' Optionally, you might want to try retrieving new peer information
        ' and resending the message, if you used the connectivity information
        ' in the local cache.
    End Try

End Sub

As implemented, this will retain ObjRef for the life of the application, or until a transmission error occurs. If you anticipate that connectivity information will change frequently, or that the Talk .NET client application will run for an extremely long period of time (for example, several days), you might want to take a few additional measures to help ensure that this information is valid. For example, you could use code in the GetUsers() method to check the currently logged-on users and remove an ObjRef as soon as a peer disappears from the network:

Public Function GetUsers() As ICollection

    Dim Peers() As String
    Peers = DiscoveryService.GetPeers()

    ' Identify any peers in the local cache that aren't online.
    Dim PeerSearch As New ArrayList()
    PeerSearch.AddRange(Peers)
    Dim PeersToDelete As New ArrayList()

    Dim Item As DictionaryItem
    Dim Peer As localhost.PeerInfo
    For Each Item In Me.RecentClients
        Peer = CType(Item.Value, localhost.PeerInfo)
        ' Check if this e-mail address is in the server list.
        If Not PeerSearch.Contains(Peer.EmailAddress) Then
            ' The e-mail address wasn't found. Mark this peer for deletion.
            PeersToDelete.Add(Peer)
        End If
    Next

    ' Remove the peers that weren't found.
    For Each Peer In PeersToDelete
        Me.RecentClients.Remove(Peer.EmailAddress)
    Next

    Return Peers

End Function

This code works in two steps because items cannot be removed from a collection while you're iterating through it, without causing an error.

Adding E-mail Validation

Currently, no validation is performed when a user registers with the server. This is simply intended as a convenience for testing purposes. Ideally, you would not create a new user account until you could confirm that the e-mail address is correct.

To validate an e-mail address, you can borrow a technique from the world of e-commerce. It works like this:

  1. When the user makes a request, save the submitted information into a different table (for example, a NewUserRequests table). Create a new GUID to identify the request.

  2. Next, send an e-mail to the user-supplied e-mail address (you can use the System.Web.Mail.SmtpServer class for this task). Here's the trick: This e-mail can include an HTTP GET link to a web-service method (or ASP.NET web page) that confirms the new user account. This link will submit the request GUID through the query string. For example, the link might take this form: http://www.mysite.com/RegisterUser.asmx?requestGuid=382c74c3-721d-4f34-80e5-57657b6cbc27 (assuming "requestGuid" is the name of the web-method parameter).

  3. When the user receives the message and clicks on the link, the confirmation method will run with the identifying GUID.

  4. The confirmation method will first check that the response has been received within a reasonable amount of time (for example, three days). If so, it can then find the request record with the matching GUID, remove it from the database, and add the user information to the Users table.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor