AOP in Action - Dirty Tracking using Unity Interception
In most enterprise applications, you end up by having a lot of cross-cutting concerns throughout your application. Most of the time, you have, for example, infrastructure code for doing logging, dirty tracking, lazy loading, caching, etc. Often infrastructure code is scattered over a number of modules making it harder to maintain and understand. Aspect-oriented programming (AOP) solves this problem by allowing the programmer to express cross-cutting concerns in stand-alone modules called aspects.
When you apply domain-driven design, you often have the need for dirty tracking, check on a business entity whether the entity has changed (is dirty) or not. There are a lot of different implementations for dirty tracking, but in this article we are doing it by using an interface called IDirty
that contains a property called IsDirty
and the setter properties of your entity object calls the IsDirty
if the value has changed.
public interface IDirty
{
bool IsDirty { get; set; }
}
public class Customer : IDirty
{
private string _firstName;
private string _lastName;
public bool IsDirty { get; set; }
public string FirstName
{
get { return _firstName; }
set
{
if (value != _firstName)
IsDirty = true;
_firstName = value;
}
}
public string LastName
{
get { return _lastName; }
set
{
if (value != _lastName)
IsDirty = true;
_lastName = value;
}
}
}
As you can see from the example, if you have a lot of properties, you end up by having a lot of infrastructure code and repeated code in each setter. What we want to achieve, is to annotate the setter property through an attribute called DirtyAttribute
and that the dirty logic is centralized in one module.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class DirtyAttribute : System.Attribute
{
}
public interface IDirty
{
bool IsDirty { get; set; }
}
public class Customer : IDirty
{
public bool IsDirty { get; set; }
public virtual string FirstName { get; [Dirty] set; }
public virtual string LastName { get; [Dirty] set; }
}
Note that these are the only dependencies needed inside your domain model project. Now we need to make clear to Unity that we need to intercept the DirtyAttribute
and execute some logic. With unity we denote a policy, a policy contains a set of matching rules and a set of call handlers. A matching rule will denote on which point the handler must be executed and the handler is the logic. In AOP this is called respectively a pointcut and an advice, the combination of the two is called an aspect. First, we are going to create our DirtyHandler
by implementing the ICallHandler
that resides in the Microsoft.Unity.Interception
assembly.
public class DirtyHandler : ICallHandler
{
public int Order { get; set; }
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Advise(input.Target, input.MethodBase, input.Arguments[0]);
return getNext()(input, getNext);
}
private 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;
}
}
private 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);
}
}
Note that in the DirtyHandler
we did not specify when the dirty logic is executed, this is done by a set of matching rules that implement the IMatchingRule
interface. In our case we have three criterias, first that it can only be applied to a setter property, secondly that the setter property must be annotated with the DirtyAttribute
and finally that the class must implement the IDirty
interface. In Unity we have predefined rules that we can use, in our case we can use the PropertyMatchingRule
and the CustomAttributeMatchingRule
. To detect that a particular instance implements a given type, I created the InstanceOfMatchingRule
.
public class InstanceOfMatchingRule : IMatchingRule
{
private readonly Type _type;
public InstanceOfMatchingRule(Type type)
{
_type = type;
}
public bool Matches(System.Reflection.MethodBase member)
{
return _type.IsAssignableFrom(member.DeclaringType);
}
}
Now we are ready to configure our container for interception. Note that the interception mechanism inside Unity is implemented as an extension to the Unity block. Below we configure a policy named "DirtyPolicy" that executes the handler named "DirtyHandler" if it matches all three matching rules (PropertyMatchingRule
, CustomAttributeMatchingRule
and InstanceOfMatchingRule
). In this example, we denote that the type Customer
is configured with the VirtualMethodInterceptor
. Note that this type of interception only works if your methods/properties are marked as virtual, which is the case for our Customer
type (see Customer.cs
).
// Start Configuration
IUnityContainer container = new UnityContainer()
.AddNewExtension<Interception>()
.RegisterInstance<ICallHandler>("DirtyHandler", new DirtyHandler());
container.Configure<Interception>()
.SetInterceptorFor<Customer>(new VirtualMethodInterceptor())
.AddPolicy("DirtyPolicy")
.AddMatchingRule(new PropertyMatchingRule("*", PropertyMatchingOption.Set))
.AddMatchingRule(new CustomAttributeMatchingRule(typeof(DirtyAttribute), true))
.AddMatchingRule(new InstanceOfMatchingRule(typeof(IDirty)))
.AddCallHandler("DirtyHandler");
// End Configuration
var customer = container.Resolve<Customer>();
var firstname = customer.FirstName;
Debug.Assert(!customer.IsDirty);
customer.FirstName = "Piet";
Debug.Assert(customer.IsDirty);
The configuration part can also be done through a configuration file. Unfortunately, there is no schema available, this means that there is no validation and intellisense inside your Visual Studio! Especially to describe the matching rules through xml, I had to introduce some extra converters that implement the TypeConverter class. This enables you to convert to any kind of object started from a string. I introduced the PropertyMatchingOptionConverter
and GetTypeConverter
.
public class PropertyMatchingOptionConverter : System.ComponentModel.TypeConverter
{
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return (PropertyMatchingOption)Enum.Parse(typeof(PropertyMatchingOption), value.ToString());
}
}
public class GetTypeConverter : System.ComponentModel.TypeConverter
{
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return Type.GetType(value.ToString());
}
}
Below you find how you use Unity with a configuration file and you find also the resulting xml file that does exactly the same as the above-mentioned version.
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers["Default"].Configure(container);
var customer = container.Resolve<Customer>();
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="....UnityConfigurationSection, ..."/>
</configSections>
<unity>
<typeAliases>
<typeAlias alias="bool" type="System.Boolean, mscorlib" />
<typeAlias alias="string" type="System.String, mscorlib" />
<typeAlias alias="int" type="System.Int32, mscorlib" />
<typeAlias alias="Type" type="System.Type, mscorlib" />
<typeAlias alias="GetTypeConverter" type="..."/>
<typeAlias alias="PropertyMatchingOptionConverter" type="..."/>
<typeAlias alias="InstanceOfMatchingRule" type="..."/>
<typeAlias alias="DirtyHandler" type="..."/>
<typeAlias alias="VirtualMethodInterceptor" type="..."/>
<typeAlias alias="CustomAttributeMatchingRule" type="..."/>
<typeAlias alias="PropertyMatchingRule" type="..."/>
<typeAlias alias="PropertyMatchingOption" type="..."/>
<typeAlias alias="DirtyAttribute" type="..."/>
<typeAlias alias="IDirty" type="..."/>
<typeAlias alias="Customer" type="..."/>
</typeAliases>
<containers>
<container name="Default">
<extensions>
<add type="....Interception, ..."/>
</extensions>
<extensionConfig>
<add name="interception" type="....InterceptionConfigurationElement, ...">
<policies>
<policy name="DirtyPolicy">
<matchingRules>
<matchingRule name="SetPropertyRule" type="PropertyMatchingRule">
<injection>
<constructor>
<param name="propertyName" parameterType="string">
<value value="*"/>
</param>
<param name="option" parameterType="PropertyMatchingOption">
<value value="Set" type="PropertyMatchingOption" typeConverter="PropertyMatchingOptionConverter"/>
</param>
</constructor>
</injection>
</matchingRule>
<matchingRule name="DirtyAttributeRule" type="CustomAttributeMatchingRule">
<injection>
<constructor>
<param name="attributeType" parameterType="Type">
<value value="...DirtyAttribute, ..." type="Type" typeConverter="GetTypeConverter"/>
</param>
<param name="inherited" parameterType="bool">
<value value="true" type="bool"/>
</param>
</constructor>
</injection>
</matchingRule>
<matchingRule name="InstanceOfIDirtyRule" type="InstanceOfMatchingRule">
<injection>
<constructor>
<param name="type" parameterType="Type">
<value value="...IDirty, ..." type="Type" typeConverter="GetTypeConverter"/>
</param>
</constructor>
</injection>
</matchingRule>
</matchingRules>
<callHandlers>
<callHandler name="DirtyHandler" type="DirtyHandler"/>
</callHandlers>
</policy>
</policies>
<interceptors>
<interceptor type="VirtualMethodInterceptor">
<key type="Customer"/>
</interceptor>
</interceptors>
</add>
</extensionConfig>
</container>
</containers>
</unity>
</configuration>
The source code of this article can be downloaded here