José F. Romaniello

Las aventuras y desventuras de un codificador.

The purpose of this post is to demonstrate several ways to use INotifyPropertyChanged as an AOP aspect in your business entities. I will use DynamicProxy for AOP and NHibernate as orm mapper. Althought all methods can be plugged into nhibernate using tuplizer, I will skip this part until the final solution.

The Problem

Let’s say you have to implement INotifyPropertyChanged for several entities of your domain. This is an example of implementation:
public class Customer : INotifyPropertyChanged
   {
       public event PropertyChangedEventHandler PropertyChanged;

       protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
       {
           PropertyChangedEventHandler changed = PropertyChanged;
           if (changed != null) changed(this, e);
       }

       private string _name;
       public string Name
       {
           get { return _name; }
           set
           {
               _name = value;
               OnPropertyChanged(new PropertyChangedEventArgs("Name"));
           }
       }

       private string _address;
       public string Address
       {
           get { return _address; }
           set
           {
               _address = value;
               OnPropertyChanged(new PropertyChangedEventArgs("Address"));
           }
       }
   }

First way: proxy with concrete target

I will start with proxies right now. If you want to know more about dynamic proxy I suggest this link.
The big picture

first

The client api call “set_Name” on the proxy, the proxy forwards this call to the interceptor stack. The PropertyInterceptor “proceed and wait” this invocation to the concrete type. The operaton is completed in the concrete type and then the interceptor raise the PropertyChanged event to the client.
The domain entity is defined as follows:

public class Customer : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Name { get; set; }
    public string Address { get; set; }
}

Note: the event is never raised inside the concrete class.
My proxy could by generated this way:

var proxyGenerator = new ProxyGenerator();
proxyGenerator.CreateInterfaceProxyWithTarget(typeof (Customer), 
                                                            new Customer(), 
                                                            new PropertyInterceptor());

The class PropertyInterceptor is trivial so I wont talk about it for now.
It’s an horrible workaround and don’t worth it. Why? because you end up with some kind of partial class that doesn’t work as expected if you don’t hang the interceptor.
Let’s clarify it, if you see “Customer : INotifyPropertyChanged” and it has a suitable constructor, you can assume that this class will work well even without proxy, interceptor or any kind of initialization.

Second way: proxy with concrete target (not INotifyPropertyChanged)

Maybe you are thinking right now: why do not remove INotifyPropertyChanged and put this interface into the proxy. Because the proxy is automatically generated your client api will need to do something like this:

Customer customer; //= Get the customer from somewhere;
            var notificable = customer as INotifyPropertyChanged;
            if(notificable != null)
                notificable.PropertyChanged += //...

And again, this is an horrible workaround. What happens if notificable is null? somebody has removed the additional interface from the proxy.

Third way: enough is enough

GoF: Program to an interface, not an implementation. I like very much this interview.
Then… your interface could be implemented in many ways (¿or not implemented at all?).
I will start showing this interface:

public interface ICustomer : INotifyPropertyChanged
{
    string Name { get; set; }
    string Address { get; set; }
}

I can guarantee that if you hold an ICustomer instance somewhere, it will work as you expect.
Do I need to code twice, one for the interface and one for the concrete class? NO. If this were the case I would not be bothered to write this beautiful post.
You don’t need to code twice because you don’t need any concrete implementation of ICustomer. I know, you are more confused now.
Fabio Maulo wrote a great post. My first impression when I read it was “Fabio is insane”, the second impression was “Fabio is really insane”. Here I’m, with a good reason to let my entities to be “services” in terms of IoC.
The big picture:

third

The client api invokes set_Name and the proxy pass this call over the interceptor stack. The PropertyChangedInterceptor still behave as the first example. The second interceptor “DataStore” store the data in a Dictionary and doesn’t proceed with the invocation because there isn’t any target!
Despite the fact that there is not a real implementation of ICustomer, there is an implementation that dynamic proxy has generated for me. The profit of using this approach is that you really need just one interceptor or two for the whole domain. Let me guess: not all of your entities are POCO. Nor mine, so the point is that you can go with “abstract” (search the interview about this) and if you really really need a concrete implementation write it, but write his interface first.

My implementation

Last but not least, I let the container to generate the proxies when a consumer of ICustomer interface needs it. I managed to create an extension for the registration api in Windsor:

container.Register(Component.For<ICustomer>()
                            .NotifyOnPropertyChange()
                            .TargetIsCommonDatastore());

