.NET Remoting and Event Handling in VB .NET, Part 2

Part 1 of the three-part .NET Remoting and Event Handling in VB .NET series introduced a Singleton remote server with published events as a text-chat server. The basic idea is that all clients connect to and register with the single server. When a client sends a message, the server broadcasts the message to all of the connected clients. Part 2 continues the tutorial with an implementation of the client. It completes the remotable, shared client code, as well as the client application. (Part 1 was published in December 2004.)

Implement the Remotable Part of the Client

When a server needs to talk back to a client, a role reversal happens: The server sort of assumes the role of client and the client assumes the role of server. In terms of implementation, this means both client and server must have a remotable piece. Unlike one-way remoting (or Web services), the remote server services the requests the client makes, but it is not connected to the client.

Consider delegates for a moment. A delegate is really just a list of function pointers. A function pointer is just the address of a method. When an event is raised, what really happens is that a method or methods are called. The event spawns an indirect method call. For remote servers to call client methods, the remote server needs to know about a function that lives on a client. In practice, remoting marshals the pointers to client methods through proxy objects just as method calls to remote servers are.

The .NET Framework handles all of the plumbing for you, whether you are implementing a remotable client or server. All you need to do is define your clients so that they inherit from MarshalByRefObject. Listing 1 shows you what the header might look like for a remotable client:

Listing 1: Remotable Clients Must Inherit from MarshalByRefObject Too

Imports System.Runtime.Remoting
Imports System.Security.Principal
Imports System.Runtime.Remoting.Lifetime
Imports Softconcepts.ApplicationBlocks.RadioPattern

Public Class Client
    Inherits MarshalByRefObject
//...

Both client and server have to see the definition of this object for both ends of a two-way remoting solution to work. For this reason, the assembly containing the Client class above has to be deployed on both client PCs and the server machine. (There are other ways to share definitions. For example, you can define an interface and deploy it to the server, and then implement the Client class as a realization of the interface—in other words, inherit from the interface.) To satisfy client and server visibility, I placed the Client class in the SharedCode.dll shared assembly and referenced that assembly from both client and server applications.

Configure the client

Like the server, the client needs configuration. Essentially, you need to tell the client how to connect to the server. As previously mentioned, you can configure the clients and servers programmatically or in the App.config file. This example uses the configuration file.

The most important part of the configuration is to specify the <wellknown> tag, which includes the object you want to create, and the URL of the remote server:

Listing 2: The Client's App.config File

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.runtime.remoting>
      <application>
         <channels>
            <channel ref="http" port="0">
        <clientProviders>
          <formatter ref="binary" />
        </clientProviders>
        <serverProviders>
          <formatter ref="binary" typeFilterLevel="Full" />
        </serverProviders>
      </channel>
         </channels>
         <client>
            <wellknown
               type="SharedCode.Chatter, SharedCode"
               url="http://localhost:6007/Chatter.soap"
            />
         </client>
      </application>
   </system.runtime.remoting>

   <appSettings>
    <add key="user" value="Your Name Here!" />
    <add key="echo" value="true" />
   </appSettings>
</configuration>

All of the elements are required, but the <wellknown> tag describes where the server is, which port to talk to it on, the name of the assembly, and the namespace that contains the remotable objects. The type attribute contains the namespace followed by the assembly name, SharedCode. The configuration employs .NET and its reflection capabilities to dynamically load the assembly. The URL attribute contains the host, port, and URI (chatter.soap) of the remote server.

The rest of the App.config file contains channel formatter information. Microsoft added quite a bit of these other elements in .NET 1.1. You can use the rest of the code as is in many instances and use the .NET help information to fill in any details.

Implement the send and receive behaviors

The send behavior is a call to the remote server to send out messages to all of the connected clients. The receive behavior is the event handler that receives messages from the server. These methods are straightforward, and while they depend on the .NET remoting plumbing, they are easy to implement:

Listing 3: Sending and Receiving Messages in the Chat Sample

    Public Shared Sub Send(ByVal sender As String, _
                           ByVal message As String)
        Try
            Instance.FChatter.Send(sender, message)
        Catch
            Console.WriteLine("Not connected")
        End Try
    End Sub

    Public Sub OnMessageEvent(ByVal Sender As Object, _
        ByVal e As ChatEventArgs)

        If (Not IsSelf(e.Sender)) Then
            Broadcaster.Broadcast(Environment.NewLine)
            Broadcaster.Broadcast("{0} said: {1}", e.Sender, e.Message)
            Broadcaster.Broadcast("chat>")
        End If
    End Sub

Send attempts to invoke the Chatter.Send method. If it fails, you write the output to the Console. In a real-world application, you may handle the exception some other way.

The OnMessageEvent method matches the signature of the delegate defined by the remote server. When it is called—the server has raised the event—you broadcast the message. You could just handle the message here, but this example uses the Observer pattern to loosen the coupling between the Client class and any presentation layer you might elect to place on top of it. (Part 3 will examine the implementation of the Broadcaster in greater detail.)

Ultimately, just make a mental note that Send pushes the message out to the remote server and OnMessageEvent plays the role of receiver.

Connect to the remote server

Listing 4 shows the complete Client class's implementation.

Listing 4: The Shared Client Class Implementation

Imports System.Runtime.Remoting
Imports System.Security.Principal
Imports System.Runtime.Remoting.Lifetime
Imports Softconcepts.ApplicationBlocks.RadioPattern

Public Class Client
    Inherits MarshalByRefObject
    Implements IDisposable

    Private FChatter As Chatter
    Private Shared FClient As Client = Nothing

    Private Sub New()
        RemotingConfiguration.Configure("client.exe.config")
        FChatter = New Chatter
        AddHandler FChatter.MessageEvent, AddressOf OnMessageEvent
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        RemoveHandler FChatter.MessageEvent, AddressOf OnMessageEvent
    End Sub

    Public Shared Sub Shutdown()
        Try
            FClient.Dispose()
            GC.SuppressFinalize(FClient)
        Catch
        End Try
    End Sub

    Private Shared ReadOnly Property Instance() As Client
        Get
            If (FClient Is Nothing) Then
                FClient = New Client
            End If
            Return FClient
        End Get
    End Property

    Public Shared Sub Send(ByVal sender As String, _
                           ByVal message As String)
        Try
            Instance.FChatter.Send(sender, message)
        Catch
            Console.WriteLine("Not conencted")
        End Try
    End Sub

    Public Shared Sub ShowHistory()
        Try
            Instance.FChatter.ShowHistory()
        Catch
            Console.WriteLine("Not connected")
        End Try
    End Sub

    Public Overrides Function InitializeLifetimeService() As Object
        Return Nothing
    End Function

    Public Sub OnMessageEvent(ByVal Sender As Object, _
        ByVal e As ChatEventArgs)

        If (Not IsSelf(e.Sender)) Then
            Broadcaster.Broadcast(Environment.NewLine)
            Broadcaster.Broadcast("{0} said: {1}", e.Sender, e.Message)
            Broadcaster.Broadcast("chat>")
        End If
    End Sub

    Private Function IsSelf(ByVal sender As String) As Boolean
#If DEBUG Then
        Return False
#Else
        Return sender = WindowsIdentity.GetCurrent().Name
#End If

    End Function

End Class

.NET Remoting and Event Handling in VB .NET, Part 2

It is a bit more advanced than a basic sample because it uses some patterns (Command, Factory, Singleton, and Observer) that you might use in a real application, as opposed to just the bare minimum code you would need to connect to the remote server.

The Client class' Sub New configures the client application, creates an instance of the remote server, and connects the Client.OnMessageEvent to the remote server object, Chatter. The code never creates an instance of the Client object directly. (Note that the constructor Sub New is private.) Instead, it uses the Singleton pattern and the Client object indirectly through the read only property, Instance. Finally, the Send and OnMessageEvent event handlers handle the connection to and from the server (as discussed in the previous section.)

Implement the Presentation Code

The objective here doesn't involve Windows Forms or Web Forms; it is about .NET remoting and server events. Consequently, you can use any kind of GUI to test your solution, and that's what the example does—with a twist.

By separating your pieces and using good patterns, you can loosen the relationship between presentation, middleware, and server. You will see this in the implementation of the Console application.

Because the console application hardly mentions—and only indirectly uses—the .NET remoting solution, the client could be practically anything (see Listing 5).

Listing 5: The Presentation Layer Is a Console Application, but Good OOP Means It Could Have Been Anything

Imports SharedCode
Imports Softconcepts.ApplicationBlocks.RadioPattern

Class ClientApp
    Implements IListener

    Public Shared Sub Main()
        With New ClientApp
            .Run()
        End With
    End Sub

    Public Sub Run()
        Broadcaster.Add(Me)

        ProcessCommand("Startup")

        While (True)
            Console.Write("chat>")
            If (ProcessCommand(Console.ReadLine()) = False) _
               Then Exit While
        End While

        ProcessCommand("Shutdown")

        Broadcaster.Remove(Me)
    End Sub

    Public Shared Function ProcessCommand(ByVal input As String) _
           As Boolean
        Return CommandFactory.Create(input).Execute(input)
    End Function

    Public Overloads Sub Listen(ByVal message As String) _
           Implements IListener.Listen
        Console.WriteLine(message)
    End Sub

    Public ReadOnly Property Listening() As Boolean _
           Implements IListener.Listening
        Get
            Return True
        End Get
    End Property

    Public Overloads Sub Listen(ByVal message As String, _
      ByVal formatter As Softconcepts.ApplicationBlocks.RadioPattern.IFormatter) _
      Implements Softconcepts.ApplicationBlocks.RadioPattern.IListener.Listen
        If (message.Equals("chat>")) Then
            Console.Write(message)
        Else
            Console.WriteLine(formatter.ApplyFormatting(message))
        End If
    End Sub
End Class

The Sub Main is the entry point for the presentation layer. It is simply an instance of the contain class and a call to a single method, Run. This is pretty good encapsulation.

Except for the Imports statement, the listing makes no reference to the middle layer—Client class—or any of the remoting plumbing. This means you could strip off this presentation layer and easily put a WinForms GUI or something else on top of it.

The basic behavior is that the run method loops while there are commands to process, period. Key concepts that make this possible are grounded in patterns. Part 3 will explore all of the patterns used for this solution.

What Have You Learned?

Part 2 demonstrated how to implement the client's App.config file, so that the client could talk to the server. It also discussed why clients that handle server events have to be remotable. Because the .NET plumbing handles this for you, you really only needed to make your remotable client object inherit from MarhsalByRefObject and share that code between client and server.

All of the extra code really demonstrates the use of some very powerful design patterns. While you wait for Part 3 of this three-part installment, see if you can spot the Command, Factory, Singleton, and Observer patterns used.

Part 3 will describe and explain the implementation of the patterns, introduce internationalization in the context of this sample, and point out where code that is too OOPY can cause goofy behavior. (For fun, write me if you find the goofy behavior.)

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his book Visual Basic .NET Power Coding from Addison-Wesley and his upcoming book UML DeMystified from McGraw-Hill/Osborne (Spring 2005). Paul is also the founder and chief architect for Software Conceptions, Inc, founded 1990. He is available to help design and build software worldwide. You may contact him for consulting opportunities or technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2005 by Paul Kimmel. All Rights Reserved.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds