How To: Write a Custom Validator for Validation Application Block (VAB) using Application Block Software Factory

26 ביולי 2007

How To: Write a Custom Validator for Validation Application Block (VAB) using Application Block Software Factory


This post is a step by step guide on how to write a custom validator using Application Block Software Factory and integrate it in the configuration editor of Enterprise Library.


When you install Enterprise Library 3.1, you not only get library code and documentation, you also get the Application Block Software Factory. This factory allows you to create new application block that can sit above the Enterprise Library code services such as configuration and ObjectBuilder. This Software Factory can also help you to create typed and untyped providers for the library such as new TraceListener for the Logging Application Block, Call Handler for the Policy Injection Application Block, Custom ValidatorValidation Application Block, etc.


Create a new Provider Library


1. In Visual Studio 2005, create a new project of type Guidance Packages -> Application Block Software Factory -> Provider Library.


Custom Validator Validation Application Block


2. This will start a wizard that will request some details from you:
Name: The name of the solution and the name pf the project that will contain the validator. I named it Bursteg.ProvidersLibrary, and we'll meet this name later in this post.
Description: A description that will be placed in the AssemblyInfo.cs of the providers library.
Author: Your Name
Namespace: Namespace of your choice.


Custom Validator Validation Application Block


3. Click Finish and Visual Studio will generate the solution structure for you. The solution contains the Bursteg.ProvidersLibrary project in which the validator will be created, and another project called Bursteg.ProvidersLibrary.Configuration.Design that will contain the visual elements needed in order to integrate this validator into the Enterprise Library Configuration Editor. If in step 1 you created a project with tests then the solution will also contain 2 unit tests projects.


ValidatorSolutionStructure


Create the Custom Validator


Generate Initial Code for the Custom Validator


4. Right click the providers project (Bursteg.ProvidersLibrary) and from select Applciation Block Software Factory -> Validation Application Block -> New Validator (Typed).


Custom Validator VAB


5. This will start another wizard that will request the name of the validator. For this demo proposes I will create a Person ID Validator, that will validate an ID number according to a configured type. I named the validator: PersonIDValidator.


Custom Validator VAB


6. Click Finish and Visual Studio will add the necessary references and generate the initial source code for this validator. Notice that Bursteg.ProvidersLibrary has 3 new files for it.
PersonIDValidator.cs contains the code for the validator.
PersonIDValidatorAttribute.cs contains the code for the validator attribute in case you want to apply it by an attribute.
PersonIDValidatorData.cs in the Configuration folder contains the code for the configuration element of the validator – the class that holds its data that was configured in the validator.


SolutionWithValidator


Add support for properties


You should follow this step only if your validator receives any parameters. In this guide, the validator will receive a Country Code parameter and will validate the Person ID with the logic according to his country.


7. Open the PersonIDValidator.cs and add the country code property.



[ConfigurationElementType(typeof(PersonIDValidatorData))]


public class PersonIDValidator : ValueValidator


{


    private int countryCode;


 


    public int CountryCode


    {


        get { return countryCode; }


        set { countryCode = value; }


    }


    …


}


8. Scroll down in this file and notice this commented code block:



// TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidator.


//public PersonIDValidator(object param1, object param2, string messageTemplate, bool negated)


//    : base(messageTemplate, null, negated)


//{


//    this.var1 = param1;


//    this.var2 = param2;


//}


Uncomment it and create an appropriate constructor that receives the relevant parameters for your validator. In this guide I will receive the country code parameter. The constructor should look like that:



public PersonIDValidator(int coutry, string messageTemplate, bool negated)


    : base(messageTemplate, null, negated)


{


    this.countryCode = coutry;


}


This constructor will be useful when you can to initialize the validator by code, or by attribute.


If the country code is a mandatory parameter (It is in my case) then you should make sure it exist in all the overloads of the constructor. Finally, this is how the constructors should look like:



/// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="coutry">Country Code to validate by it</param>


public PersonIDValidator(int country)


    : this(country, null, false)


{ }


 



/// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="configuration">The configuration instance for the validator.</param>


public PersonIDValidator(PersonIDValidatorData configuration)


    : this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)


{


}


 


/// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="coutry">Country Code to validate by it</param>


/// <param name="negated">True if the validator must negate the result of the validation.</param>


