sharing about .NET and technology RSS 2.0
# Saturday, January 10, 2009

In Part 1, we used the Unity block to intercept properties for dirty tracking. Our target class needed to implement the IDirty interface, and the setter properties (annotated with the Dirty attribute) assigned the Dirty flag (of IDirty) when the value has changed. Take for example the following classes:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class DirtyAttribute : System.Attribute
{
}

public interface IDirty
{
    bool IsDirty { get; set; }
}

public class Customer
{
    public virtual string FirstName { get; [Dirty] set; }
    public virtual string LastName { get; [Dirty] set; }
}

Note that in contrast to Part 1, the Customer object doesn’t implement the IDirty interface. In this example we are going to use a mixin for dirty tracking, a mixin is a class that provides a certain functionality to be inherited by a subclass, but is not meant to stand alone. That’s exactly what we want to achieve, that is to provide extra functionality, namely dirty tracking, to our Customer object.

We simply have to implement the IDirty interface once and for all and call it for example DirtyMixin.

[Serializable]
public class DirtyMixin : IDirty
{
   public bool IsDirty { get; set; }
}

As interception and mixin mechanism we use Castle’s DynamicProxy, the latest build can be found here. To intercept our (dirty) properties we need to implement an interface called IInterceptor. In the implementation we write the code that need to be executed (advice) at particular points (pointcut) in the program. Note that with Unity we have the notion of IMatchingRule for defining Pointcuts, whereas in DynamicProxy we have to do it manually.

public class DirtyInterceptor : Castle.Core.Interceptor.IInterceptor
{
    public void Intercept(Castle.Core.Interceptor.IInvocation invocation)
    {
        if (PointCut(invocation.Proxy, invocation.Method))
        {
            Advise(invocation.Proxy, invocation.Method, invocation.GetArgumentValue(0));
        }

        invocation.Proceed();
    }

    bool PointCut(object target, MethodInfo methodInfo)
    {
        if (IsSetter(methodInfo) && target is IDirty)
        {
            object[] dirtyAttributes = methodInfo.GetCustomAttributes(typeof(DirtyAttribute), true);
            return (dirtyAttributes != null && dirtyAttributes.Length > 0);
        }
        else
        {
            return false;
        }
    }

    void Advise(object target, MemberInfo methodInfo, object value)
    {
        string propertyName = methodInfo.Name.Substring("set_".Length);
        PropertyInfo info = target.GetType().GetProperty(propertyName);

        if (info != null)
        {
            object oldValue = info.GetValue(target, null);

            if (!IsEqual(value, oldValue))
                ((IDirty)target).IsDirty = true;
        }
    }

    bool IsSetter(MethodInfo methodInfo)
    {
        return (methodInfo.Name.StartsWith("set_")) && (methodInfo.GetParameters().Length == 1);
    }

    bool IsEqual(object valueX, object valueY)
    {
        if (valueX == null && valueY != null)
            return false;

        if (valueX != null && valueY == null)
            return false;

        if (valueX == null && valueY == null)
            return true;

        return valueX.Equals(valueY);
    }
}

To generate our proxy we use the ProxyGenerator class that reside in the Castle.DynamicProxy2 assembly. Adding a mixin is done through the ProxyGenerationOptions which is passed to the CreateClassProxy method.

var generator = new ProxyGenerator();

ProxyGenerationOptions options = new ProxyGenerationOptions();
options.AddMixinInstance(new DirtyMixin());

Customer customer = generator.CreateClassProxy(typeof(Customer), options, new DirtyInterceptor()) as Customer;

var firstname = customer.FirstName;
Debug.Assert(!((IDirty)customer).IsDirty);
customer.FirstName = "Piet";
Debug.Assert(((IDirty)customer).IsDirty);

The customer object that we receive from the generator is a proxy through subclassing and you will see that it now implements the IDirty interface.

MixinReflector

You can use the PersistentProxyBuilder to save the generated assembly. It renders an assembly called CastleDynProxy2.dll. Below you find an example how you can use PersistentProxyBuilder.

var generator = new ProxyGenerator(new PersistentProxyBuilder());

ProxyGenerationOptions options = new ProxyGenerationOptions();
options.AddMixinInstance(new DirtyMixin());

Customer customer = generator.CreateClassProxy(typeof(Customer), options, new DirtyInterceptor()) as Customer;

string proxyAssemblyPath = ((PersistentProxyBuilder)generator.ProxyBuilder).SaveAssembly();

var firstname = customer.FirstName;
Debug.Assert(!((IDirty)customer).IsDirty);
customer.FirstName = "Piet";
Debug.Assert(((IDirty)customer).IsDirty);

You can go a bit further and use an extension method that enables you to ask for a certain service. Internally it will simply try to cast to the given interface.

public static class ObjectExtensions
{
    public static T GetService<T>(this object instance) where T : class
    {
        return instance as T;
    }
}

Given the GetService extension method, we use our customer as follow

IDirty dirty = customer.GetService<IDirty>();
if (dirty != null)
{
    var firstname = customer.FirstName;
    Debug.Assert(!dirty.IsDirty);
    customer.FirstName = "Piet";
    Debug.Assert(dirty.IsDirty);
}

The source code of this article can be downloaded here (zipfile, 259.19 KB)

Saturday, January 10, 2009 1:17:43 AM (Romance Standard Time, UTC+01:00) -  # -  Comments [1] -
.NET | AOP
Navigation
Archive
<January 2009>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Christoph De Baene
Sign In
Statistics
Total Posts: 176
This Year: 2
This Month: 0
This Week: 0
Comments: 249
All Content © 2010, Christoph De Baene
DasBlog theme 'Business' created by Christoph De Baene