Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVVM - Creating ViewModel : create dynamic proxies with Castle (solution 3 of n)

0.00/5 (No votes)
13 Mar 2010 1  
Here is the next episode of our serie MVVM - Creating ViewModel.

Here is the next episode of our serie MVVM - Creating ViewModel. A list of all the articles about creating a ViewModel is here.

Today we are going to see how to create dynamic proxies for our business objects.

What are Dynamic Proxies ?

Readers in a hurry can directly jump to the third part "An implementation".

Proxies are a Design Pattern which are used a lot in our computer programming world. Here is the Wikipedia definition:

A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.

The easiest example to understand I have found is sunglasses. The sunglasses are a proxy between the real world (with all the funky colors) and the world you see through them (where everything is grey/brown)... You also noticed that the proxy is not neutral, it adds a behavior(here the gray filter on your vision), but this is not mandatory.
Note: Laurent pointed out that he prefers the TV analogy to explain proxies, but I prefer the sunglasses in which there is no transformations 3D to 2D.

Proxies

With this in mind, let met introduce some vocabulary:

  • The subject: This is the proxied object. In our case: the real world
  • The client: This is the object which wants to use the subject. In our case, the one who wears the sunglasses
  • The proxy: This is the object used by the client and which uses the subject. In our case: the sunglasses
  • The behaviors/interceptors: This is a behavior that the subject does not have and that the proxy creates. In a program, a common interceptor is a logger which tells the start and the end of a method.

Behavior algo example:

C#
public void loggerExampleBehavior(Method theMethod){
  Log("Before method execution.");
  //Execution of the method
  Log("After method execution.");
}

So How To Use Them for our ViewModel? (Theory)

Readers in a hurry can directly jump to the third part, "An implementation".

To be brief: we will add the INotifyPropertyChanged behavior to the business objects by creating a dynamicProxy. We will then no more directly use the business object but the proxies of/to them.
We will so launch the PropertyChangedEvent for each call made to a setter of the business object. Pretty simple ?

proxies_INotify_logic.jpg

An Implementation with Castle dynamicProxy (Code)

To implement the proxy pattern dynamically, we will use Castle Dynamic Proxy. This framework is pretty simple to use as you'll see.

There is only one limitation: properties of your business object must be marked as virtual.

Creation of the Proxy

To create our proxy, I declare a static method. This method is generic and makes any object send NotifyPropertyChanged events on setter calls. The different classes used will be described later.

C#
public class ProxyCreator
{
  public static T MakeINotifyPropertyChanged<T>() where T : class, new()
  {
    //Creates a proxy generator
    ProxyGenerator proxyGen = new ProxyGenerator();
 
    //Generates a proxy using our Interceptor and implementing INotifyPropertyChanged
    var proxy = proxyGen.CreateClassProxy(
      typeof(T),
      new Type[] { typeof(INotifyPropertyChanged) },
      ProxyGenerationOptions.Default,
      new NotifierInterceptor()
      );
 
    return proxy as T;
  }
}

The Interceptor

The interceptor does two main things:

  1. It exposes a PropertyChangedEventHandler
  2. It raises the PropertyChangedEventHandler event when a setter is called with the good name

Also, I have cached the PropertyChangedEventArgs for better performance.

C#
public class NotifierInterceptor : IInterceptor
{
  private PropertyChangedEventHandler handler;
  public static Dictionary<String, PropertyChangedEventArgs> _cache =
    new Dictionary<string, PropertyChangedEventArgs>();
 
  public void Intercept(IInvocation invocation)
  {
    //Each subscription to the PropertyChangedEventHandler is intercepted (add)
    if (invocation.Method.Name == "add_PropertyChanged")
    {
      handler = (PropertyChangedEventHandler)
            Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
      invocation.ReturnValue = handler;
    }
    //Each de-subscription to the PropertyChangedEventHandler is intercepted (remove)
    else if (invocation.Method.Name == "remove_PropertyChanged")
    {
      handler = (PropertyChangedEventHandler)
         Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
      invocation.ReturnValue = handler;
    }
    //Each setter raise a PropertyChanged event
    else if (invocation.Method.Name.StartsWith("set_"))
    {
      //Do the setter execution
      invocation.Proceed();
      //Launch the event after the execution
      if (handler != null)
      {
        PropertyChangedEventArgs arg =
          retrievePropertyChangedArg(invocation.Method.Name);
        handler(invocation.Proxy, arg);
      }
    }
    else invocation.Proceed();
  }
 
  // Caches the PropertyChangedEventArgs
  private PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
  {
    PropertyChangedEventArgs arg = null;
    NotifierInterceptor._cache.TryGetValue(methodName, out arg);
    if (arg == null)
    {
      arg = new PropertyChangedEventArgs(methodName.Substring(4));
      NotifierInterceptor._cache.Add(methodName, arg);
    }
    return arg;
  }
}

How We Use It in the Application

We'll only have to expose the proxies to our views with a little snippet of code:

C#
MyBusinessObject myBusinessObject;
DataContext = myBusinessObject = 
	ProxyCreator.MakeINotifyPropertyChanged<MyBusinessObject>();

Interesting Links

Shout it kick it on DotNetKicks.com

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here