Change Tracking with StructureMap and DynamicProxy

January 5, 2019 by Michael

Somehow you’ve found yourself in a position where you need to implement change tracking (a.k.a dirty flag). If your model has several classes or classes with many properties, this becomes a tedious task. Being the SOLID developer you no doubt are, you’ve decided to automate this as much as possible.

Looking at change tracking from afar, it becomes apparent that it falls into the bucket of cross-cutting concerns. This is an area where aspect orient programming (AOP) excels. With AOP you have two options: compile-time or run-time injection. PostSharp is the leader in the compile-time realm; however, getting the most out if it will cost you. DynamicProxy (part of the Castle Project) is a leader in the run-time injection space.

The last piece of this automation puzzle is your IoC container (in my case, StructureMap). It’s tempting to integrate DynamicProxy into your IoC configuration. If you’re leaning that way, I strongly urge you to consider whether or not it is necessary. A model made up of simple POCO objects does not need to be instantiating via an IoC container. There are no dependencies to inject. There is no benefit to using your IoC container.

More complex models (perhaps you’re using DDD) will benefit from being created via your IoC container. To accommodate this use case (or your strong desire to use your IoC container), I’ll provide an example that integrates DynamicProxy with StructureMap.

More often than not, this probably isn’t the right choice.

As usual, the code for this article can be found on GitHub.


Change Tracking with AOP and DynamicProxy

Implementing AOP requires using an interceptor. We want an interceptor to be called every time an object’s set method is called for a given property. DynamicProxy allows you to generate proxy classes with interceptors. Therefore we can easily use DynamicProxy to implement change tracking.

The Model

The first thing we need is a base class for your POCO model objects. This will centralize our change tracking and help keep our code DRY:

public abstract class Entity : IChangeTrackable
{
	#region Properties
	public virtual Guid Id { get; set; }
	#endregion

	#region Wrapper Properties
	public virtual bool IsDirty => ChangedValues.Any();
	#endregion

	#region Navigation Properties
	public virtual List ChangedValues { get; set; } = new List();
	#endregion

	#region Utility Methods
	public void MarkAsClean()
	{
		ChangedValues.Clear();
	}

	public string GetChangeLog()
	{
		return ChangedValues
			.OrderBy(cv => cv.ChangedAt)
			.Select(cv => cv.ToString())
			.Aggregate(
				(Index:0,Desc:$"{GetType().Name} Change Log:"), 
				(a, c) => (++a.Index, $"{a.Desc}{Environment.NewLine}{a.Index,2}. {c}"))
			.Desc;
	}
	#endregion
}

When using DynamicProxy for change tracking you must use virtual properties. A proxy class generated by DynamicProxy will not apply your interceptors to properties that do not use the virtual keyword.

In the above source code you’ll notice that the Entity class implements the IChangeTrackable interface. You can find the code for that interface in the repository. The GetChangeLog() method is there primarily for demo purposes. You can remove it if you do not have a need for it or modify it to suit your needs.

Next we need a simple class to represent our model:

namespace PocoChangeTracking.Model
{
    public class Person : Entity
    {
        #region Properties
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual int Age { get; set; }
        #endregion
    }
}

Using DynamicProxy

In order to handle the change tracking we need to create an interceptor class that implements the IIntercepter interface. Be careful not to implement the IIntercepter interface that is part of StructureMap. Below you will find my implementation of the Intercept() method (you can find the complete source code in the repository):

public void Intercept(IInvocation invocation)
{
	Match match = _setterRegex.Match(invocation.Method.Name);

	// we only want to intercept set calls
	if(!match.Success)
	{
		invocation.Proceed();
		return;
	}

	IChangeTrackable obj = (IChangeTrackable)invocation.InvocationTarget;
	string property = match.Groups["property"].Value;
	MethodInfo getterMethod = invocation.TargetType.GetMethod($"get_{property}");

	// get the old and new values
	object oldValue = getterMethod != null ? getterMethod.Invoke(invocation.InvocationTarget, null) : "?";
	object newValue = invocation.Arguments[0];

	// check to see if something changed
	if(!AreEqual(oldValue, newValue))
	{
		if(obj.ChangedValues == null)
			obj.ChangedValues = new List();

		obj.ChangedValues.Add(
			new ChangedValue
			{
				PropertyName = property,
				OldValue = oldValue,
				NewValue = newValue,
				ChangedAt = DateTime.Now
			});
	}

	invocation.Proceed();
}

The method above does the bulk of the change tracking for us. Whenever a class’s setter is called for any property the interceptor checks to see if the value has actually changed. As long as the new value is different from the old value we add a new item to the ChangedValues property. As a result, we have nice record of what has changed.

Finally, we need to call one of DynamicProxy’s many Create methods using the interceptor we defined above. To make things a bit easier, I like to wrap this code in something a bit more user friendly:

public class ChangeTracker : IChangeTracker
{
	#region Member Variables
	private readonly ProxyGenerator _proxy = new ProxyGenerator();
	#endregion

	#region IChangeTracker Implimentation
	public TProxy GenerateProxy<TProxy>() where TProxy : class, IChangeTrackable
	{
		return _proxy.CreateClassProxy<TProxy>(new ChangeTrackingInterceptor());
	}

	public TProxy GenerateProxyFrom<TProxy>(TProxy target) where TProxy : class, IChangeTrackable
	{
		return _proxy.CreateClassProxyWithTarget(target, new ChangeTrackingInterceptor());
	}
	#endregion
}