There are two different things, you could service with a concrete type and still use “NotifyOnPropertyChange”. If you use NotifyOnPropertyChange, the service must implement INotifyPropertyChanged.
For instance the consumer of the ICustomer could be an API that wants to create a new Customer in our system, or NHiberante. In NHibernate I just have to map the interface to the database.
On the other hand, NHibernate need to know how to create an instance of an ICustomer, “Tuplizer” and “IInstantiator” is the solution (as Fabio has mentioned in his blog).

/// <summary>
/// Resolve an entity through ServiceLocator.
/// This allow to inject a proxy and intercepted entity.
/// </summary>
public class ServiceLocatorInstantiator : IInstantiator
{
    #region IInstantiator Members

    private readonly Type _entityType;

    public ServiceLocatorInstantiator(Type entityType)
    {
        _entityType = entityType;
    }

    public object Instantiate(object id)
    {
        var obj = ServiceLocator.Current.GetInstance(_entityType);
        return obj;
    }

    public object Instantiate()
    {
        return Instantiate(null);
    }

    public bool IsInstance(object obj)
    {
        return _entityType.IsInstanceOfType(obj);
    }

    #endregion
}

Fabio’s instantiator and mine have the same aim. The main difference is that my IInstantiator uses a ServiceLocator while the other uses DynamicProxy directly.
The sample project is in unhaddins.
I hope you enjoy this history as I did.

kick it on DotNetKicks.com

| More

13 comentarios:

Thanks for sharing code. Will be interesting to play with it and see how it might be possible to include this in production!

Thanks for your comment and feel free to contact me with any doubt.

Do you add MS Unity implement?Thanks

No, I use ServiceLocator but my di container is castle because it give me the power of DynamicProxy. I think unity have some kind of method interception but I never use it.

This is http://www.pnpguidance.net/Post/VirtualMethodInterceptorUnity12CastleDynamicProxyFans.aspx
amarok ,Thanks

<span><span> Take a look at INotifyPopertyChanged with PostSharp: http://www.lesnikowski.com/blog/index.php/inotifypropertychanged-with-postsharp/</span></span>

José F. Romaniello dijo...

Yes, I like postsharp is powerfull. I'll use soon.

WPF won't bind to my generated proxy class.

Any tips in figuring out how to get this working with WPF?

José F. Romaniello dijo...

I'm affraid that I can't help you without the code. What is the problem? WPF
won't bind, OR the notification mechanism doesn't work?
For the NotifyPropertyChange, remember that the sender of the event, should
be the proxy, not the target... This only if you are using interface proxy,
if you are using class proxy, the target and the proxy are the same thus
this doesn't care.

<span style="cursor: pointer;">Hi José, </span>

Since yesterday i've chopped and changed my solution to try and figure out a fix. Here is what I did.
1. Copied the NotifyPropertyIntercepter
2. Copied all Intercepters regarding the TargetIsCommonDataStore
3. Set up my Windsor Container inside the OnStartUp event inside the WPF App.xmal.cs
4. Registered all Interceptors first
5. Registered my ICustomer with the Notify Changed and Target Is CommonDataStore.
6. Create a new ViewModel and Bind the CurrentCustomer.Name to a Label like this:
CurrentCustomer = container.Resolve<icustomer>();

<label content="{Binding Path=CurrentCustomer.Name}"/>

When I run the application the Label is empty.
When I step through the application in Debug mode the Object has a name and is working correctly. So why isn't WPF binding correct?

So I removed the TargetIsCommonDatastore and created a concrete type.
Same problem, object is filling 100% but binding isn't working

Next I removed the Notify Interceptor and still used windsor for my IoC.
WPF now displays and the object is 100%

Seems like WPF doesn't like the object windsor has created when using interceptors.

I still haven't tried using the Proxy Generator Directly.

When I have my code back to how it was i'll post it. :D just through this information might help :D

Thanks :D

José F. Romaniello dijo...

Sorry for the delay. I will help you if you post the code somewhere..
Here you have my example (a little out-date) checkout with svn:
http://digg.com/u1HtYW
I have a full example working, "Chinook Media Manager". See my other posts.

I must be going crazy Jose.

It turns out that the DLL Castle.DynamicProxy2 was an old version. After using the references from my project everything is fine :D.

Even added the DataErrorBehaviour to my solution and that works 100% :D

Thank you.

José F. Romaniello dijo...

You are welcome.

Publicar un comentario en la entrada