Click here to Skip to main content
Click here to Skip to main content

A Remoting Event (Simple and Efficient for Enterprise Solutions)

By , 21 Aug 2006
 

demo project Image

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.

Security Exception Image

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.

IO exception Image

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.

Sequense of events Image

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

/// <summary>
/// Represents the method that will handle
/// the Remotable.RemoteClass.MessageReceived event.
/// </summary>
/// <param name="message">Received message</param>

[Serializable]
public delegate void MessageHandler(string message);

/// <summary>
/// Shared remoting class that orchestrate messaging tasks 
/// </summary>

public class RemoteClass:MarshalByRefObject
{

    /// <summary>
    /// Occurs when a broadcast message received.
    /// </summary>

    public event MessageHandler MessageReceived;
    /// <summary>
    /// Initializes a new instance of the Remotable.RemoteClass class.
    /// </summary>

    public RemoteClass()
    {
       
    }

    /// <summary>
    /// Obtains a lifetime service object to control the lifetime policy for this
    /// instance.
    /// </summary>
    /// <returns>
    ///An object of type 
    ///System.Runtime.Remoting.Lifetime.ILease used to control
    ///the lifetime policy for this instance. This is the current lifetime service
    ///object for this instance if one exists; otherwise, a new lifetime service
    ///object initialized to the value of the 
    ///System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime
    ///property.
    ///null value means this object has to live forever.
    /// </returns>

    public override object InitializeLifetimeService()
    {
        return null;
    }

    /// <summary>
    /// Broadcast message to all clients
    /// </summary>
    /// <param name="message">message string</param>

    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)
{
    //Configure remoting.

    RemotingConfiguration.Configure(Application.StartupPath + 
                          "\\Client.exe.config",false);

    // Create a proxy from remote object.

    remoteClass = (RemoteClass)Activator.GetObject(typeof(RemoteClass), 
                   "http://localhost:8080/Chat");
    //Create an instance of wrapper class.

    wrapperClass = new WrapperClass();
  
    //Associate remote object event with wrapper method.

    remoteClass.MessageReceived += new 
          MessageHandler(wrapperClass.WrapperMessageReceivedHandler);
    //Associate wrapper event with current form event handler.

    wrapperClass.WrapperMessageReceived += new 
           MessageHandler(MessageReceivedHandler);
}

Wrapper Class

/// <summary>
/// Occurs when a broadcast message received.
/// </summary>

public event MessageHandler WrapperMessageReceived;
/// <summary>
/// Wrapper method for sending message to the clients.
/// </summary>
/// <param name="sender">Caller object</param>
/// <param name="args">Message data</param>

public void WrapperMessageReceivedHandler(string message)
{
    // forward the message to the client

    if(WrapperMessageReceived != null)
        WrapperMessageReceived(message);
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Hossein Ghahvei Araghi
Program Manager System Group
Iran (Islamic Republic Of) Iran (Islamic Republic Of)
Member
Hossein Ghahvei Araghi
Birth date: 1978
Birth place: Iran
Academic Credentials : BS(Tehran University)
Microsoft Credentials : MCP, MCAD, MCTS 2.0, MCTS 3.5, MCPD 2.0, MCPD 3.5

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMultiple Eventmemberborchanii7 Sep '10 - 0:10 
Thanks for this greatefull post Smile | :)
I am triying to use this sample to reproduce my own remoting need , the problem is how can i handle multiple event for server / client :
we can say as example to have tow listbox for chat and they are updated standalone ( not a the same event) ?
thanks.
GeneralRe: Multiple Eventmemberysgepl27 Jul '11 - 19:49 
this is what i am finding ...
GeneralSerilizable tagmemberxandy197926 Nov '08 - 14:02 
I wonder when why I made an custom event arguments, I have to add the [Serilizable] Tag while the Marshalbyobjref remote object that is no needed. I tried if my event arguments is not tagged in serilizable, the program can still run without runtime or compile time error, but the only issue is the remote client cannot get any events, even with the wrapper event class.
 
And also, using the wrapper class in-between the remote object and remote client sounds non-sense to me somehow, as what I expected the remote proxy or whatever should handle these kinds of routine work for me, rather than making the codes repeating again and again (Whenever I have remote event, I have to write two lines to register the delegate instead of one), and the asynchrous call from the remote event to GUI elements, I have to make two handler to compeletely receive that event. The proxy is not transparent to the client code at all!
GeneralThat's what I really need!memberxandy197920 Nov '08 - 13:50 
I tried hard to figure out how to get rid of those security issues in your problem solution 1. For days of searching in msdn and nothing good was found. Thanks.
GeneralTrying to do this without the app.config filesmemberObsidianPC10 Nov '08 - 14:09 
Hi guys,
 
First off - awesome tutorial - I'm very new to this and trying to learn a bit more about it.
 
