Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Remoting with GUIs

0.00/5 (No votes)
6 Jun 2005 1  
This article illustrates how .NET remoting is used in a GUI environment. It illustrates how a server can reflect what is happening to a remote object that it controls. The use of synchronous and asynchronous calls is also examined along with code design improvements.

Client screen shot

Server screen shot

Introduction

NOTE: This article uses Remoting, a legacy .NET technology that has been replaced by Windows Communication Foundation (WCF).

Most introductory articles about remoting focus on console-based applications, but frequently beginners ask, "How can I do remoting with a GUI?". I was asking the same question several months ago, but I couldn't find any useful introductory code samples on the Web that adequately explained how remoting should be done using a GUI, especially on the server. This article is an attempt to fill that gap.

There are a few introductory articles about remoting that do use GUIs and could be very useful to you, but they do not address all the topics discussed in this article, and only the third article shows a GUI being used for the server.

Prerequisites

This article will give you a good idea of how to use .NET remoting in a GUI application. You do not need to know a whole lot about remoting before reading this article, but you should be familiar with remoting basics- you should understand how a basic remoting console-based application works. A nice intro is at gotdotnet.com.

There are many design considerations when creating a distributed application using remoting, and I suggest you do a lot more research on the topic before creating any full-blown applications with it. A great resource is Advanced .NET Remoting 2nd Edition (Ingo Rammer and Mario Szpuszta, Apress, March 2005). Ingo goes into depth regarding remoting events and delegates which are common in GUI applications. He also provides some very useful advice regarding GUI usage with remoting in his article PRB: GUI application hangs when using non-[OneWay]-Events. This article is meant only as a simple introduction and only illustrates synchronous and asynchronous calls.

High Level Design

Let's start with a high level look at the application we are going to develop. A Client application will make calls on a remote object called Greeter which is housed in the Server's application space. The Server will register itself with the Greeter to be notified when a call is made on the Greeter. The Greeter will notify the Server of the call, and the Server will display the information to its GUI.

Diagram of application

Greeter Remote Object

The Greeter is our remote object, and like all remote objects, it inherits from MarshalByRefObject. Its lease does not expire since it returns null to InitializeLifetimeService. We'll see the Greeter being instantiated by the Server later on.

// Greeter.cs

using System;
using System.Threading;

namespace guiexample
{
    public class Greeter : MarshalByRefObject
    {
        // Used by GUI to listen for SayHello calls
        public delegate void HelloEventHandler(object sender, HelloEventArgs e);
        public event HelloEventHandler HelloEvent;

        // Time in msec to wait until we respond to the Client
        private int mRespondTime;
        public int RespondTime
        {
            get { return mRespondTime; }
            set { mRespondTime = Math.Max(0, value); }
        }

        public Greeter()
        {
            // Default no argument constructor
        }

        public override Object InitializeLifetimeService()
        {
            // Allow this object to live "forever"
            return null;
        }

        public String SayHello(String name)
        {
            // Inform the GUI that SayHello was called
            if (HelloEvent != null)
                HelloEvent(this, new HelloEventArgs(name));

            // Pretend we're computing something that takes a while
            Thread.Sleep(mRespondTime);

            return "Hello there, " + name + "!";
        }
    }
}

The Greeter only implements a single method that can be called remotely: SayHello. It also has a property called RespondTime which will be used by the Server to set a delay when responding to a SayHello call. We're going to delay our response to simulate a slow-returning call and see how it affects the Client's GUI.

The SayHello method first raises a HelloEvent when it is called. This will alert any HelloEvent listeners to who has called this method. The Server will need to register an event handler for the HelloEvent so it can update its GUI. (Notice that we first checked for null just in case the Server "forgot" to register for the event.) This allows the Server and Greeter to be decoupled. This also matches the Observer design pattern where the Greeter is the subject being observed and the Server is the observer. Any number of events that the observer is interested in could be raised by our Greeter.

Server

In the Server's constructor the TCP channel using port 50050 is first created, and then the Greeter is instantiated and registered with RemotingServices.Marshal so it can be accessed by our Client. The Server is interested in knowing when the Greeter's SayHello method is called, so it registers a listener for the HelloEvent event. You will notice that we have not used RemotingConfiguration.RegisterWellKnownServiceType to register the Greeter as a Singleton because the Server needs to actually use the Greeter itself, not just expose it to clients.

