Wednesday, May 23, 2012

The Amazing EventProxy

Recently at work (as these posts usually come), I've been tasked with handling loading our entire framework (after downloading it from a remote source), and executing everything in a way that let's us administrate it.  I've been delving into the entire AppDomain paradigm which was plenty fun to deal with in the 70-536, but I haven't really touched it (or read on it) in about five years.

That being said, when working with AppDomain's, you have to make sure that the objects you're using can remote across to other AppDomain's correctly -- and by doing so, the easiest method is generally to derive the classes in the calls from MarshalByRefObject.  Pretty easy, huh?  Almost.

There is one caveat to this -- if you want to access an event on your cross-domain object, it requires your class to be Serializable or also be derived from MarshalByRefObject.  Still pretty simple, right?  Mostly -- but what if you have a control or user interface element that is already derived from something else?  Something that shouldn't be serialized at all...  In my case, it is a ViewModel.

After much deliberation, the smartest idea was to create a proxy -- pretty cool and easy, right?  Almost.  The only problem is that if I hard-coded the proxy, we'd be stuck with constantly updating the proxy for each and every object.  Some of our calls are just a simple EventHandler<EventArgs>, but some of our calls are more complicated EventHandler<MakesYourMindExplodeFromAnotherAppDomainEventArgs>.  How could I possibly allow this level of flexibility...?

Enter, the EventProxy:

    public class EventProxy<T, Args> : MarshalByRefObject
        where Args : EventArgs
    {
        public event EventHandler<Args> EventOccurred;

        public EventProxy(T instance, string eventName)
        {
            EventInfo eventOccurred = typeof(T).GetEvent(eventName);
            if (eventOccurred != null)
            {
                MethodInfo onEventOccurred = this.GetType().GetMethod("OnEventOccurred", BindingFlags.NonPublic | BindingFlags.Instance);

                Delegate handler = Delegate.CreateDelegate(eventOccurred.EventHandlerType, this, onEventOccurred);

                eventOccurred.AddEventHandler(instance, handler);
            }
        }

        private void OnEventOccurred(object sender, Args e)
        {
            if (EventOccurred != null)
                EventOccurred.Invoke(sender, e);
        }
    }

It is incredibly simple.

It's also important to note that this needs to be an assembly that both AppDomains reference.

For my ViewModel's, I'm adding it as such:

   EventProxy<CrossDomainObject, EventArgs<string>> Proxy = new EventProxy<CrossDomainObject, EventArgs<string>>(MyCrossDomainObject, "StatusUpdated");
   Proxy.EventOccurred += (sender, e) => { Status = e.Value };

In this example, CrossDomainObject is my CrossDomainObject class, wherein MyCrossDomainObject is the instance of that class.  The EventArgs<string> is a generic EventArgs that takes a string.  From there, I just update my status in the ViewModel, without any issues of the SerializationException.

There are a few safe-guards that would help make sure the EventProxy doesn't blow up and Exception out to hell, but it's a good start for someone to build on top of.

No comments:

Post a Comment