Background
I always had an interest in socket programming. I have created several chat applications and complex socket based applications. When I learned about events in Remoting I was so glad, and the first thing that I thought about was creating a chat application. But in the test, I met with some problems.
Problem 1
The first problem was a security exception:
System.Security.SecurityException: Type System.DelegateSerializationHolder
and the types derived from it (such as System.DelegateSerializationHolder)
are not permitted to be deserialized at this security level.
Solution 1
This problem was solved by adding the typeFilterLevel
attribute with Full
value to the formatter
element of both configuration files.
<formatter ref="soap" typeFilterLevel="Full" />
Problem 2
But again, it did not work and an IO exception occurred:
System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
System.IO.FileNotFoundException: Could not load file or assembly
'Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
or one of its dependencies. The system cannot find the file specified.
This exception occurs while the request is deserialized at the server. The server tries to call the event handler whereas the event handler exists in the client assembly. Because the client assembly is not available at the server, the exception is thrown.
Solution 2
An intermediate wrapper class, MarshalByRefObject
will solve this problem. This wrapper class is located in the shared assembly, accessible for both the client and the server; therefore, a delegate can resolve the method's signature. In the client application, we will associate the shared object event with the WrapperMessageReceivedHandler
method of the wrapper class and associate an event handler on the client with the MessageReceived
event of the wrapper class.
Why [OneWay] Attribute
Without defining the remote method as [OneWay]
, an exception will occur when the client is unreachable or has been disconnected without disassociating the event handler. By using [OneWay]
, no exception will occur on the client, but it will be in the invocation list of the server and, in longtime, will make your server slower to respond.
Solution 3
Instead of using the normal event invocation mechanism, you must invoke each delegate on your own, and if an exception occurs, you must disassociate the delegate from the invocation list of the event. In the end, you can remove the [OneWay]
attribute.
Shared Class
[Serializable]
public delegate void MessageHandler(string message);
public class RemoteClass:MarshalByRefObject
{
public event MessageHandler MessageReceived;
public RemoteClass()
{
}
public override object InitializeLifetimeService()
{
return null;
}
public void Send(string message)
{
if (MessageReceived != null)
{
MessageHandler messageDelegate = null;
Delegate[] invocationList_ = null;
try
{
invocationList_ = MessageReceived.GetInvocationList();
}
catch (MemberAccessException ex)
{
throw ex;
}
if (invocationList_ != null)
{
lock (this)
{
foreach (Delegate del in invocationList_)
{
try
{
messageDelegate = (MessageHandler)del;
messageDelegate(message);
}
catch (Exception e)
{
MessageReceived -= messageDelegate;
}
}
}
}
}
}
Client Application
RemoteClass remoteClass;
WrapperClass wrapperClass;
private void Form1_Load(object sender, EventArgs e)
{
RemotingConfiguration.Configure(Application.StartupPath +
"\\Client.exe.config",false);
remoteClass = (RemoteClass)Activator.GetObject(typeof(RemoteClass),
"http://localhost:8080/Chat");
wrapperClass = new WrapperClass();
remoteClass.MessageReceived += new
MessageHandler(wrapperClass.WrapperMessageReceivedHandler);
wrapperClass.WrapperMessageReceived += new
MessageHandler(MessageReceivedHandler);
}
Wrapper Class
public event MessageHandler WrapperMessageReceived;
public void WrapperMessageReceivedHandler(string message)
{
if(WrapperMessageReceived != null)
WrapperMessageReceived(message);
}