If you’ve read my earlier posts, you’ll see that I have a somewhat childish fascination (crush if you will) with the .NET Expressions and dynamics APIs. I think there is limitless potential with these two platforms and in this post, I will demonstrate the two working together by creating a small interception “framework” built on a dynamic proxy object provider…
My goal is to create a class deriving from DynamicObject
. When operations are executed against an object of type DyanamicObject
, there is an opportunity to intercept the call and do something meaningful; here I am going to just forward the call to the target object. What is this target object? It’s going to be a type fed to the generic class deriving from DynamicObject
.
So the “real” object (the interception target) is going to be simple:
Ignore the attributes for now… just take note that there is nothing special about this class. No class attributes, no interfaces… just a POCO with a property and two methods that do rather boring things.
The interception “framework” is a single class.
public delegate void BeforeSetDelegate(string name, object value);
public class DynamicProxy<T> : DynamicObject
{
public event BeforeSetDelegate OnBeforeSet;
private T instance = default(T);
public DynamicProxy()
{
instance = Activator.CreateInstance<T>();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
PropertyInfo info = instance.GetType().GetProperty(binder.Name);
if(info == null)
return false;
result = info.GetValue(instance, null);
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if(OnBeforeSet != null)
OnBeforeSet(binder.Name, value);
PropertyInfo info = instance.GetType().GetProperty(binder.Name);
if(info == null)
return false;
foreach (var att in info.GetCustomAttributes(typeof(ValidationAttribute), true))
{
var vatt = att as ValidationAttribute;
if(!vatt.IsValid(value))
{
throw new ValidationException("Parameter failed validation.", vatt, value);
}
}
info.SetValue(instance, value, null);
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
MethodInfo info = instance.GetType().GetMethod(binder.Name);
for(int i = 0; i < args.Length; i++)
{
var param = info.GetParameters()[i];
foreach(var att in param.GetCustomAttributes(typeof(ValidationAttribute), true))
{
ValidationAttribute vatt = att as ValidationAttribute;
bool isValid = vatt.IsValid(args[i]);
if (!isValid)
{
throw new ValidationException(String.Format(
"Parameter: ({0}) failed validation.", param.Name), vatt, args[0]);
}
}
}
result = info.Invoke(instance, args);
return true;
}
}
Ahhh… so that’s how the interception target is created! In the constructor of this class. So if I want to proxy say “SomeClass
”, I can use the following code:
Now, what if I want to fire an event before a property is set? Holy Smokes! This allows us to! Since DynamicProxy
intercepts ALL calls, if I wire up an event, I can raise it before forwarding on the call to the actual object.
This is where the magic happens. Before any property is executed on the target object, the message gets relayed here… in TrySetMember
. Here, I raise the OnBeforeSet
event which I subscribed to earlier. So when I make the call “d.Name = ‘String one’
”, I will get the following output:
So it tells me the name of the property being set. The second line of the output is simply the value of the Name
property… what is really happening under the hood when I interrogate a property is the DynamicProxy
class is intercepting the call, asking the target class for the value and returning it. But it’s totally transparent to the caller (aside from using the dynamic
keyword when creating the object).
So what more can we do with this interception technique? How about data validation (could be used as pre/post conditions, for instance). If you take a look back at SomeClass
, the property, Name
, and a parameter to the function “DoSomethingAndReturn
” are both decorated with attributes from the System.ComponentModel.DataAnnotations namespace
: StringLength
and Range
, respectively. I bet you can see where I am going with this… If a property on the target object is decorated with one of these ValidationAttributes
, I will check the validity of the data before setting the property; throwing an exception if validation fails. If you look at the “TrySetMember
” operation of the DynamicProxy
class, you’ll see I find all custom attributes of type “ValidationAttribute
” and check the validity of the data being provided (remember: TrySetMember
will have the value of the property). Nothing special here…I just call IsValid()
with the data provided (StringLength
and Range
both derive from ValidationAttribute
).
To demonstrate the validation, I am going to change the code in Main
slightly to purposely fail the StringLength(10)
rule applied to the “Name
” property of “SomeClass
.” If I run it, I will get the following output:
So it told me which validator failed and the expression??? Where did that come from? Ooooh, wait…that’s the actual StringLength
attribute displayed as a mathematical expression! How useful is that!? I get the validator that failed along with why (the expression…telling me (26 > 0 and 26 < 10)
. Remember: The StringLength
attribute describes the maximum length of a string
… and 0
is obviously the minimum. This makes sense but how did it know? With a simple extension method I created to craft the expression from the attribute:
… and on and on. You can take this as far as you want to go… and I’ve only scratched the surface.