FluentValidation and PostSharp for RESTful WCF parameter validation – Part 2

 

Now that we have introduced FluentValidation in the previous post we can take a look at applying these based on attributes applied to the incoming WCF parameters. Let’s take a simple method and apply some parameters to it.

[RestArgumentValidation]
public Basket Basket(
    [CustomerIdValidator] string customerid,
    [BasketItemValidator] BasketItem basketItem)
{
    // Stuff happens
}

While these attributes do clutter the code, personally I feel they make it clear these parameters are being validated. Let’s take a look at what these parameter attributes are before looking at the PostSharp.

First we need a base class for the attributes to provide both a default value if needed, and what validators should apply to the decorated parameter.

[Serializable]
public abstract class RestArgumentValidatorBase : Attribute
{
    public abstract IEnumerable<Type> Validators { get; }

    public virtual Func<object> DefaultValue { get; private set; }
}

The reason we need to define a function that returns the default value is because PostSharp serialises these attributes as an optimisation. If the default value is based on an expression which can only be calculated at runtime, then we cannot serialise it, but we can serialise the expression which computes this value. Let’s take a look at some examples of this in action:

public class BasketItemValidatorAttribute : RestArgumentValidatorBase
{
    public override IEnumerable<Type> Validators
    {
        get
        {
            return new[]
                       {
                           typeof(BasketItemValidator)
                       };
        }
    }
}

Here we specify the validator used for the object BasketItem, and no default value. This means if the validation fails a response will be sent to the client indicating what has gone wrong.

public class CurrencyValidatorAttribute : RestArgumentValidatorBase
{
    public override Func<object> DefaultValue
    {
        get { return () => ConfigurationManager.AppSettings["defaultCurrency"]; }
    }

    public override IEnumerable<Type> Validators
    {
        get { return new[] { typeof(CurrencyValidator) }; }
    }
}

Here we specify a default value which is taken from the app settings, so if an invalid parameter is sent down, we can use this value instead. All that’s left is the PostSharp aspect which ties all this together which we can go through step by step.

First we need to analyse the method which has the aspect applied to see what validation needs to happen. We can compute this ahead of time during compilation so the runtime overhead is reduced.

public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
    foreach (var parameterInfo in method.GetParameters())
    {
        var validationAttribute = (from attributeType in parameterInfo.GetCustomAttributes(true)
                                   where typeof(RestArgumentValidatorBase).IsAssignableFrom(attributeType.GetType())
                                   select attributeType).Cast<RestArgumentValidatorBase>().FirstOrDefault();

        if(validationAttribute != null)
        {
            _validationRules.Add(new ParameterValidation
                                     {
                                         Parameter = parameterInfo,
                                         DefaultValue = validationAttribute.DefaultValue,
                                         ValidationRules = validationAttribute.Validators
                                     });
        }
    }
}

This can be summarised as:

  1. Take a look at each parameter on the method.
  2. See if it has a validation attribute applied.
  3. Cache the default value expression and validation rules for that parameter in a serialised list which is used at runtime.

The cached serialised object itself looks like:

[Serializable]
class ParameterValidation
{
    public ParameterInfo Parameter { get; set; }

    public Func<object> DefaultValue { get; set; }

    public bool HasDefaultValue
    {
        get
        {
            return DefaultValue != null;
        }
    }

    public IEnumerable<Type> ValidationRules { get; set; }
}

Next up we’ll take a look at using this cached information at runtime.

blog comments powered by Disqus