public PersonIDValidator(int country, bool negated)


    : this(country, null, negated)


{ }


 


/// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="coutry">Country Code to validate by it</param>


/// <param name="messageTemplate">The message template to use when logging results.</param>


public PersonIDValidator(int country, string messageTemplate)


    : this(country, messageTemplate, false)


{ }


 


// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="coutry">Country Code to validate by it</param>


/// <param name="messageTemplate">The message template to use when logging results.</param>


/// <param name="negated">True if the validator must negate the result of the validation</param>


public PersonIDValidator(int coutry, string messageTemplate, bool negated)


    : base(messageTemplate, null, negated)


{


    this.countryCode = coutry;


}


9. Open the PersonIDValidatorData.cs (under the Configuration folder) and locate the following commented line:



// TODO: Add the configuration properties for PersonIDValidatorData. The snippet for creating configuration properties would be useful.


Add the coutry code property to this class using the code snippet for creating configuration properties provided with the Application Block Software Factory. (To insert a snippet, click Ctrl + K + X and select the snippet you want. If you want to use this one directly, you can type its shortcut configproperty and click Tab twice).


Custom Validator with Application Block Software Factory


Edit the snippet fields with the property's name, configuration name and type.



private const string CountryCodePropertyName = "CountryCode";


[ConfigurationProperty(CountryCodePropertyName)]


public int CountryCode


{


    get { return (int)this[CountryCodePropertyName]; }


    set { this[CountryCodePropertyName] = value; }


}


10. Go back to the PersonIDValidator.cs and modify the constructor that receives the PersonIDValidatorData as parameter. This constructor is being called when the validator was configured in the configuration file. Simply assign the values from the configuration properties to the validator properties.



/// <summary>


/// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>


/// </summary>


/// <param name="configuration">The configuration instance for the validator.</param>


public PersonIDValidator(PersonIDValidatorData configuration)


    : this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)


{


}


Notice the first parameter that this constructor overload receives.


10. Open the PersonIDValidatorAttribute.cs  and add a data member for the country code.



private int coutryCode;


Locate the following commented code:



// TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidatorAttribute.


//public PersonIDValidatorAttribute(object param1, object param2)


//{


//    this.var1 = param1;


//    this.var2 = param2;


//}


Uncomment this code and create the constructor with the country code parameter:



public PersonIDValidatorAttribute(int country)


{


    this.coutryCode = country;


}


If the country code is a mandatory parameter (It is in my case) then you should remove the default constructor.


11. In the same file edit the DoCreateValidator method. This method creates the instance of the validator based on the parameters of the attribute. Make sure to use the constructor overload that receives the country code.



protected override Validator DoCreateValidator(Type targetType)


{


    return new PersonIDValidator(this.coutryCode, MessageTemplate, Negated);


}


Implement the Validation Logic


12. Go back the the PersonIDValidator.cs and locate the DoValidate method at the bottom of the class. This method will contain the validation logic, but first you must know what each of the parameters mean. From the parameters description above the method:
objectToValidate – The object to validate.
currentTarget – The object on the behalf of which the validation is performed.
key – The key that identifies the source of objectToValidate
validationResults – The validation results to which the outcome of the validation should be stored.


For example:
If this validator receives a string to validate (person id is string in this example):



PersonIDValidator validator = new PersonIDValidator(3);


ValidationResults results = validator.Validate("123");


Than the objectToValidate will be a string containing "123", as same as the currentTarget. The key will null.


If the validator receives an object and validates only a property of that object:



Person p = new Person();


p.PersonID = "123";


ValidationResults results = Validation.Validate<Person>(p);


Than the objectToValidate will be "123", the currentTarget will be the person instance and the key will be "PersonID".


Now, when you understand what each parameter does, we can go on and implement the validation logic. The validation logic in this case is very simple, since this is not the main purpose of this guide. The validator checks the input string and make sure it ends with the country code.




protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)


{


    // Get the object to validate


    string id = (string)objectToValidate;


 


    // Check if it is valid


    string end = countryCode.ToString().Trim();


    bool isValid = id.EndsWith(end);


 


    // If the negated property is false, and the id is not valid, log a validation error


    if (isValid == Negated)


    {


        LogValidationResult(validationResults, this.MessageTemplate, currentTarget, key);


    }


}