public Server()
{
    // Required for Windows Form Designer support
    InitializeComponent();

    // Register our tcp channel
    ChannelServices.RegisterChannel(new TcpChannel(50050));

    // Register an object created by the server
    rmGreeter = new Greeter();
    ObjRef refGreeter = RemotingServices.Marshal(rmGreeter, "Greeter");

    rmGreeter.RespondTime = Convert.ToInt32(txbRespond.Text);

    // Register ourself to receive updates that we will display in our GUI
    rmGreeter.HelloEvent += new Greeter.HelloEventHandler(Server_HelloEvent);
}

The Server's Server_HelloEvent method is called when the Greeter raises the HelloEvent event. This is the Server's chance to update its GUI. It would be nice if we could update our GUI like this:

private void Server_HelloEvent(object sender, HelloEventArgs e)
{
    lblHello.Text = "Saying hello to " + e.Name;
}

But this could cause a deadlock. The reason is because the event is running in a thread that didn't create the Label control. The control can only be updated with the UI thread. So instead we will call BeginInvoke on the control and give it a delegate which will run asynchronously on the UI thread that created the control. This is necessary when updating GUI controls in a multithreaded environment. We'll do the same thing later on when updating the client's GUI.

For more information on using Control.BeginInvoke to update GUI controls, see S. Senthil Kumar's article What's up with BeginInvoke? and Ingo's article PRB: GUI application hangs when using non-[OneWay]-Events.

private delegate void SetLabelTextDelegate(string text);
// For updating label

private void SetLabelText(string text)
{
    lblHello.Text = text;
}

// Called when the Greeter object tells us SayHello has been called
private void Server_HelloEvent(object sender, HelloEventArgs e)
{    
    // Pull out name from the event
    string text = "Saying hello to " + e.Name;

    // Set the label text with the UI thread to avoid possible application hanging
    this.BeginInvoke(new SetLabelTextDelegate(SetLabelText), new object[] {text});
}

The HelloEventArgs supplies the information needed to update the GUI. In this case we are only interested in who has called the SayHello method, but we could have enhanced HelloEventArgs to contain error messages, timestamps, etc. The following is our HelloEventArgs class which has a read-only property called Name:

public class HelloEventArgs : EventArgs 
{
    private string mName;
    public string Name
    {
        get { return mName; }
    }

    public HelloEventArgs(string name)
    {
        mName = name;
    }
}

Our Server uses a text box to allow us to control the amount of time before our remote object responds to the client. We don't want the user to enter just any value in the text box, so we perform some data validation using the TextChanged event before setting the RespondTime.

private void txbRespond_TextChanged(object sender, System.EventArgs e)
{
    try
    {
        // Set delay before we respond to the client

        int delay = Convert.ToInt32(txbRespond.Text);
        if (delay >= 0)
            rmGreeter.RespondTime = delay;
    }
    catch (Exception) {}
}

Client

The Client creates a TCP channel in its constructor and obtains a reference to the Greeter object made available by the Server.

public Client()
{
    // Required for Windows Form Designer support
    InitializeComponent();

    // Register our tcp channel
    ChannelServices.RegisterChannel(new TcpChannel());

    rmGreeter = (Greeter)Activator.GetObject(
        typeof(guiexample.Greeter), "tcp://localhost:50050/Greeter");

    lblResult.Text = "";
}

The Client has two buttons for calling the remote method SayHello. The first button, Call Synchronously, uses a synchronous call to SayHello. It is a blocking call - it locks the current thread until a response is returned by the Greeter. This is not the ideal situation in a GUI because the GUI must continue to listen for messages from the OS such as button clicks, mouse moves, paint updates, etc. Clicking Call Synchronously will cause the GUI to freeze until SayHello returns. Try clicking the button and then moving the window before the window is updated, to see what I mean.

We use a try/catch block around the call to SayHello just in case our Server goes down while we are in the process of making the call or if the Server was never available to start with. It's always a good idea to encapsulate remote method calls in a try/catch to make your application more robust.

private void btnCallSynch_Click(object sender, System.EventArgs e)
{
    lblResult.Text = "";

    // Force update since the remote method
    // call blocks us from receiving window messages
    lblResult.Refresh();   

    try
    {
        lblResult.Text = rmGreeter.SayHello(txbName.Text);
    }
    catch (Exception ex)
    {
        MessageBox.Show(this, "Unable to call SayHello. " + 
             " Make sure the server is running.\n"
             + ex.Message, "Client", MessageBoxButtons.OK, 
             MessageBoxIcon.Error);
    }
}