DynamicProxy’s CreateClassProxy will create a new object for you based on the type you provide. CreateClassProxyWithTarget will return a proxy object built around an existing object. There are several overloads for these methods. Some are generic and some are not. There are also overloads that allow you to pass in constructor arguments (should you need to do that for some reason).


Change Tracking with DynamicProxy and StructureMap

I put a lot of effort into integrating DynamicProxy into my StructureMap configuration before finally realizing there was no need to do so. That said, your use case might be different than mine. As a result, you may still want to know how to make this happen.

StructureMap does not have a built in way to register interceptors for concrete classes. Ideally you would be able to configure StructureMap to apply interceptors to all classes that are derived from a base class. For example, Person (which is derived from Entity). OnCreationForAll allows you to take some action on created objects before they are returned by the container; however, you cannot replace that object with a proxy.

Setting Up StructureMap

You can manually configure interceptors for all objects you want to handle change tracking; however, that gets tedious to maintain. DecorateAllWith is another method that gets you close but not quite all of the way. The solution to this problem is to implement your own registration convention:

public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention
{
	#region Member Variables
	private readonly ProxyGenerator _proxy = new ProxyGenerator();
	private readonly Type _genericInterceptorPolicyType = typeof(InterceptorPolicy<>);
	private readonly Type _genericInterceptorType = typeof(ChangeTrackingFuncInterceptor<>);
	private readonly Type _changeTrackableType = typeof(IChangeTrackable);
	#endregion

	#region Base Class Overrides
	public override void ScanTypes(TypeSet types, Registry registry)
	{
		types.FindTypes(TypeClassification.Concretes).Where(type => type.HasConstructors()).ToList().ForEach(type =>
		{
			Type pluginType = FindPluginType(type);

			if(pluginType == null)
				return;

			registry.AddType(pluginType, type);

			if(_changeTrackableType.IsAssignableFrom(pluginType))
				registry.Policies.Interceptors(CreatePolicy(pluginType));

			ConfigureFamily(registry.For(pluginType));
		});
	}

	public virtual Type FindPluginType(Type concreteType)
	{
		return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == $"I{concreteType.Name}");
	}
	#endregion

	#region Utility Methods
	private IInterceptorPolicy CreatePolicy(Type pluginType)
	{
		return (IInterceptorPolicy)Activator.CreateInstance(
			_genericInterceptorPolicyType.MakeGenericType(pluginType), 
			CreateInterceptor(pluginType), null);
	}

	private IInterceptor CreateInterceptor(Type pluginType)
	{
		return (IInterceptor)Activator.CreateInstance(
			_genericInterceptorType.MakeGenericType(pluginType), 
			_proxy);
	}
	#endregion
}

The custom registration convention above takes care of adding an interceptor policy for any plugin type that implements the IChangeTrackable interface. In this case the interceptor ChangeTrackingFuncIntercepter<> is derived from StructureMap’s FuncInterceptor<>. This custom interceptor will take care of calling DynamicProxy to create a proxy object that has our change tracking interceptor:

public class ChangeTrackingFuncInterceptor<T> : FuncInterceptor<T> where T : class
{
	#region Constructor
	public ChangeTrackingFuncInterceptor(ProxyGenerator proxy)
		: base(x => proxy.CreateInterfaceProxyWithTarget(x, new ChangeTrackingInterceptor()))
	{
	}
	#endregion
}

Notice that this time we are using CreateInterfaceProxyWithTarget instead of CreateClassProxyWithTarget. This is to accommodate the way StructureMap works with interfaces instead of concrete types.

Modifying the Model

We need to modify our model so that we can create an instance of the class via StructureMap’s GetInstance<>() method. You can create concrete objects via StructureMap; however, the ChangeTrackingFuncInterceptor will not get called unless you are working with a plugin type (interface):

public interface IPerson : IEntity
{
	string FirstName { get; set; }
	string LastName { get; set; }
	int Age { get; set; }
}

public class Person : Entity, IPerson
{
	#region Properties
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public int Age { get; set; }
	#endregion
}

Notice that we had to create an interface and then implement that interface in our model. This is because of the way StructureMap is intended to be used and one of the reasons I do not recommend coupling change tracking with your IoC container.

You can find all of the changes to model in the repository for this article’s source code.

Configuring StructureMap

Finally, it’s time to configure StructureMap. The majority of this work was done in the custom registration convention. As a result, we only need to configure the StructureMap container to use it:

_container.Configure(_ =>
{
	_.Scan(s =>
	{
		s.TheCallingAssembly();
		s.AddAllTypesOf<IEntity>();
		s.Convention<DefaultConventionWithProxyScanner>();
	});
});

After that, you will be able to create instances of your model as follows:

IPerson person = _container.GetInstance();

Conclusion…

Regardless of which path you’ve taken above you should see the following output after compiling and running the example code:

Change Tracking results
Change Tracking results

Ultimately I ended up going a different route in my project and did not use change tracking. However, putting this code together was an interesting challenge. As a result, I decided to share it with you. I hope that you found this post somewhat useful.

As usual, if you know of a way I could improve this code, please leave a comment and I’ll see what I can do. Reminder, you can find the source code for this post on GitHub.


Discussion


Leave a Reply

Your email address will not be published. Required fields are marked *