José F. Romaniello

Las aventuras y desventuras de un codificador.

I have been working over the last three weeks on a project with WPF. You can see my last four post about the subject:

This time I will go with the INotifyCollectionChanged interface. 
Given the following domain:

public class Store : IStore
{ public virtual string Name { get; set; } [NotifyOnChange] public virtual IList<IProduct> Products { get; private set; } public Store() { Products = new List<IProduct>(); } public virtual void AddProduct(IProduct product) { product.Store = this; Products.Add(product); } public virtual void RemoveProduct(IProduct product) { product.Store = null; Products.Remove(product); } }

Note: NotifyOnChangeAttribute is mine. 
The mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="uNHAddIns.Examples.CustomInterceptor"
                   namespace="uNHAddIns.Examples.CustomInterceptor.Domain">
  <class name="Store" >

    <id type="guid">
            <generator class="guid"/>
        </id>
    
        <property name="Name"/>
    
    <bag name="Products" cascade="all" inverse="true">
      <key column="StoreId" />
      <one-to-many class="IProduct" />
    </bag>
    
    </class>
</hibernate-mapping>
We need to meet the following test:

[Test]
public void NotifyOnChangeProperty_Should_Implement_INotifyCollectionChanged()
{
    var store = container.Resolve<IStore>();
    typeof(INotifyCollectionChanged).IsAssignableFrom(store.Products.GetType());
    typeof(INotifyPropertyChanged).IsAssignableFrom(store.Products.GetType());
}
[Test]
public void add_should_raise_collectionchanged()
{
    var store = container.Resolve<IStore>();
    IProduct newProduct = CreateNewProduct();
    bool eventWasRaised = false;

    ((INotifyCollectionChanged) store.Products)
        .CollectionChanged += (sender, args) =>
        {
            eventWasRaised = true;
            args.Action.Should().Be.EqualTo(NotifyCollectionChangedAction.Add);
            args.NewItems.Count.Should().Be.EqualTo(1);
            args.NewItems[0].Should().Be.EqualTo(newProduct);
        };


    store.AddProduct(newProduct);

    eventWasRaised.Should().Be.True();
}

I won't show you the full set of test for now.

Implementing the dynamicProxy interceptor:

public class CollectionPropertyInterceptor : IInterceptor
{

    private readonly IDictionary<string, object> _interceptedCollections
        = new Dictionary<string, object>();

    private object[] AttributeLookup(Type type, Type attribType, string propertyName) 
    {}

    #region IInterceptor Members

    public void Intercept(IInvocation invocation)
    {
        if (!invocation.MethodInvocationTarget.Name.StartsWith("get_"))
        {
            invocation.Proceed();
            return;
        }

        string propName = invocation.MethodInvocationTarget.Name.Substring(4);

        if (AttributeLookup(invocation.InvocationTarget.GetType(),
                            typeof (NotifyOnChangeAttribute),
                            propName).Length != 1)
        {
            invocation.Proceed();
            return;
        }


        object interceptedCollection;
        _interceptedCollections.TryGetValue(propName, out interceptedCollection);

        if (interceptedCollection != null)
        {
            invocation.ReturnValue = interceptedCollection;
        }
        else
        {
            invocation.Proceed();

            var itemType = invocation.ReturnValue.GetType()
                                     .GetGenericArguments()[0];

            var observableType = typeof (ObservableCollection<>)
                                        .MakeGenericType(itemType);

            object newInterceptedCollection = 
                Activator.CreateInstance(observableType, invocation.ReturnValue);

            invocation.ReturnValue = newInterceptedCollection;

            _interceptedCollections.Add(propName, newInterceptedCollection);
        }
    }

    #endregion
}
Ok, what I’m doing here? Let me explain, this interceptor intercept the “get_Products” method call, and enwrap the return type into an ObservableCollection. Then I store this collection in a dictionary for subsequent calls.

You need to register the Store entity as follows:

container.Register(Component.For<Store>()
                       .Interceptors(new InterceptorReference(
                           typeof (CollectionPropertyInterceptor))).Anywhere
                       .ImplementedBy<Store>()
                       .EnableNhibernateEntityCompatibility()
                       .LifeStyle.Transient);

container.Register(Component.For<IStore>()
                       .UsingFactoryMethod(kernel => kernel.Resolve<Store>())
                       .LifeStyle.Transient);
Using the EnhancedBytecode provider from unhaddins this test also works:

[Test]
public void add_should_raise_collectionchanged_on_nontrascient()
{
    var id = CreateFooStore();
    IProduct newProduct = CreateNewProduct();

    using (var session = sessions.OpenSession())
    using(session.BeginTransaction())
    {
        var store = session.Get<Store>(id);
        bool eventWasRaised = false;
        ((INotifyCollectionChanged)store.Products)
            .CollectionChanged += (sender, args) =>
                {
                    eventWasRaised = true;
                    
                    args.Action
                        .Should().Be.EqualTo(NotifyCollectionChangedAction.Add);
                    
                    args.NewItems.Count
                        .Should().Be.EqualTo(1);
                    
                    args.NewItems[0]
                        .Should().Be.EqualTo(newProduct);
                };


        store.AddProduct(newProduct);

        eventWasRaised.Should().Be.True();
    }

}

I'm going to merge these functions into one package with everything you need to work with wpf, nhibernate and the aforementioned interfaces. This package will be available at unhaddins.

| More

0 comentarios:

Publicar un comentario en la entrada