This article discusses the Castle opensource library that can be useful for dynamic proxy generation and interception of calls to the proxy methods and properties.
Introduction
Good day, and welcome to my new article.
I would like to present to you a powerful opensource library called Castle DynamicProxy that gives you the ability to intercept calls to your model classes using proxies. The proxies are generated dynamically at runtime, so you don't need to change your model classes to start intercepting their properties or methods.
By the way, it is not a good design decision to have methods and any other logic in model classes.
Let me start by defining a user story.
User Story #3: Interception of Model Calls
- Add the ability to track changes in a model class
- Store a sequence of calls in a collection attached to the model class
Implementation - Model
Let's create a new Visual Studio project, but this time let's use the .NET Core class library and check how our code works using the xUnit testing framework. We add a new main project and name it DemoCastleProxy
:
When the main project is created, add a new xUnit project DemoCastleProxyTests
to the solution, we will need it to check how our proxies demo works:
Our user story says that we need to have a collection for change tracking inside the model class, so we start from an interface that defines this collection. If we want to create more model classes, we can reuse this interface. Let’s add a new interface to the main project:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public interface IModel
{
List<string> PropertyChangeList { get; }
}
}
Now we can add the model class:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public class PersonModel : IModel
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime? BirthDate { get; set; }
public virtual List<string> PropertyChangeList { get; set; } =
new List<string>();
}
}
As you can see, PersonModel
implements interface IModel
and PropertyChangeList
is initialized every time we create an instance of PersonModel
. You can also see that I marked all the properties using the virtual
keyword. This is an important part of the model definition.
Castle DynamicProxy can only intercept virtual properties, using polymorphism to achieve that. Actually, the Castle proxy engine works in such a way by creating inherited classes from your model classes and it overrides all virtual properties. When you call the overridden property, it executes an interceptor first, and only then it hands over your call to a base model class.
You can try to do this yourself by creating a proxy manually. It may look like this:
public class PersonModelProxy : PersonModel
{
public override string FirstName
{
get
{
Intercept("get_FirstName", base.FirstName);
return base.FirstName;
}
set
{
Intercept("set_FirstName", value);
base.FirstName = value;
}
}
private void Intercept(string propertyName, object value)
{
}
}
but Castle does it for us at runtime, in a generic way and proxy classes will have the same properties as the original model classes - thus we will only need to maintain our model classes.
Implementation - Proxy Factory
Many of you know that Proxy
is a structural design pattern, and I always recommend to developers to read about OOP design and especially read the Gang of Four Design Patterns book.
I will use another design pattern, Factory Method
, to implement the generic logic for a proxy generation.
But before all that, we need to add Castle.Core
NuGet package to the main project:
Now, I will start from an interface:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public interface IProxyFactory
{
T GetModelProxy<T>(T source) where T : class;
}
}
and will add its implementation:
using Castle.DynamicProxy;
using System;
namespace DemoCastleProxy
{
public class ProxyFactory : IProxyFactory
{
private readonly IProxyGenerator _proxyGenerator;
private readonly IInterceptor _interceptor;
public ProxyFactory(IProxyGenerator proxyGenerator, IInterceptor interceptor)
{
_proxyGenerator = proxyGenerator;
_interceptor = interceptor;
}
public T GetModelProxy<T>(T source) where T : class
{
var proxy = _proxyGenerator.CreateClassProxyWithTarget(source.GetType(),
source, new IInterceptor[] { _interceptor }) as T;
return proxy;
}
}
}
Using the interface gives us the flexibility to have several implementations of IProxyFactory
and choose one of them in the Dependency Injection registration at startup.
We use the CreateClassProxyWithTarget
method from the Castle framework to create a proxy object from the supplied model object.
Now we need to implement an interceptor that will be passed to the ProxyFactory
constructor and supplied to the CreateClassProxyWithTarget
method as a second parameter.
The interceptor code will be:
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace DemoCastleProxy
{
public class ModelInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var method = invocation.Method.Name;
if (method.StartsWith("set_"))
{
var field = method.Replace("set_", "");
var proxy = invocation.Proxy as IModel;
if (proxy != null)
{
proxy.PropertyChangeList.Add(field);
}
}
}
}
}
Each call to a proxy object will execute the Intercept
method. In this method, we check that if a property setter is called, then we add the name of the called property to PropertyChangeList
.
Now we can compile our code.
Implementation - Unit Tests
We need to run our code to make sure that it works, and one of the possible ways is to create a unit test. It will be much faster than the creation of an application that will use our proxies.
Here at Pro Coders, we pay close attention to unit testing, because unit tested code will work in an application too. Furthermore, if you refactor code covered by unit tests, you can be assured that after the refactoring, your code will work correctly if unit tests pass.
Let's add the first test:
using Castle.DynamicProxy;
using DemoCastleProxy;
using System;
using Xunit;
namespace DemoCastleProxyTests
{
public class DemoTests
{
private IProxyFactory _factory;
public DemoTests()
{
_factory = new ProxyFactory(new ProxyGenerator(), new ModelInterceptor());
}
[Fact]
public void ModelChangesInterceptedTest()
{
PersonModel model = new PersonModel();
PersonModel proxy = _factory.GetModelProxy(model);
proxy.FirstName = "John";
Assert.Single(model.PropertyChangeList);
Assert.Single(proxy.PropertyChangeList);
Assert.Equal("FirstName", model.PropertyChangeList[0]);
Assert.Equal("FirstName", proxy.PropertyChangeList[0]);
}
}
}
In xUnit, we need to mark each test method with a [Fact]
attribute.
In the DemoTests
constructor, I created _factory
and supplied a new instance of ModelInterceptor
as a parameter, though in an application we will use Dependency Injection for ProxyFactory
instantiation.
Now, in every method of our test class, we can use _factory
to create proxy objects.
My test simply creates a new model object, then it generates a proxy object from the model. Now any calls to the proxy object should be intercepted and PropertyChangeList
will be filled.
To run your unit test, put your cursor to any part of the test method body and click [Ctrl+R]+[Ctrl+T]. If hotkey doesn't work, use the context menu or Test Explorer window.
If you put a breakpoint, you can see the values of the variables we used:
As you can see, we changed the FirstName
property and it has appeared in the PropertyChangeList
.
Implementation - Rule Engine
Let's make this exercise more interesting and use our interceptor to execute a rule attached to a model property.
We will use a C# attribute to attach a type of rule that the interceptor should execute, let's create it:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public class ModelRuleAttribute : Attribute
{
public Type Rule { get; private set; }
public ModelRuleAttribute(Type rule)
{
Rule = rule;
}
}
}
Having the name of the property allows the interceptor to use reflection to read attributes attached to the property and execute the rule.
To make it elegant, we will define the IModelRule
interface:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public interface IModelRule
{
void Execute(object model, string fieldName);
}
}
and our rule will implement it, like so:
using System;
using System.Collections.Generic;
using System.Text;
namespace DemoCastleProxy
{
public class PersonRule : IModelRule
{
public void Execute(object model, string fieldName)
{
var personModel = model as PersonModel;
if (personModel != null && fieldName == "LastName")
{
if (personModel.FirstName?.ToLower() == "john" &&
personModel.LastName?.ToLower() == "lennon")
{
personModel.BirthDate = new DateTime(1940, 10, 9);
}
}
}
}
}
The rule will check if the changed field is LastName
(only when the LastName
setter is executed) and if FirstName
and LastName
have the values "John Lennon
" in the lower or upper case, then it will set the BirthDate
field automatically.
Now we need to attach the rule to our model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace DemoCastleProxy
{
public class PersonModel : IModel
{
public virtual string FirstName { get; set; }
[ModelRule(typeof(PersonRule))]
public virtual string LastName { get; set; }
public virtual DateTime? BirthDate { get; set; }
public virtual List<string> PropertyChangeList { get; set; } =
new List<string>();
}
}
You can see the [ModelRule(typeof(PersonRule))]
attribute added above the LastName
property and we supplied to .NET the type of the rule.
We also need to modify ModelInterceptor
adding the functionality to execute rules, the new code added after the // rule execution
comment:
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace DemoCastleProxy
{
public class ModelInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var method = invocation.Method.Name;
if (method.StartsWith("set_"))
{
var field = method.Replace("set_", "");
var proxy = invocation.Proxy as IModel;
if (proxy != null)
{
proxy.PropertyChangeList.Add(field);
}
var model = ProxyUtil.GetUnproxiedInstance(proxy) as IModel;
var ruleAttribute = model.GetType().GetProperty(field).GetCustomAttribute
(typeof(ModelRuleAttribute)) as ModelRuleAttribute;
if (ruleAttribute != null)
{
var rule = Activator.CreateInstance(ruleAttribute.Rule) as IModelRule;
if (rule != null)
{
rule.Execute(invocation.Proxy, field);
}
}
}
}
}
}
The interceptor simply reads custom attributes of the fired property using reflection, and if it finds a rule attached to the property, it creates the rule instance and executes it.
The last bit now is to check that our rule engine works, let's create another unit test for that in our DemoTests
class:
[Fact]
public void ModelRuleExecutedTest()
{
var model = new PersonModel();
var proxy = _factory.GetModelProxy(model);
proxy.FirstName = "John";
Assert.NotEqual("1940-10-09", model.BirthDate?.ToString("yyyy-MM-dd"));
proxy.LastName = "Lennon";
Assert.Equal("1940-10-09", model.BirthDate?.ToString("yyyy-MM-dd"));
}
This test sets FirstName
to "John
" and checks that the BirthDate
property is not 1940-10-09, then it sets LastName
to "Lennon
" and checks that the BirthDate
is 1940-10-09 now.
We can run it and make sure that the interceptor executed the rule and changed the BirthDate
value. We also can use the debugger to see what is happening when we set the LastName
property, I know for sure - it is interesting.
Also, it is a good practice to have negative tests as well - which test the opposite scenarios. Let's create a test that will check that nothing happens if the full name is not "John Lennon
":
[Fact]
public void ModelRuleNotExecutedTest()
{
var model = new PersonModel();
var proxy = _factory.GetModelProxy(model);
proxy.FirstName = "John";
Assert.NotEqual("1940-10-09", model.BirthDate?.ToString("yyyy-MM-dd"));
proxy.LastName = "Travolta";
Assert.NotEqual("1940-10-09", model.BirthDate?.ToString("yyyy-MM-dd"));
}
You can find the full solution code on my GitHub, folder DemoCastleProxy-story3
:
Upgrading code to .NET 7.0 and Castle.Core 5.1.1
Having a few questions from readers I upgraded this article codebase to the latest version of .NET and Castle. As I see it works as expected and I made a screenshot:
If you look at the bottom, I highlighted the real type of the proxy object - it is "Castle.Proxies.PersonMoldeProxy
".
Summary
Today, we discussed the Castle open-source library that can be useful for dynamic proxy generation and interception of calls to the proxy methods and properties.
Because proxy is an extension of an original class, you can substitute the model objects with proxy objects, for example, when your Data access layer reads data from a database and returns back proxy objects (instead of original objects) to the caller, and then you will be able to track all the changes that happened with the returned proxy objects.
We also considered using unit tests to check that the created classes work as expected and to debug our code.
Thank you for reading!
History
- 24th October, 2020: Initial version