Introduction
This article is a step by step guide on how to write a custom validator using the 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 a 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 Validator - Validation Application Block, etc.
Create a New Provider Library
- In Visual Studio, create a new project of type Guidance Packages -> Application Block Software Factory -> Provider Library.
- This will start a wizard that will request some details from you:
- Name: The name of the solution and the name of the project that will contain the validator. I named it Bursteg.ProvidersLibrary,
and we'll come across this name later in this article.
- Description: A description that will be placed in the AssemblyInfo.cs of the providers library.
- Author: Your name.
- Namespace: Namespace of your choice.
- 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
two unit test projects.
Create the Custom Validator
Generate Initial Code for the Custom Validator
- Right click the providers project (Bursteg.ProvidersLibrary) and select Applciation Block Software Factory -> Validation Application Block -> New Validator (Typed).
- This will start another wizard that will request the name of the validator. For 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.
- Click Finish and Visual Studio will add the necessary references and generate the initial source code for this validator.
Notice that Bursteg.ProvidersLibrary has three 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 with 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.
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.
- Open 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; }
}
...
}
- Scroll down in this file and notice this commented code block:
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 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 exists in all the overloads of the constructor.
Finally, this is how the constructors should look like:
public PersonIDValidator(int country)
: this(country, null, false)
{ }
public PersonIDValidator(PersonIDValidatorData configuration)
: this(configuration.CountryCode, configuration.MessageTemplate,
configuration.Negated)
{
}
public PersonIDValidator(int country, bool negated)
: this(country, null, negated)
{ }
public PersonIDValidator(int country, string messageTemplate)
: this(country, messageTemplate, false)
{ }
public PersonIDValidator(int coutry, string messageTemplate, bool negated)
: base(messageTemplate, null, negated)
{
this.countryCode = coutry;
}
- Open PersonIDValidatorData.cs (under the Configuration folder) and locate the following commented line:
Add the country 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).
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; }
}
- Go back to PersonIDValidator.cs and modify the constructor that receives
PersonIDValidatorData
as parameter.
This constructor is called when the validator is configured in the configuration file. Simply assign the values from the configuration properties to the validator properties.
public PersonIDValidator(PersonIDValidatorData configuration)
: this(configuration.CountryCode, configuration.MessageTemplate,
configuration.Negated)
{
}
Notice the first parameter that this constructor overload receives.
- Open PersonIDValidatorAttribute.cs and add a data member for the country code.
private int coutryCode;
Locate the following commented code:
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.
- 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
- Go back to 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 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 objectToValidate
will be a string containing "123", same as the currentTarget
. key
will be 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);
Then objectToValidate will be "123", currentTarget
will be the person instance, and key
will be "PersonID".
Now that 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 makes sure it ends with the country code.
protected override void DoValidate(object objectToValidate, object currentTarget,
string key, ValidationResults validationResults)
{
string id = (string)objectToValidate;
string end = countryCode.ToString().Trim();
bool isValid = id.EndsWith(end);
if (isValid == Negated)
{
LogValidationResult(validationResults,
this.MessageTemplate, currentTarget, key);
}
}
Change the Message Templates of the Custom Validator
- When you log a validation error, you also add a message that contains the error description. By default the validator uses two 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.
This will open the Resource Editor and let you edit the messages. Notice that there are two message templates - PersonIDValidatorNegatedDefaultMessage
(for the negated
template) and PersonIDValidatorNonNegatedDefaultMessage
(for the non-negated template).
Add Design-Time support for the Custom Validator
When you develop a Custom Validator for your project, you'll probably want to add design-time support that will enable developers to 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 the Design-Time providers node is a recipe that is similar to all Enterprise Library providers and not something specific for each provider type.
- 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.
- 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 a 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 two PersonIDValidator
s validating a single property.
For this guide, it is Single
.
- Click Finish and Visual Studio will generate the code for the design-time configuration support.
Notice that Bursteg.ProvidersLibrary.Configuration.Design has four new files for it:
- PersonIDValidatorNode.cs contains the class that represents the node in the configuration.
- CommandRegistrar.cs, ConfigurationDesignManager.cs, and NodeMapRegistrar.cs contain the code that
registers the new configuration node to the configuration tree in the location we have selected earlier in the wizard.
- In step 14, we selected to place the validator node under
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 the AddSingleChildNodeCommand
method as many as you need
to add the validator parent nodes. Usually you will want to allow all four parent nodes, so you should copy it four times. The only difference between all the four invocations
is the last parameter which is the type of the parent node. In step 14, we selected SelfNode
, so the other three 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));
}
}
- 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
validatorPersonIDValidatorNodeUICommandLongText
- PersonID
validator long textPersonIDValidatorNodeUICommandText
- PersonID
validator command text
You'll soon see where each of these captions are displayed.
- 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 paste the code, remove the ValueValidatorData
property. You don't need it anymore.
- Change the constructor that receives
PersonIDValidatorData
as a parameter. Have it call the base class constructor with the parameter value.
public PersonIDValidatorNode(Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData data)
:base(data)
{
...
}
- 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
- 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; }
}
}
- 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.
- In the
Main
method, create a new instance of Person
and set its ID. Use the validator to check if the ID is valid.
Person p = new Person();
p.PersonID = "123";
PersonIDValidator validator = new PersonIDValidator(3);
ValidationResults results = validator.Validate(p.PersonID);
Debug.Assert(results.IsValid);
DisplayValidationResults(results);
validator = new PersonIDValidator(2);
results = validator.Validate(p.PersonID);
Debug.Assert(!results.IsValid);
DisplayValidationResults(results);
validator = new PersonIDValidator(3, true);
results = validator.Validate(p.PersonID);
Debug.Assert(!results.IsValid);
DisplayValidationResults(results);
validator = new PersonIDValidator(2, true);
results = validator.Validate(p.PersonID);
Debug.Assert(results.IsValid);
DisplayValidationResults(results);
Where the 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
- 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; }
}
}
- In the
Main
method, validate the Person
instance to check if the ID is valid.
results = Validation.ValidateFromAttributes<Person>(p);
DisplayValidationResults(results);
Test the Validator When Configured in the Configuration File
- Add a new app.config to your Console Application, and edit with the Enterprise Library Configuration Tool.
- Add the Validation Application Block Configuration Section. Right click the configuration file name node and choose New -> Validation Application Block.
- 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.
- 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.
- 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.
- The Custom Validator we created in this guide validates objects of type string that are IDs of people. In the
Person
type, we would like
to validate the Person
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.
- Add the
PersonIDValidator
to the PersonID
node. Right click the PersonID
node and select New-> PersonID Validator Command Text.
(Now you will see where the command text you edited in step 18 goes to.)
- 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.
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 any other out-of-the-box validator.
History