Change the Message Templates of the Custom Validator


13. When you log a validation error you also add a message that contains the error description. By default the validator uses 2 message templates – one for the default validation error and one for the negated error. To change the validation error messages, expand the provider library project (Bursteg.ProvidersLibrary) and double click the Resources.resx file under the properties folder.


Custom Validator with Application Block Software Factory


This will open the Resource Editor and let you edit the messages. Notice that there 2 message templates – PersonIDValidatorNegatedDefaultMessage (for the negated template) and PersonIDValidatorNonNegatedDefaultMessage (for the non-negated template).


Custom Validator with Application Block Software Factory


Add Design-Time support for the Custom Validator


When you develop Custom Validator for your project, you'll probably want to add design-time support that will enable developers configure the new validator from the Enterprise Library Configuration Tool just as simple as they would do for every other out-of-the-box validator.


Creating Design-Time providers node is a recipe that is similar to all Enterprise Library providers and not something specific for each provider type.


14. Add references to the Validation Application Block assemblies (Microsoft.Practices.EnterpriseLibrary.Validation and Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design).


Right click the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design) and choose Application Block Software Factory -> Create Design-Time Provider Node.


Custom Validator with Application Block Software Factory


15. This will start a new wizard that will request some detail about the provider:


Node Name is the name of the class that  will be generated and will represent the node in the configuration tree for this validator. In this guide it is PersonIDValidatorNode.


Runtime Configuration Type is the type that holds the configuration data for this validator, and it was already created for us earlier. In this guide it is PersonIDValidatorData in the Bursteg.ProvidersLibrary assembly.


Base Design Mode is the class to inherit from when generating the configuration type. If you noticed when we edited the validator class it inherited from a class called ValueValidator, which has a Design-Time configuration type called ValueValidatorNode in the Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design assembly. You should pick this type as the Base Design Mode.


Parent UI Node is the parent node in the configuration tree. The validator will always have parent of the following nodes:



  • SelfNode – When we want to validate an object (the whole object).
  • PropertyNode – When we want to validate a property of a type.
  • MethodNode – When we want to validate method parameters of a type
  • FieldNode – When we want to validate a field of a type.

You would probably want to support all of the above options, but the wizard supports only a single selection. You can select SelfNode for now, and we will add the others by code.


Cardinality indicates whether this validator can be applied more than once. For example – if you want to have 2 PersonIDValidators validating a single property. For this guide it is Single.


Custom Validator with Application Block Software Factory


16. Click Finish and Visual Studio will generate the code for the design-time configuration support. Notice that Bursteg.ProvidersLibrary.Configuration.Design has 4 new files for it:
PersonIDValidatorNode.cs contains the class that represents the node in the configuration.
CommandRegistrar.cs, ConfigurationDesignManager.cs and NodeMapRegistrar.cs contains the code that registers the new configuration node to the configuration tree in the location we have selected earlier in the wizard.


Custom Validator with Application Block Software Factory 


17. In step 14, we have selected to place the validator node under the SelfNode. If you want to add support for other node types, expand the CommandRegistrar.cs file node, and open the CommandRegistrar.PersonIDValidatorNode.cs file. This is a part of the file that registers the validator to the configuration.


Edit the AddPersonIDValidatorNodeCommand method: duplicate the code that invokes AddSingleChildNodeCommand method as many as you need to add the validator parent nodes. Usually you will want to allow all 4 parent nodes, so you should copy it 4 times. The only difference between all the 4 invocations is the last parameter which is the type of the parent node. In step 14 we selected the SelfNode, so the other 3 invocations should use PropertyNode, MethodNode and FieldNode.



sealed partial class CommandRegistrar


{


    private void AddPersonIDValidatorNodeCommand()


    {


        AddSingleChildNodeCommand(


            Resources.PersonIDValidatorNodeUICommandText,


            Resources.PersonIDValidatorNodeUICommandLongText,


            typeof(PersonIDValidatorNode),


            typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.SelfNode));


 


