Wednesday, 19 December 2007

A time for System.Reflection

In developing a class that holds search criteria for a generic application search tool I needed to do two things: apply wildcards to the search criteria, and sanitize the search input data. As the class that holds the search criteria is basically a collection of protected or private data members and a set of public properties to expose them (and since there will not necessarily be only one single class of this type, setting up this functionality by calling each property explicitly could be really tedious - not to mention hard to maintain.

So a different measure was clearly called for. I decided to do this through reflection, and letting the class reflect upon an instance of itself and subject the relevant properties to treatment by a set of methods.

I'll show a simplified example here (don't worry, I'll leave in all the relevant bits concerning reflection). The requirements for the class, aside from holding and exposing search criteria, is that it exposes a method that you can call to sanitize the data it holds, and another method you can call to apply wildcards to the criteria.

The example class I'll use looks like this:

public class SearchCriteria
{
private string _firstName;
private string _lastName;
private string _email;

public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public string Email
{
get { return _email; }
set { _email = value; }
}

}

It's very simple. No constructor, only three private fields and three public get/set properties. The real-life class has lots more properties but there's no need to bore you with those here - it's an example after all.

We need to both sanitize the properties and apply wildcards to them. We'll provide two public methods (Sanitize() and ApplyWildCards()) that will do the work on the properties. And this is how it looks:

using System;
using System.Reflection;

public class SearchCriteria
{
private string _firstName;
private string _lastName;
private string _email;
private delegate void ActionDelegate(PropertyInfo prop);

public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public string Email
{
get { return _email; }
set { _email = value; }
}

public void Sanitize()
{
ModifyProperties(new ActionDelegate(Sanitize));
}

public void ApplyWildCards()
{
ModifyProperties(new ActionDelegate(ApplyWildCards));
}

private void ModifyProperties(ActionDelegate actionDelegate)
{
foreach (PropertyInfo prop in GetProperties())
{
if (prop.PropertyType == typeof(String))
actionDelegate(prop);
}
}

private PropertyInfo[] GetProperties()
{
return typeof (SearchCriteria).GetProperties();
}

private void Sanitize(PropertyInfo prop)
{
//......
}

private void ApplyWildCards(PropertyInfo prop)
{
//......
}
}

At the core of this we have the method called ModifyProperties which consumes a delegate of type ActionDelegate which is defined at the top as private delegate void ActionDelegate(PropertyInfo prop). If you look carefully you'll see that the methods Sanitize(PropertyInfo prop) and ApplyWildcards(PropertyInfo prop) both match the delegate's definition. This is in fact what allows us to pass references to these methods into ModifyProperties via Sanitize() and ApplyWildCards(). All that the ModifyProperties method does is loop through a collection of properties which is returned by the GetProperties() method and call the appropriate method (Sanitize or ApplyWildCards) via the delegate that was passed in.

I've taken out the details of Sanitize(PropertyInfo prop) and ApplyWildCards(PropertyInfo prop) because it isn't really relevant here.

Simple, isn't it? It's quite neat, I think. Now there is one thing to note here. ModifyProperties only looks at string properties, so if you have to sanitize properties of an other type you'd have to do change the code. Probably it would be wiser to create a custom attribute class and mark up the properties that can be sanitized instead of relying on property type.

Obviously this is a simple example and a first pass at creating a solution and I've not taken into consideration things such as sanitation requirements changing based on context etc., but that's fairly simple to slot in. Hopefully this has been a useful and simple real-world example of how you can use System.Reflection.

Happy coding!

No comments: