.NET Remoting and Event Handling in VB .NET

Wednesday Dec 29th 2004 by Paul Kimmel

Using .NET Remoting, learn how to configure a remote server, define remotable objects, and define custom event arguments and delegates. Do all of this while creating a simple chat application!

You can implement very advanced solutions with .NET Remoting. XML Web Services is an example of a general .NET Remoting solution. If you are writing distributed applications you can often use XML Web Services as an easy form of Remoting. That said, let's talk about .NET Remoting.

If you need to interact with objects that reside on the server, if are working with objects that may be too large to serialize, or if you need to handle server events then you will need to use .NET Remoting. In this three part series we will look at the many code elements you will need to use to implement a .NET Remoting solution that includes server events. (A complete code listing will be available for downloading at the end of the series.)

Implementing the Chat Server

A great way to demonstrate a cool technology is to write a fun application. Everyone in my house uses a messenger service, so I wondered how hard it would be to use .NET Remoting to create a simplified chat program. In this three part series you will implement a chat program, starting with the server.

You should note that the .NET framework ships with a chat sample. The code in this three part series was developed independently by me, but you could look at the chat sample from Microsoft for some additional ideas or to compare styles.

For a chat program you need a server and clients that register with that server. When one client sends a message, the other clients need to be notified; this implies server events and shared code. In this article I will focus on the server and the elements of the server. Here is an overview of the topics I will demonstrate in this part:

  • An executable server application
  • Channel configuration using an App.config file (which describes the service and provides port information)
  • A custom delegate that is in a separate assembly to permit sharing between client and server
  • A remotable MarshalByRefObject
  • And, the OneWayAttribute

Defining the Executable Server Code

The server can be simple or complex, a console application or Windows service; you just need something running on the server that can service requests from clients. For this chat program, you will use a console application project. Console applications are easy to create and will let us stay focused on Remoting.

Because you will be configuring the remote server using the App.config file, the code for the remote server is very easy. Listing 1 contains the entire listing for the server, and listing 2 contains the App.config file with the configuration information. I will talk about the configuration file after listing 2.

Listing 1: All the code you need for a running server.

Option Strict On
Option Explicit On 

Imports System
Imports System.Diagnostics
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http

Module Module1

    Sub Main()
        Console.WriteLine("Reading configuration information...")

        Console.WriteLine("press [enter] to quit")
    End Sub

End Module

Listing 2: The App.config server configuration XML.

<?xml version="1.0" encoding="utf—8" ?>
      <channel ref="http" port="6007">
          <provider ref="wsdl" />			
          <formatter ref="soap" typeFilterLevel="Full" />
          <formatter ref="binary" typeFilterLevel="Full" />
          <formatter ref="binary" />
        type="SharedCode.Chatter, SharedCode" 

You can programmatically configure your server or use the App.config file to configure the server. By using an external XML file you can change elements like the port number without recompiling and redistributing the server application.

Tip: You can copy and paste the above XML into the Toolbox—referred to as a code snippet—and drag and drop the XML each time you need to configure a server. Generally, you will only need to update the service section and the port.

The remote configuration information is defined between the <system.runtime.remoting> </system.runtime.remoting> tags—the namespace for remoting information. Nested in the namespace is the channel configuration and service configuration. Let me divide the discussion between these two blocks of XML.

Configuring the Channel

I think of a channel like a pipeline for moving data between clients and servers. Channels are configured inside of the <channels></channels> tag. The outer tag supports configuring multiple channels. You only need one channel for the sample.

To configure the channel you need to supply arguments for the ref and port attributes. Ref describes the name of channel, which can be tcp or http. If you provide a Ref attribute then the type attribute is not needed. The port can be any port value, but you should avoid common ports like 20, 21 (FTP), 23 (Telnet), 25 (SMTP), 110, and 80. You can check online for a list of reserved port numbers, but ports above 1024 are probably safe, and you can use numbers as high as 65520.

The inner <serverProvider> and <clientProviders> tags are required in .NET 1.1. If you forget these elements then you will get an exception when you try to run your remote server. The formatter describes the level of serialization for each of the soap and binary formatters, and the provider tag is used to describe elements that participate in channeling the data between client and server.

The best resource for .NET Remoting I have found is Ingo Rammer's website www.ingorammer.com. For more information about providers and formatters pick up a copy of Ingo's book Advanced .NET Remoting from Apress.

Configuring the Service

The service tags indicate the class and assembly that represent your remote service. Using the App.config file, the service configuration information is described with the <wellknown> tag (see listing 2). The type is a string that contains the namespace and class—SharedCode.Chatter—delimited by a comma and the assembly name. When you see a string like this think Reflection and dynamic assembly loading.

The objectUri attribute—"Chatter.soap"—uses a .soap or .rem by convention and is a uniform resource indicator that uniquely identifiers our service by name. The URI will be part of the URL when you connect to your service from the client.

Finally, you specify the mode attribute. The mode can be SingleCall or Singleton. The mode attribute is a subtle but critical part of the chat server. If you used the SingleCall mode then every client would get a different instance of the remote server object, which means the call to connect your event handler would get one instance of the server and subsequent calls to send messages would go to additional instances of the server. For the example, data will be shared between clients, so you need to use the Singleton mode. As a result it is possible that data from one client can be shared by other clients, which is precisely what is wanted in a chat program—one user writes some text and other users can see it.