        AddSingleChildNodeCommand(


            Resources.PersonIDValidatorNodeUICommandText,


            Resources.PersonIDValidatorNodeUICommandLongText,


            typeof(PersonIDValidatorNode),


            typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.PropertyNode));


 


        AddSingleChildNodeCommand(


            Resources.PersonIDValidatorNodeUICommandText,


            Resources.PersonIDValidatorNodeUICommandLongText,


            typeof(PersonIDValidatorNode),


            typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.MethodNode));


 


        AddSingleChildNodeCommand(


            Resources.PersonIDValidatorNodeUICommandText,


            Resources.PersonIDValidatorNodeUICommandLongText,


            typeof(PersonIDValidatorNode),


            typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.FieldNode));


 


 


    }


}


18. Change the captions of the Custom Validator as they will show in the configuration tool. Edit the resources file of the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design).


Edit the following resources:
PersonIDValidatorNodeName – PersonID Validator 
PersonIDValidatorNodeUICommandLongText – PersonID Validator Long Test
PersonIDValidatorNodeUICommandText – PersonID Validator Command Text


You'll soon see where each of these captions are displayed.


19. I've already said that adding a design-time support for all provider types is done using the same recipe, but with validators you have to change some code in order to complete this task.


Open PersonIDValidatorNode.cs and override the CreateValidatorData() method. As an implementation, paste the code from the ValueValidatorData property.



public override ValidatorData CreateValidatorData()


{


    PersonIDValidatorData data = new PersonIDValidatorData(this.Name);


    data.CountryCode = this.countryCode;


    data.Negated = this.negated;


    data.MessageTemplate = this.messageTemplate;


    data.MessageTemplateResourceName = this.messageTemplateResourceName;


    data.MessageTemplateResourceTypeName = this.messageTemplateResourceTypeName;


    data.Tag = this.tag;


    return data;


}


After you pasted the code, remove the ValueValidatorData property. You don't need it anymore.


20. Change the constructor that receives PersonIDValidatorData as a parameter. Have it call the base class constructor with the parameter value.



/// <summary>


/// Initialize a new instance of the <see cref="PersonIDValidatorNode"/> class with a <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance.


/// </summary>


/// <param name="data">A <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance</param>


public PersonIDValidatorNode(Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData data)


    :base(data)


{


    …


}


21. In order that the configuration tool can see your provider library and register the validator, you should copy the output assembly of the providers library and design type support (Bursteg.ProvidersLibrary.dll and Bursteg.ProvidersLibrary.Condiguration.Design.dll) to the Enterprise Library bin directory. By default it is C:\Program Files\Microsoft Enterprise Library 3.1 – May 2007\Bin.


Test the Custom Validator


Test the validator when used in code


22. Open a new instance of Visual Studio (otherwise the configuration changes will not be applied) and create a new Console Application in order to test the validator. After the project is created, add a new class of type Person:



public class Person


{


    private string personID;


 


    public string PersonID


    {


        get { return personID; }


        set { personID = value; }


    }


}


23. Add references to the relevant assemblies:
(Microsoft.Practices.EnterpriseLibrary.Common and Microsoft.Practices.EnterpriseLibrary.Validation) for the validation support, providers library(Bursteg.ProvidersLibrary) and System.Configuration.dll.


24. In the Main method, create a new instance of person and set its ID. Use the validator to check if the ID is valid.



// Initialize a new instance of Person


Person p = new Person();


p.PersonID = "123";


 



// Validate a valid person ID


PersonIDValidator validator = new PersonIDValidator(3);


ValidationResults results = validator.Validate(p.PersonID);


Debug.Assert(results.IsValid);


DisplayValidationResults(results);


 


// Validate an invalid person ID


validator = new PersonIDValidator(2);


results = validator.Validate(p.PersonID);


Debug.Assert(!results.IsValid);


DisplayValidationResults(results);


 


// Validate a valid Person ID, but with negated = true


validator = new PersonIDValidator(3, true);


results = validator.Validate(p.PersonID);


Debug.Assert(!results.IsValid);


DisplayValidationResults(results);


 


// Validate an invalid Person ID, but with negated = true


validator = new PersonIDValidator(2, true);


results = validator.Validate(p.PersonID);


Debug.Assert(results.IsValid);


DisplayValidationResults(results);


Where DisplayValidationResults method can be something like:



private static void DisplayValidationResults(ValidationResults results)


{


    if (!results.IsValid)


    {


        foreach (ValidationResult result in results)


        {


            Console.WriteLine(result.Message);


        }


    }


}


Test the validator when configured using an attribute


25. Add the validator attribute to the ID Property of the Person class.



public class Person


{


    private string personID;


 


    [PersonIDValidator(3)]


    public string PersonID


    {


        get { return personID; }


        set { personID = value; }


    }


}


26. In the Main method, validate the person instance to check if the ID is valid.



// Validate the person


results = Validation.ValidateFromAttributes<Person>(p);


DisplayValidationResults(results);


Test the validator when configured in the configuration file


27. Add a new app.config to your Console Application, and edit with the Enterprise Library Configuration Tool.


Custom Validator Validation Application Block


28. Add the Validation Application Block Configuration Section. Right click the configuration file name node and choose New -> Validation Application Block.


Custom Validator Validation Application Block


29. This will add the Validation Application Block configuration section to your configuration file. Add a new type to validate. Right click the Validation Application Block node and choose New -> Type.


Custom Validator Validation Application Block  


30. This will open the Type Selector Dialog. Click Load an Assembly, and navigate to the output folder of the test project in order to find the assembly that contains the class Person. Finally, select the Person type.


Custom Validator Validation Application Block  


31. When configuring validations for a type using the configuration tool you cannot add validations straight on the type, you must group a few validations into a Ruleset. Add a new Ruleset by clicking the Person type and selecting New -> Ruleset. After the new Ruleset is added, you should change its name to something meaningful.


Custom Validator Validation Application Block


32. The Custom Validator we created in this guide validates objects of type string that are ID's of people. In the Person type, we would like to validate the PersonID property with this validator. To validate this property we first have to add it to the Person node. Right Click the Person node and choose New -> Property. Name the property exactly as it is named in the class definition, case sensitive.


Custom Validator Validation Application Block  


33. Add the PersonIDValidator to the PersonID node. Right click the PersonID node and select New-> PersonID Validator Command Text. (Now you see where the command text you edited in step 18 goes to.


Custom Validator Validation Application Block


34. After the validator is added, a new node will be shown called PersonID Validator (same as the PersonIDValidatorNodeName resource from step 18). Go to the properties pane and set its country code.


In the Main method, validate the person instance to check if the ID is valid, and don't forget to use the name of the ruleset.



// Validate the person


results = Validation.ValidateFromConfiguration<Person>(p, "DefaultValidation");


DisplayValidationResults(results);


Conclusion


Creating a Custom Validator is very easy to do, especially with the Application Block Software Factory. You create a provider library and generate the validator and you add design-type support using predefined recipes. Developers can use the new validator as simple as with any other out-of-the-box validator.


You can download a sample project: Custom Validator with Application Block Software Factory


If you have any questions or feedback, I'd like to hear.


Enjoy!

Add comment
facebook linkedin twitter email

6 comments

  1. Pritam Bramhecha23 באוגוסט 2007 ב 17:06

    Hi Guy,
    I would like to know that what are the prerequiste for Application Block Software factory? I have intsalled GAX, GAT and web service software factory. I am interested in creating the WCF services and applying custom validation classes to my services but that through giving it in App.Config file of the Host project.
    Do reply.
    Regards,
    Pritam Bramhecha

  2. Pritam Bramhecha29 באוגוסט 2007 ב 17:02

    hi guy,
    Thanks for your information. Actaully I was interested in creating a Custom Validator using Application Block Software Factory but was not able to do that so. Now I have found the solution as we have to install the Entrprise Library Source Code Installer and after that we can enable that Guidance Package and create the custom validator using Application Block Software Factory.

    Thanks,
    Pritam Bramhecha

  3. Myckyc27 במרץ 2008 ב 10:41

    There is some problem with Custom Typed Validator and
    Composite VAB Validators (OR, AND) – they do not display custom typed validator's nodes
    in context menu. Can You help to resolve this problem ?

  4. Ben27 במאי 2008 ב 13:09

    That was very useful, thanks!

  5. gazza11 באוגוסט 2008 ב 0:46

    Hi Guy,

    Thanks for the info. Your articles on VAB are really helpful.

    Had a query tho, if you specify the validators in configuration … can you get your app to pick changes to the validation config while its running?

    E.g. if I changed the upper limit of some range validator in config while the app is running, is it possible to have that change picked up without restart the app?

    thanks!

Comments are closed.