The second button, Call Asynchronously, uses an asynchronous call to SayHello. Actually it is just using a delegate to simulate a synchronous call, but the net effect is that our GUI is no longer locked when making a remote call. This is probably what you should do anytime your GUI client needs to call a remote method. You'll notice that when we call BeginInvoke, we pass the text the user has entered into our text box.

As an aside, you may be wondering about OneWay calls. OneWay calls allow you to call a void function without blocking the calling thread. Unfortunately these types of calls can cause serious performance hits when the recipients of these calls go down. For more information on why you should avoid OneWay calls, read the section entitled Why [OneWay] Events Are a Bad Idea from Ingo's book Advanced .NET Remoting.

private void btnCallAsynch_Click(object sender, System.EventArgs e)
{
    lblResult.Text = "";

    // Produce an asynchronous call to the remote method SayHello from which
    // we'll immediately return

    AsyncCallback cb = new AsyncCallback(this.SayHelloCallBack);
    SayHelloDelegate d = new SayHelloDelegate(rmGreeter.SayHello);
    IAsyncResult ar = d.BeginInvoke(txbName.Text, cb, null);
}

When the SayHello method completes, the Client's SayHelloCallBack method will be called. We can pull out the return value from SayHello by calling EndInvoke. Just like the synchronous call to SayHello, we have placed the call to EndInvoke in a try/catch block in case our Server wasn't running.

The SayHelloCallBack method doesn't try to update the Label directly because it is not running in the UI thread that created the Label control. It instead calls BeginInvoke so it will asynchronously call SetLabelText to change the Label's text. We did something similar with the Server.

private delegate void SetLabelTextDelegate(string text);
// For updating label

private void SetLabelText(string text)
{
    lblResult.Text = text;
}

public void SayHelloCallBack(IAsyncResult ar)
{
    SayHelloDelegate d = (SayHelloDelegate)((AsyncResult)ar).AsyncDelegate;

    try
    {
        // Pull out the return value
        string text = (string)d.EndInvoke(ar);

        // Set the label text with the UI thread
        // to avoid possible application hanging
        this.BeginInvoke(new SetLabelTextDelegate(SetLabelText), 
                                           new object[] {text});
    }
    catch (Exception ex)
    {
        MessageBox.Show(this, "Unable to call SayHello." + 
            " Make sure the server is running.\n" + 
            ex.Message, "Client", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Running the Application

Download the application and run the server (server.exe) and the client (client.exe). If you want to run the application on different computers, you'll need to change "localhost" to the names of the computers the client and server are running on, and rebuild the application.

I have provided a make.bat file that uses the C# command-line compiler csc to rebuild the application:

csc /t:library /r:System.Runtime.Remoting.dll Greeter.cs HelloEventArgs.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll 
                   /target:winexe /out:server.exe Server.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll 
                   /target:winexe /out:client.exe Client.cs

Improvements

There are several improvements we could make to our application that don't improve its execution speed but do improve its design: use the Singleton design pattern to ensure the Greeter is only instantiated once by the Server, use interfaces to enforce proper use of the Greeter by the Server and Client, and use configuration files to allow for easier changes in deployment.

Singleton Design Pattern

Our Greeter should only be instantiated once by the Server. We can control this by using the Singleton design pattern. Jon Skeet discusses implementing the Singleton design pattern in C#.

We need to make the Greeter's constructor private so that we can enforce how many times the Greeter can be instantiated. We'll also provide an Instance property that the Server will use to instantiate the Greeter.

static Greeter mInstance = null;

private Greeter()
{
    // Private constructor prevents instantiation with "new"
}

public static Greeter Instance
{
    get
    {
        if (mInstance == null)
            mInstance = new Greeter();
        
        return mInstance;
    }
}

The Server will now instantiate the Greeter like so:

Greeter g = Greeter.Instance;

Now we are assured that the Server cannot accidentally create several Greeters.

Using Interfaces

The current implementation allows a Client to change the RespondTime and listen for HelloEvents. We may want the Server and Client to have separate views of the Greeter to enforce proper implementation. By creating separate interfaces for the Client and Server to Greeting, we can prevent the Client from having access to RespondTime and HelloEvents.

Let's first create an interface for the Client that allows the Client to only call SayHello.

public interface IClientGreeter
{
    String SayHello(String name);
}

Now let's create an interface for the Server to allow it access to RespondTime and HelloEvent.

public interface IServerGreeter
{
    int RespondTime
    {
        get;
        set;
    }

    event Greeter.HelloEventHandler HelloEvent;
}

The Greeter needs to implement both these interfaces and modify how ResponseTime and SayHello are declared:

public class Greeter : MarshalByRefObject, IServerGreeter, IClientGreeter
...

int IServerGreeter.RespondTime
...

String IClientGreeter.SayHello(String name)
...

Now we need to change the way the Client and Server access the Greeter. In the Client, we change our private reference from a Greeter to an IClientGreeter, and we change how we activate the Greeter.

private IClientGreeter rmGreeter;

...

rmGreeter = (guiexample.IClientGreeter)Activator.GetObject(
    typeof(guiexample.IClientGreeter), "tcp://localhost:50050/Greeter");

We'll make similar code changes on the Server:

private IServerGreeter rmGreeter;

...

Greeter g = new Greeter();
ObjRef refGreeter = RemotingServices.Marshal(g, "Greeter");
rmGreeter = (IServerGreeter)g;

Now if you tried to set the RespondTime on the Client, your code wouldn't compile.

A nice benefit to using interfaces is that you can completely decouple the implementation of the remote object from the client. If we create a DLL for our IClientGreeter interface, we can deploy just the IClientGreeter.dll to the client. Any changes we make to the Greeter on the server (keeping the interface intact) will not require any redeployment of code or files to the client.

We would compile our system like so:

csc /t:library /r:System.Runtime.Remoting.dll 
         Greeter.cs HelloEventArgs.cs IServerGreeter.cs IClientGreeter.cs
csc /t:library /r:System.Runtime.Remoting.dll IClientGreeter.cs
csc /r:Greeter.dll /r:System.Runtime.Remoting.dll 
         /target:winexe /out:server.exe Server.cs
csc /r:IClientGreeter.dll /target:winexe /out:client.exe Client.cs

The server would need Greeter.dll and server.exe, and the client would need IClientGreeter.dll and client.exe to execute.

Configuration Files

When you are first learning about remoting, it is much easier to understand when all the channel and objects are explicitly created directly in the source code. But later when you want to run your programs on different computers or use different ports, you find that it's a real pain to have to change your code and recompile. That's where configuration files come into play.

Let's create a server.exe.config file for the Server:

<configuration>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="tcp" port="50050" />
            </channels>
        </application>
    </system.runtime.remoting>
</configuration>

Let us replace the RegisterChannel call with the following in the Server's constructor:

RemotingConfiguration.Configure("server.exe.config");

And let's create a client.exe.config file for the Client:

<configuration>
    <appSettings>
        <add key="GreeterUrl" value="tcp://localhost:50050/Greeter" />
    </appSettings>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="tcp" port="0" />
            </channels>
        </application>
    </system.runtime.remoting>
</configuration>

This allows us to remove the RegisterChannel call from the Client's constructor and to remove the hard-coded URL to the Greeter remote object:

// Create a TCP channel
RemotingConfiguration.Configure("client.exe.config");

Greeter g = 
    (Greeter)Activator.GetObject(typeof(guiexample.Greeter), 
    System.Configuration.ConfigurationSettings.AppSettings["GreeterUrl"]);

Now we can deploy our application and just change the config files whenever our server's URL or port number changes.

Conclusion

I've presented some very elementary concepts when using remoting in a GUI environment that I have been unable to find anywhere else on the Web.

Here are the important points to remember when using GUIs with remoting:

  • The server should instantiate the remote object and make it accessible to the client using RemotingServices.Marshal.
  • The remote object should use events to update the server's GUI.
  • Asynchronous calls are better in a GUI environment than synchronous calls because asynchronous calls will not lock the GUI while the remote method call completes.
  • All updates to the client's GUI and server's GUI should be made with the UI thread using Control.BeginInvoke (or a similar method) when completing an asynchronous call or handling an event from the remote object.
  • Use interfaces to cleanly separate the client's view of the remote object and the server's view of the remote object.

I hope you'll find this article helpful. Feel free to leave me any constructive criticism.

Revision History

  • 06-03-2005
    • Added a few more introductory remoting articles to the list.
    • Showed how to compile the system using make.bat.
    • Checked for null before invoking HelloEvent.
    • Improved interfaces section.
  • 05-17-2005
    • Added compiler switch to make.bat so server and client applications wouldn't open console windows.
    • Added "important points" to the conclusion section.
  • 05-11-2005
    • Changed code to show Label updates with BeginInvoke.
  • 05-07-2005
    • Original article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here