This article explains how to implement the remoting Framework with a simple Test example.

Introduction
During my professional activity as a Senior Software Engineer for Computer Numerical Control (CNC), I had the opportunity to develop a Framework covering a wide spectrum of applications. This article specifically covers generic Client-Server communication Framework with server callback notifications.
Further articles will focus on diagnostics with log4net and DbgView, common WinForm oriented Framework and a LogViewer application for viewing log files and implementing the Framework as an example.
As simple is beautiful, I've removed everything from the Framework that isn't necessary for this article.
This article describes only what must be understood for the implementation of the generic remoting Framework in a Client-Server communication. The details of the classes are reported to the appreciation of each one.
DotNet Remoting Framework
In a Client-Server communication, what is important for the client is that it is able to connect to the server at any time, whether the server is started before or after the client or that communication is cut for some reason and re-established later. For the server, what is important is that it is able to recognize if a client connects, if it disconnects, or that the communication is cut for some reason or another and re-established later.
The most important thing in Client-Server communication is that the server should be able to return notifications to the client.
Restriction, Limitation
The generic remoting Framework assumes that multiple clients can connect to the server, but the server has only one Sink
for all clients. It also assumes that the connection between the client and the server is permanent, no lease.
Client-Server Definitions
Before implementing a client-server communication, it is necessary to define two things.
- The interface that the server presents to the client.
- The type of notification argument that the server uses to notify the client.
As this information is used by the server and the client, it is necessary to define them in a common project for the client and the server. For the test example in this article, the common project is TestLibrary
. The client project is TestClient
and the server is TestServer
.
The Interface
For this test example, the interface looks like this:
namespace TestLibrary
{
public interface ITestInterface : IConnect<EventArgs>
{
void Execute(string message);
void Execute(Guid clientID, string message);
void ExecuteSync(string message);
void ExecuteException();
void ExecuteNotifyException();
}
}
The interface must always derive from IConnect<EventArgs>
! The IConnect<EventArgs>
interface is implemented in the RemoteServer
class server-side, and in the RemoteConnector
class client-side. Only your interface is to be implemented using these two base classes.
It is possible to define any method in this interface, but care must be taken that the method parameters used must be serializable. This example defines fife methods but the number is not limited. These methods simply send a message to the server or ask the server to generate exceptions. The server implementation will send the message back to the client via a notification asynchronously or synchronously or generate the exceptions as example.
The Server Notification Argument
The server notification argument looks like this:
namespace TestLibrary
{
[Serializable]
public class TestMessageArgs : EventArgs
{
public string Message { get; }
public TestMessageArgs(string message)
{
Message = message;
}
}
}
It should be derived from EventArgs
but more importantly it should be serializable.
The Executes(..)
message on the server will send back the message as notification to the client using this argument.
Server Exceptions
There are two exception types. Exception by notification or exception thrown by the server. You may catch a server exception on the server and notify the clients that an exception occurs via a NotifyExceptionArgs
notification or you may catch a generic server exception on the client. It is possible to define your one exception in the common library, but the exception should be serializable.
Server Implementation
The server implementation looks like this:
namespace TestServer
{
public class TestServer : RemoteServer<TestSink>
{
public TestServer(IChannel channel)
: base(channel) { }
public void SendNotificationToAllClients(string message)
{
Sink?.PerformNotifyClients(new TestMessageArgs(message));
}
}
}
The TestServer
class derives from the generic RemoteServer<T>
and is only used to define the Sink
as T
parameter.
The application has no access to the sink created by the DotNet remoting but the RemoteServer<T>
class provides a T
Sink property which is created when and only when the first client connects. Once created, the Sink
is permanent.
The RemoteServer<T>
class provides events for:
ClientConnect
: a client connects ClientDisconnect
: a client disconnects Starting
: the server is starting Waiting
: the server is waiting, it is possible to cancel the server from the event handler Stopping
: the server is stopping Stopped
: the server is stopped
The most important piece of server-side code is the Sink
. There is only one Sink
in the server by definition. It implements the server Interface.
namespace TestServer
{
public class TestSink : NotifyClientSink, ITestInterface
{
public void Execute(string message)
{
PerformNotifyClients(new TestMessageArgs(message));
}
public void Execute(Guid clientID, string message)
{
PerformNotifyClient(clientID, new TestMessageArgs(message));
}
public void ExecuteSync(string message)
{
PerformNotifyClientsSync(new TestMessageArgs(message));
}
public void ExecuteException()
{
throw new NotImplementedException("ExecuteException");
}
public void ExecuteNotifyException()
{
try
{
throw new ApplicationException("ExecuteNotifyException");
}
catch (Exception ex)
{
PerformNotifyClients(new NotifyExceptionArgs(ex));
}
}
}
}
The Sink
simply notify the clients or one client asynchronously or synchronously that a message is received. It also generates notification exception and server exception.
Attention: If the notification is synchronous, the server is blocked until the client gives the control back.
Client Implementation
The client implementation looks like this:
namespace TestClient
{
public class TestClient : IDisposable
{
private Delay _delay;
private RemoteConnector<ITestInterface> _remoteConnector;
private Guid ClientID => _remoteConnector.ClientID;
private ITestInterface TestProvider => _remoteConnector.RemoteObject;
public RemoteState RemoteState => _remoteConnector.RemoteState;
public void Dispose()
{
_remoteConnector?.Dispose();
}
public void Initialize(IChannel channel)
{
_remoteConnector = new RemoteConnector<ITestInterface>(new CHANNEL());
_remoteConnector.Callback += OnRemoteCallback;
_remoteConnector.RemoteStateChanged += OnRemoteStateChanged;
}
private void OnRemoteCallback(object sender, EventArgs e)
{
if ((Guid)sender != ClientID)
return;
if (e is TestMessageArgs args1)
Console.WriteLine("Message from Server: " + args1.Message);
else if (e is NotifyExceptionArgs args2)
Console.WriteLine("Exception from Server: " + args2.Exception.Message);
}
private void OnRemoteStateChanged(object sender, RemoteState remoteState)
{
switch (remoteState)
{
case RemoteState.Connected:
Console.WriteLine("RemoteState: [" + RemoteState +
"] Client [" + ClientID +
"] Delay [" + _delay.TotalMilliseconds + "] ms");
ExecuteCommandsOnServer();
break;
default:
Console.WriteLine("RemoteState: [" + RemoteState + "]");
break;
}
}
public void Start()
{
if (_remoteConnector == null)
throw new ApplicationException("Please call Initialize before Start");
_delay = new Delay();
_remoteConnector.Start(10);
}
public void Stop()
{
_remoteConnector?.Stop();
}
private void ExecuteCommandsOnServer()
{
for (int i = 1; i <= 3; i++)
{
TestProvider.Execute("Hello TestServer all #" + i);
Thread.Sleep(500);
}
for (int i = 1; i <= 3; i++)
{
TestProvider.Execute(ClientID, "Hello TestServer me #" + i);
Thread.Sleep(500);
}
for (int i = 1; i <= 3; i++)
{
TestProvider.ExecuteSync("Hello TestServer Sync all #" + i);
Thread.Sleep(500);
}
try
{
TestProvider.ExecuteException();
}
catch (Exception ex)
{
Console.WriteLine("** Server exception: " + ex.Message);
}
TestProvider.ExecuteNotifyException();
}
}
}
The TestClient
class is like a controller. It implements a RemoteConnector<ITestInterface>
to connect to the server. The remote connector provides the ITestInterface
from the server as TestProvider
, the server Callback
event, the RemoteStateChanged
events from the connector and the client ID. It must implement the IDisposable
interface to release the remote connector as the remote connector has a working thread to connect or reconnect to the server.
The Start()
method connects to the server. In this example, it will wait max 10 seconds until the connection is established but it is also possible to wait longer or not (param = 0). Once the connection is ready, a RemoteStateChanged
event will be generated with a RemoteState.Connected
argument. The Delay
object informs about the duration of the connection. When ready, the client code ExecuteCommandsOnServer()
is executed, but this is only as an example. Client may execute server calls everywhere.
The OnRemoteCallback(..)
event checks if the notification is for me. The server can send notification to a specified client as necessary. If more than one client is connected, the server broadcast notifications to all clients.
How to test the example
To test the remoting Framework, start a Client and a Server, stop and restart else the Client or the Server and see how the Client or the Server can reconnect automatically.
Projects in this Article
Generic Client-Server DotNet Remoting Projects
The projects for this Test example are:
FW.Remoting
: the generic client-server DotNet remoting component TestLibrary
: the common library for server and client test projects TestServer
: the server project TestClient
: the client project TestClientServer
: an application to launch the server and 3 clients
Other Framework Projects
These components will be described in other publications.
FW.Common
: common components FW.Diagnostics
: diagnostics components for all kind of projects using log4net and/or DbgView FW.WinApi
: generic components to access the Windows API FW.WinForm
: generic components for Winform applications
LogViewer WinForm Application
Log4Net
simple viewer to display the xlog files generated by the Test
applications. This viewer will be described in another publication.

Conclusion
If you need a strong client-server remote communication with server Callback, this Framework is what you need!
History
- 27th February, 2021: Initial version
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.