Sorry if this is a silly question, but I am trying to do this without the app.config files - partly because I like it all handled by code, and partly to force myself to learn this without magic files - and I'm running into some frustrating problems.
 

First, in the client form, I changed
RemotingConfiguration.Configure(Application.StartupPath + "\\Client.exe.config",false);
to
 
RemotingConfiguration.RegisterWellKnownClientType(typeof(Remotable.RemoteClass), "http://localhost:8080/Chat");
HttpChannel chan = new HttpChannel(0);
ChannelServices.RegisterChannel(chan, false);
After that, all was well - I could still test everything out, and multiple clients still would connect ok.
 
Then, on the server form, I changed
 
RemotingConfiguration.Configure(Application.StartupPath + "\\Server.exe.config",false);
to
 
RemotingConfiguration.RegisterWellKnownServiceType(typeof(Remotable.RemoteClass), "Chat", WellKnownObjectMode.Singleton);
            
BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
                        
BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
            
IDictionary props = new Hashtable();
props["port"] = 8080;
 
HttpChannel chan = new HttpChannel(props, clientProv, serverProv);
ChannelServices.RegisterChannel(chan);
 

Now I am getting "System.ArgumentNullException: No message was deserialized prior to calling the DispatchChannelSink." followed by a stacktrace.
 
It happens in the "buttonSend_Click" method in the Client form. It compiles and opens fine, but when you hit the send button you get that error. Like I said, I'm not too familiar with this, but if you could help me out, or point me in the right direction I would be really grateful.
 

Thanks very much,
 
Alex
GeneralRe: Trying to do this without the app.config filesmemberObsidianPC10 Nov '08 - 14:44 
Alright, I found out the main problem - I am using sending everything using a Http channel, and then had tried to use Binary client and server FormatterSinkProviders.
 
Once i changed BinaryServerFormatterSinkProvider to SoapServerFormatterSinkProvider it started working ok.
 
But after I finish this, the reason I'm looking into this is actually to transfer some binary data - I was intending on using TCP channels to do that.
 
So... I changed all of the channels over to using TCP channels. And then I got a
"The server committed a protocol violation." error.
 
Well, I was in the middle of writing this out, asking for help again, when I remembered I hadn't changed my URI strings to TCP - they still read "http://localhost:6666/Chat"
 
So I changed them to "tcp://localhost:6666/Chat" and it works again, but I decided to finish off the message in case anyone else ran into that error.
GeneralRe: Trying to do this without the app.config files [modified]memberObsidianPC10 Nov '08 - 14:51 
-- Edit --
Eraghi -- I finally got all of this working in my own app for the first time. Thank-you so much for writing this article - I know it doesn't go over everything, but I've gone over a good handful of remoting articles in the last few days and none of them actually got me anywhere, or made it seem clearer for me - all I got was confused. This one was the one that finally 'clicked' it for me - I just wanted you to know that!
-- EndEdit --
 
Incidentally, I am trying to learn this to accomplish something specific.
 
I am sort of an amateur game designer. I'm working with XNA to make some fun stuff, but I want to try my hand at writing some cool tools to complement it.
 
Instead of just an exporter, I am trying to tie 3dStudio Max or Maya to my engine a bit more. I want to let the editor connect to a running game instance and update the position of entities, lights, and whatnot directly in the game. If that works I might go on to triggering events or sending scripted commands to the game also - but baby steps.
 
My intention was to write a .Net component that Max can spawn that will connect to the game and commandeer it.
 

So...this is one of the tutorials I'm looking at. If any of you guys have any insight on whether this is (or is definitely not!) a good way to go about achieving this I would really appreciate any input.
 
Thanks for any input!
 

Alex
 
modified on Monday, November 10, 2008 9:30 PM

QuestionQuestion about broadcastingmemberMazdak22 Jul '07 - 20:43 
Thanks for your nice article but I have a questuoin, how can I prevent broadcasting and specific client just notify when I use remoting and events?
 
Mazy

"This chancy chancy chancy world."

AnswerRe: Question about broadcastingmemberObsidianPC11 Nov '08 - 7:08 
I'm not very knowledgeable in this sort of thing -- all I could really think of using remote events would be to store the client object reference when it subscribes to events, then check to see if it matches as the event checks through the invocation list.
 
It might not be that bad, since the code from the article has you go through the invocation list anyway to make sure there aren't any unresponsive clients each time the event is fired.
 
However...I don't know if the object of the invocation would get invalidated over time -- never done that before.
 

 
Alex
Generalconvert to VB.NETmemberfyclinic23 May '07 - 23:47 
hello my friend
please help me about convert this project to VB.NET !
i have 2 problem too .
i can not speak english very well .
please help me
thanks
my mail : yavarifatemeh@yahoo.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 21 Aug 2006
Article Copyright 2006 by Hossein Ghahvei Araghi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid