Validate Parameters Using Attributes and PostSharp ~ D. Patrick Caldwell on Software Engineering

Wednesday, March 11, 2009

Validate Parameters Using Attributes and PostSharp

I've been learning a lot about C# 4.0 lately and I have been very intrigued by the design by contract capabilities that will be added with the Microsoft Research Code Contracts project. One feature I'm particularly excited about is the static validation.

I've been working on a project which is little more than an abstract class and a bunch of test cases to verify that the outsourced implementers of this class make appropriate parameter validation on their overridden methods. I thought, "man, it sure would be nice if I could just put a code contract on my abstract class and then I wouldn't have to write all of these annoying unit tests."

Well, I just couldn't wait, so again I leveraged the pre-compiler flexibility of PostSharp and AoP concepts to simplify things a little (at least until code contracts are widely supported).

The first thing I did was define the contract for my parameter validation attributes:
[AttributeUsage(AttributeTargets.Parameter)]
public abstract class ParameterAttribute : Attribute
{
public abstract void CheckParameter(ParameterInfo parameter, object value);
}
Then, I wrote a method attribute with a method boundary aspect to process the parameter attributes:
[Serializable]
public class HasParameterAttributes : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
ParameterInfo[] pis = eventArgs.Method.GetParameters();
Object[] args = eventArgs.GetReadOnlyArgumentArray();

for (int i = 0; i < pis.Length; i++)
foreach (ParameterAttribute pa in pis[i].GetAttributes<ParameterAttribute>())
pa.CheckParameter(pis[i], args[i]);

base.OnEntry(eventArgs);
}
}
The version I wrote in honor of my friend Sir Nathan Rigsby is called CanHazParameterAttributes.

The HasParameterAttributes method attribute implementation uses an extension method I wrote to get a specific type of attributes from the PropertyInfo objects. You can find it in the article entitled: Extension Method to Get Custom Attributes with Reflection

I wrote two basic parameter validators to test the system:
public class NotNull : ParameterAttribute
{
public string Message { get; set; }
public override void CheckParameter(ParameterInfo parameter, object value)
{
if (value == null)
throw new ArgumentNullException(parameter.Name, Message);
}
}

public class NotEmpty : ParameterAttribute
{
public string Message { get; set; }

public override void CheckParameter(ParameterInfo parameter, object value)
{
if (value != null && value.ToString().Length == 0)
throw new ArgumentException(Message, parameter.Name);
}
}
The test I wrote does not execute like you would expect a unit test to execute. That is because I didn't have a lot of time to spend on this so I was too lazy to specify which test cases expect exceptions and which don't. If you run these test cases, you'll see that the ones which throw exceptions fail and the ones which don't throw exceptions pass. They perform as designed. Here's my NUnit test:
[TestFixture]
public class Tests
{
[Test, Combinatorial]
[HasParameterAttributes]
public void TestNotNull
([NotNull, Values(null, "", "Lulu")] string param1,
[NotEmpty, Values(null, "", "Lulu", 0)] object param2)
{ }
}
I really appreciate comments so please feel free to comment on my posts. Whether you agree or disagree, I'd love to hear from you. Also, feel free to link back to your own blog in your comments. You can even subscribe to an RSS feed of the comments on this thread.

© 2008 — , D. Patrick Caldwell, President, Autopilot Consulting, LLC

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi Patrick,

    Great article. It really got me thinking more carefully on how to solve all my validation woes. Was wondering if you had seen this post from the creator of PostSharp that kind of goes along the same lines (found here) and is to do with design by contract. Unfortunately the google code site for it is a mess but the correct place to find the source code is here.

    Ed

    ReplyDelete
  3. Nice!!
    one nice side effect is that it should be very easy (search and replace?!) to switch to MS CodeContracts when this becomes ready to use,
    which would offer you compile time checks for your input parameters.
    http://research.microsoft.com/en-us/projects/contracts/

    ReplyDelete