Writing the Shared Code

At this point your code will compile but not run. You need to define the Singleton remote service described in the App.config file, the Chatter.

There are a lot of ways to implement remotable objects. If serialization is used then you are basically mirroring what XML Web Services do. If you want a proxy reference to an object that resides on the server then you need a MarshalByRefObject. For the program you are creating, you want the latter. You want the clients to be able to get a handle to your remote service, store and address to your client's event handler, and invoke operations on that service. In general terms your remote service needs to:

  • Permit clients to begin listening for messages
  • Permit clients to send messages
  • And, then send that message to all listening clients

Collectively these elements are supported by a custom class that inherits from EventArgs. This custom EventArgs derivative is used to contain your message. You need a new delegate that accepts the custom event arguments, and you need the Chatter class. The Chatter class is the remotable object. You will put all of these elements in a new Class Library project because these definitions must be shared between client and server.

Defining the Custom Event Arguments

The custom event arguments include the sender and that sender's message. Because this object is sent between client and server it has to be remotable. To make the class remotable you can apply the SerializableAttribute to make the event arguments serializable, marshal—by—value objects, that is remotable. Listing 3 contains the implementation of the custom event arguments.

Listing 3: Our serializable event arguments class, ChatEventArgs.

<alizable()> _
Public Class ChatEventArgs
    Inherits System.EventArgs

    Private FSender As String
    Private FMessage As String

    Public Sub New()
    End Sub

    Public Sub New(ByVal sender As String, _
      ByVal message As String)
        FSender = sender
        FMessage = message
    End Sub

    Public ReadOnly Property Sender() As String
            Return FSender
        End Get
    End Property

    Public ReadOnly Property Message() As String
            Return FMessage
        End Get
    End Property
End Class

Next a delegate that accepts your new event arguments type needs to be defined.

Declaring the Delegate

.NET uses multicast delegates to manage events. A multicast delegate is really a list that can contain function pointers with a specific signature. What you are doing when you define a delegate is implicitly defining a class that can contain a list of function pointers that have the same number, order, and type of arguments, that is the addresses of event handlers. The delegate I defined for our new event arguments class is show here:

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

Implementing the Chatter Class

The Chatter is remotable. The basic behavior is that clients assign their message event handler to an instance of the Chatter's public event property. Then, each client can call a Send method and the server notifies clients of a message via their event handlers. I implemented Send to store a copy of the message and then raise the MessageEvent to send the message to all of the registered clients.

There are some interesting features in the Chatter class. I will go over those elements after the code in listing 4.

Listing 4: Our remotable Chatter class which tracks messages send and recipients using a delegate.

Imports System.Collections
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Lifetime

Public Class Chatter
    Inherits MarshalByRefObject

    Private history As Queue = New Queue

    Public Event MessageEvent As MessageEventHandler

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

    Public Sub New()

    End Sub

    <OneWay()> _
    Public Sub Send(ByVal sender As String, _
    ByVal message As String)

        Console.WriteLine(New String("—"c, 80))
        Console.WriteLine("{0} said: {1}", sender, message)
        Console.WriteLine(New String("—"c, 80))

        history.Enqueue(String.Format("At {0} {1} said: {2}", _
          DateTime.Now, sender, message))
        DoMessageEvent(sender, message)
    End Sub

    Private Sub DoMessageEvent(ByVal sender As String, _
        ByVal message As String)

        RaiseEvent MessageEvent(Me, _
            New ChatEventArgs(sender, message))
    End Sub

    <OneWay()> _
    Public Sub ShowHistory()
        Dim O As Object
        For Each O In history
            DoMessageEvent("server—history", O.ToString())
    End Sub
End Class

If you want clients to have a reference to an object that lives on the server, as opposed to a deserialized copy of the object, then you need to inherit from MarshalByRefObject.

The Queue is not relevant to this discussion other than it demonstrates an interesting way to keep a running log of all the messages received.

Next, declare a public event. When a client attaches an event handler to this event the server can talk back to the client by raising the event. Remember events are multicast—which means there might be many handlers for one event in .NET—so you can have an unlimited number of clients receiving this event.

By overriding the InitializeLifetimeService method inherited from MarshalByRefObject you can extended the life of the remotable object. By returning Nothing from InitializeLifetimeService the remotable object hangs around indefinitely.

The last step is to provide behaviors to consumers. In this example, two are offered: Send and ShowHistory. Both methods are implemented the same way, using the OneWayAttribute to describe how this method behaves. The OneWayAttribute means that no data is returned and only input parameters are passed to the method. In practice the OneWayAttribute means you are defining a subroutine with only ByVal parameters.

When ShowHistory is called the queue containing all of the previous messages is dumped. When Send is called, the MessageEvent is simply raised. All that is left to do is create a single client application that listens for MessageEvents. That piece will be tackled in the next part of this series of articles.


.NET Remoting requires a lot of knowledge about a lot of little pieces of the .NET framework. In this first part of a three, you learned how to configure a remote server, define remotable objects, and define custom event arguments and delegates.

In the next part, you will implement a client for your server and complete the chat application. The client is the other half of a distributed solution, so it has some special configuration needs too. In addition, because the server has to connect to the client to invoke the client's event handler, the client has to be remotable too and consequently has some special requirements. More on this later.

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 © 2004. All Rights Reserved.

Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved