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.
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:
public void loggerExampleBehavior(Method theMethod){
Log("Before method execution.");
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 ?
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.
public class ProxyCreator
{
public static T MakeINotifyPropertyChanged<T>() where T : class, new()
{
ProxyGenerator proxyGen = new ProxyGenerator();
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:
- It exposes a
PropertyChangedEventHandler
- It raises the
PropertyChangedEventHandler
event when a setter is called with the good name
Also, I have cached the PropertyChangedEventArgs
for better performance.
public class NotifierInterceptor : IInterceptor
{
private PropertyChangedEventHandler handler;
public static Dictionary<String, PropertyChangedEventArgs> _cache =
new Dictionary<string, PropertyChangedEventArgs>();
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name == "add_PropertyChanged")
{
handler = (PropertyChangedEventHandler)
Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
invocation.ReturnValue = handler;
}
else if (invocation.Method.Name == "remove_PropertyChanged")
{
handler = (PropertyChangedEventHandler)
Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
invocation.ReturnValue = handler;
}
else if (invocation.Method.Name.StartsWith("set_"))
{
invocation.Proceed();
if (handler != null)
{
PropertyChangedEventArgs arg =
retrievePropertyChangedArg(invocation.Method.Name);
handler(invocation.Proxy, arg);
}
}
else invocation.Proceed();
}
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:
MyBusinessObject myBusinessObject;
DataContext = myBusinessObject =
ProxyCreator.MakeINotifyPropertyChanged<MyBusinessObject>();
Interesting Links
CodeProject