Ken Kolda
10/27/2004 9:33:00 PM
When you subscribe to a server event, you are essentially making the client
the server and vice versa. So, the same rules that govern when the client
needs the server assembly now govern whether the server needs the client
assembly.
When you subscribe to an event, you are passing a delegate from the client
to the server. Inside the delegate it holds two pieces of information:
1) A "target" object, which is a reference to the object instance on which
the callback lives
2) A "method" object which represent the method to be called.
So, the key point is that the delegate holds a reference to your client
object. Also, delegates are serializable types. So, when you pass the
delegate to the server, it gets serialized and deserialized. When the
deserialization takes place on the server, it goes to deserialize the
"target" object. To do that, it needs the assembly in which tha object
lives.
The problem with events is that they're not interface-driven -- the entire
target objects is referenced in the delegate. To work around this, you have
two main choices:
1) Don't use events. Instead, use straight callbacks and interfaces. For
example, instead of exposing an event named "Y", put a method on your server
called "SubscribeToY(IYListener)". Then have your client implement
IYListener and put the definition of this interface in your common assembly.
When the event is to be raised, the server invokes the method on the
IYListener interface for each subscribed object.
2) Use an event wrapper object. This is a simple object that is MarshalByRef
the whole purpose of which is to forward events to another object. For
example:
public class EventWrapper : MarshalByRefObject
{
public delHandler wrappedDelegate;
public EventWrapper(delWrapper wrappedDelegate)
{
this.wrappedDelegate = wrappedDelegate;
}
public delHandler EventHandler
{
get { return new delHandler(this.onEvent); }
}
private void onEvent()
{
wrappedDelegate();
}
}
The above class would go into your common assembly that's shared by client
and server. Now, to subscribe to a server event looks like:
XInterface x = Activator.GetObject(...)
x.Initialize();
x.Y += new EventWrapper(new delHandler(this.HandleEvent)).EventHandler;
Now, when the event gets raised on the server, the EventWrapper handles
transporting the event back to the client. It then passes the event on to
the ClientObject.
Hope that helps -
Ken
"mdb" <m_b_r_a_y@c_t_i_u_s_a__d0t__com> wrote in message
news:Xns958FAC72B961Fmbrayctiusacom@207.46.248.16...
> "Ken Kolda" <ken.kolda@elliemae-nospamplease.com> wrote in
> news:#TU#wxEvEHA.1824@TK2MSFTNGP10.phx.gbl:
>
> > You asserted that you would need to have the work class assembly on
> > the client -- what makes you think this is the case? The only shared
> > assembly should be the work interface assembly assuming you've got
> > your client coded correctly (i.e. it never references the work class,
> > only teh work interface).
>
> OK so I've seen how I don't have to distribute the server assembly to
> the client... I have a project defining the remoting interface, and I
> have a server project (windows service) that registers the type, and I
> have a client project that connects by using Activator.GetObject(...) on
> the interface type. Now I have another problem...
>
> When I try to subscribe to an event by running the client on a box other
> than the server, I get the "customErrors" issue that is well known with
> .NET 1.1 (accepted). When I run the client on the same machine as the
> server, I get the exception that was serialized back to the client
> saying that the server couldn't find the assembly for my client!! Why
> would the server need this??
>
> I'm not doing anything particularly wierd, just trying to subscribe to
> an event... its about like this...
>
> public interface XInterface
> {
> event delHandler Y;
> bool Initalize();
> }
> public delegate void delHandler();
>
> public ServerObject : MarshalByRefObject, XInterface
> {
> public event delHandler Y = null;
> }
>
> public ClientObject
> {
> private ConnectToServer()
> {
> ...
> XInterface x = Activator.GetObject(...)
> x.Initialize();
> x.Y += new delHandler(this.HandleEvent);
> ...
> }
>
> private void HandleEvent()
> {
> ...
> }
> }
>
> the x.Initialize function call works... I see it work on my server.
> When I try to subscribe to x.Y, that's where it says it can't find the
> client assembly.
>
> I have configured the TcpChannel on both the server and client with
> specific port numbers (in this case, 48100 on the server and 8008 on the
> client), and set the TypeFilterLevel to Full on both the server and the
> client, although I'm not sure I need it on the client.
>
> I know I can get thru this if I can just get over these wierd bumps!
>
> -mdb