Dmitry Shechtman's Blog

December 19, 2009

IDataErrorInfo with Validation Application Block and Unity Interception AOP

Filed under: PropertyChange — Tags: , , , , , , , , , , , , — Dmitry Shechtman @ 12:40

This is the second article in a planned series of articles concerning AOP with Unity Interception and Policy Injection:

  1. INotifyPropertyChanged AOP
  2. IDataErrorInfo AOP
  3. Policy Injection
  4. Performance and optimizations
  5. The final solution

I hope you will find this useful in your work; all feedback will be greatly appreciated.

The previous article discussed an implementation of the INotifyPropertyChanged aspect using Unity Interception AOP. Today we will see how a similar approach can be applied to another interface commonly used by WPF (as well as by ASP.NET MVC).

The IDataErrorInfo interface consists of two methods:

  1. an indexer accepting a property name (columnName) parameter and
  2. an Error property.

We will solely focus on the indexer, which returns error messages on a per-property basis.

The Validation Application Block allows developers to easily incorporate input validation via property attributes and/or application configuration, supporting a variety of provided validators, as well as custom ones.

IDataErrorInfo and VAB seem to be a perfect match, and have indeed been integrated. Adding Unity Interception will further simplify input validation, so that (presentation) models could be specified in the following manner:

[NotifyPropertyChanged, DataErrorInfo]
public class Contact : MarshalByRefObject, INotifyPropertyChanged, IDataErrorInfo
{
    [StringLengthValidator(1, 20, MessageTemplate = "First name length must be between {3} and {5}.")]
    [ContainsCharactersValidator("0123456789", Negated = true, MessageTemplate = "First name cannot contain digits")]
    public string FirstName { get; set; }

    [StringLengthValidator(1, 20, MessageTemplate = "Last name length must be between {3} and {5}.")]
    [ContainsCharactersValidator("0123456789", Negated = true, MessageTemplate = "Last name cannot contain digits.")]
    public string LastName { get; set; }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get { return null; }
    }
}

Note: The implementation presented below is limited to classes derived (possibly indirectly) from MarshalByRefObject.

Project

In addition to Microsoft.Practices.Unity.Interception, we will require a reference to Microsoft.Practices.EnterpriseLibrary.Validation, which is listed as Enterprise Library Validation Application Block under the .NET tab in Visual Studio (provided the Enterprise Library is installed).

Matching Rules

We will be intercepting

  1. getters of indexers accepting a single string parameter
  2. defined in types decorated with DataErrorInfoAttribute:
    [Serializable, AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public class DataErrorInfoAttribute : Attribute { }

The second rule is best defined using CustomTypeAttributeMatchingRule.

The first rule could be defined using MethodSignatureMatchingRule by specifying get_Item as the methodName. However, (yet another) custom matching rule seems more appropriate. This time, we will reuse some code from MethodSignatureMatchingRule and PropertyMatchingRule:

public class IndexerMatchingRule : IMatchingRule
{
    private readonly List<Glob> patterns = new <Glob>();
    private readonly List<TypeMatchingRule> parameterRules = new List<TypeMatchingRule>();

    public IndexerMatchingRule(PropertyMatchingOption option, IEnumerable<string> parameterTypeNames)
        : this(option, parameterTypeNames, false)
    {
    }

    public IndexerMatchingRule(PropertyMatchingOption option, IEnumerable<string> parameterTypeNames, bool ignoreCase)
    {
        if (option != PropertyMatchingOption.Set)
            patterns.Add(new Glob("get_Item"));
        if (option != PropertyMatchingOption.Get)
            patterns.Add(new Glob("set_Item"));

        foreach (string parameterTypeName in parameterTypeNames)
            parameterRules.Add(new TypeMatchingRule(parameterTypeName, ignoreCase));
    }

    public bool Matches(MethodBase member)
    {
        if (!member.IsSpecialName || !patterns.Exists(delegate(Glob pattern) { return pattern.IsMatch(member.Name); }))
            return false;

        ParameterInfo[] parameters = member.GetParameters();
        if (parameters.Length != parameterRules.Count)
            return false;

        for (int i = 0; i < parameters.Length; ++i)
            if (!parameterRules[i].Matches(parameters[i].ParameterType))
                return false;

        return true;
    }
}

Call Handler

Our call handler will be pretty compact.

public class DataErrorInfoItemCallHandler : ICallHandler

ICallHandler members:

    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        IMethodReturn res = getNext()(input, getNext);

        string propertyName = input.Arguments.Count > 0 ?
            input.Arguments[0] as string :
            null;

        res.ReturnValue = GetError(input.Target, propertyName);

        return res;
    }

    public int Order { get; set; }

Lines 7-9 provide a sanity check, so that the handler won’t throw an exception when invoked incorretly (i.e. without a string argument).

    private ValidationResults GetError(object target, string propertyName)
    {
        MethodInfo validateGen = validate.MakeGenericMethod(target.GetType());
        ValidationResults results = (ValidationResults)validateGen.Invoke(null, new object[] { target });
        if (results.IsValid)
            return string.Empty;

        return string.Join("\n", results.Where(r => r.Key == propertyName).Select(r => r.Message).ToArray());
    }

    private static readonly MethodInfo validate = typeof(Validation).GetMethods().First(m => m.Name == "Validate");

While the reference to Validation.Validate() is constant, we need to obtain a type-specific (“generic”) version per target type. Multiple validation messages are concatenated with newlines inserted between them.

In the next article we will update our implementation to support the Policy Injection Application Block and see several deployment and usage scenarios. Stay tuned!

Advertisements

7 Comments »

  1. […] IDataErrorInfo AOP […]

    Pingback by INotifyPropertyChanged with Unity Interception AOP « Dmitry Shechtman's Blog — December 19, 2009 @ 12:51

  2. Hi,

    Cool series so far, any news on the last 3 articles?
    Really interested in how it’s gonna end, if you’re not planning on writing the articles, feel free to send me the final code bits 😉

    Comment by Tim — April 14, 2010 @ 00:31

    • Hi Tim,

      Thanks and sorry for the late reply.

      I do hope to complete the series, but since it doesn’t seem to be getting too much user attention, I’m taking my time with it. I could publish just the code, but I feel it would be incomplete without thorough documentation.

      Comment by Dmitry Shechtman — April 19, 2010 @ 07:00

  3. Hi,

    i am not able to run the code within a project althoug
    i integrated
    a) Microsoft.Practices.Unity.Interception and
    b) Microsoft.Practices.EnterpriseLibrary.Validation

    the programm does not know the type MethodBase in the function

    public bool Matches(MethodBase member)

    what do i have to do?
    is it possible to get your code as a project ?

    please email to
    mental-iq@gmx.de

    or post it here

    Comment by Torsten Schmitz — July 13, 2010 @ 10:10

  4. Ok,

    found the solution: you have to integrate
    using System.Reflection; in the Class IndexerMatchingRule

    The Class Customer has to integrate
    using Microsoft.Practices.EnterpriseLibrary.Validation;
    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

    Comment by Torsten Schmitz — July 13, 2010 @ 10:26

  5. ok i wont get the code run,
    although i integrated all classes.
    perhaps a demo projekt with all usings would be fine
    takes to much time to get this thing started.
    its just half the battle;-)

    Comment by Torsten Schmitz — July 13, 2010 @ 11:35

  6. whoah this weblog is magnificent i really like reading your posts.
    Stay up the good work! You understand, many people are hunting round for this information, you
    could aid them greatly.

    Comment by Enrique — October 27, 2012 @ 15:16


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.