|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionCorrect and comprehensive validation of the state of business objects is a critical requirement in the development of every data driven application. This article will demonstrate how to instrument POCOs (plain old C# objects) with support for validation rules using dependency injection at runtime. Implementation Objectives
Motivating ExampleI will not attempt to present a full-blown LOB application, rather I will provide a simple example that demonstrates the applicability of this approach. Let us suppose that we wish to model a person using an
The specification of the public interface IPerson {
string FirstName {
get;
set;
}
string LastName {
get;
set;
}
DateTime DateOfBirth {
get;
set;
}
}
We shall then create an The specification of the public abstract class Person : IPerson {
private string firstName;
private string lastName;
private DateTime dateOfBirth;
public virtual string FirstName {
get {
return firstName;
}
set {
firstName = value;
}
}
public virtual string LastName {
get {
return lastName;
}
set {
lastName = value;
}
}
public virtual DateTime DateOfBirth {
get {
return dateOfBirth;
}
set {
dateOfBirth= value;
}
}
}
Finally, we shall define two concrete classes The specification of the public class Employee : Person {
}
public class Customer : Person {
}
In a real-world application, the The Validation RulesWe shall require any class that implements the
For instances of the
Implementation of Validation RulesThe validation rules will be coded up in the class public class Validator {
// Returns KeyValuePair<true, IBrokenRule>
// if value has any non-letter characters
public KeyValuePair<bool, IBrokenRule> ValidateIsAlpha
(object instance, object value, object[] arguments) {
var brokenRule = new BrokenRule
("ValidateIsAlpha", "Only letters are allowed");
var stringValue = value as string;
if (stringValue != null && stringValue.Any(c => !char.IsLetter(c))) {
return new KeyValuePair<bool,IBrokenRule>(true, brokenRule);
}
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
// Returns KeyValuePair<true, IBrokenRule>
// if value is null or value == string.Empty
public KeyValuePair<bool, IBrokenRule>
ValidateIsNotBlank(object instance, object value, object[] arguments) {
var stringValue = (value as string);
var brokenRule = new BrokenRule("ValidateIsNotBlank", "Value cannot be blank");
if (stringValue == null || stringValue == string.Empty) {
return new KeyValuePair<bool, IBrokenRule>(true, brokenRule);
}
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
// Returns KeyValuePair<true, IBrokenRule> if value is not
// an integral number or value
// is not between the minimum and maximum values specified in
// arguments[0] and arguments[1]
public KeyValuePair<bool, IBrokenRule> ValidateIntegralNumberIsBetween
(object instance, object value, object[] arguments) {
double doubleValue;
double minValue;
double maxValue;
GetMinMaxValue(arguments, out minValue, out maxValue);
var brokenRule = new BrokenRule("ValidateIntegralNumberIsBetween",
string.Format("Value must be between {0} and {1}",
minValue, maxValue));
if (!double.TryParse((value.ToString()).Trim(), out doubleValue) ||
doubleValue < minValue || doubleValue > maxValue) {
return new KeyValuePair<bool, IBrokenRule>(true, brokenRule);
}
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
// Utility function to extract the minimum and maximum value from
// arguments[0] and arguments[1]
private static void GetMinMaxValue(object[] arguments,
out double minValue, out double maxValue) {
Debug.Assert(arguments != null && arguments.Length == 2);
Debug.Assert((arguments[0] as string) != null);
Debug.Assert((arguments[1] as string) != null);
var validMinValue = double.TryParse((arguments[0] as string).Trim(),
out minValue);
var validMaxValue = double.TryParse((arguments[1] as string).Trim(),
out maxValue);
Debug.Assert(validMinValue && validMaxValue);
}
// Returns KeyValuePair<true, IBrokenRule> if the length of value
// is not an integral number
// between the minimum and maximum values specified in
// arguments[0] and arguments[1]
public KeyValuePair<bool, IBrokenRule> ValidateStringLengthIsBetween
(object instance, object value, object[] arguments) {
double minValue;
double maxValue;
GetMinMaxValue(arguments, out minValue, out maxValue);
var brokenRule = new BrokenRule("ValidateStringLengthIsBetween",
string.Format("Value must have between
{0} and {1} characters",
minValue, maxValue));
var stringValue = value as string;
if (stringValue != null && stringValue != string.Empty &&
(stringValue.Length < minValue || stringValue.Length > maxValue)) {
return new KeyValuePair<bool, IBrokenRule>(true, brokenRule);
}
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
// Returns KeyValuePair<true, IBrokenRule> if the age
// (calculated using value) is not an integral number
// between the minimum and maximum values specified in
// arguments[0] and arguments[1]
public KeyValuePair<bool, IBrokenRule> ValidateAgeIsBetween
(object instance, object value, object[] arguments) {
double minValue;
double maxValue;
GetMinMaxValue(arguments, out minValue, out maxValue);
var brokenRule = new BrokenRule("ValidateAgeIsBetween",
string.Format("Age must be between {0} and {1}",
minValue, maxValue));
if (value == null) {
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
DateTime dateTime;
var isValid = DateTime.TryParse(value.ToString(), out dateTime);
if (!isValid ||
ValidateIntegralNumberIsBetween(instance, DateTime.Now.Year -
dateTime.Year, arguments).Key) {
return new KeyValuePair<bool, IBrokenRule>(true, brokenRule);
}
return new KeyValuePair<bool, IBrokenRule>(false, brokenRule);
}
}
Definition of the Application of Validation RulesThe definition of the application of validation rules will be specified in an XML file rules.xml. That will look as follows: <Rules>
<Class name="IPersonAndImplementations.IPerson, IPersonAndImplementations,
Version=1.0.0.0" >
<Property name="FirstName">
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateIsAlpha" arguments=""/>
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateStringLengthIsBetween" arguments="2,15"/>
</Property>
<Property name="LastName">
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateIsAlpha" arguments=""/>
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateIsNotBlank" arguments=""/>
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateStringLengthIsBetween" arguments="2,15"/>
</Property>
<Property name="DateOfBirth">
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateAgeIsBetween" arguments="1, 120"/>
</Property>
</Class>
<Class name="IPersonAndImplementations.Employee, IPersonAndImplementations,
Version=1.0.0.0, Culture=neutral" >
<Property name="FirstName">
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateIsNotBlank" arguments=""/>
</Property>
<Property name="DateOfBirth">
<Rule assemblyName="SampleRules, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
class="SampleRules.Validator" method="
ValidateAgeIsBetween" arguments="18, 65"/>
</Property>
</Class>
</Rules>
The schema for rule definition should be fairly self-explanatory, but will be covered in more detail in Part II of the series. Putting It All TogetherYou will have noticed that all the properties defined in the I have created a
I will provide further information on the implementation of the Seeing It In Action
The relevant lines of code to create the proxies and implement data binding are listed below: public frmEmployee() {
InitializeComponent();
var list = new LinkedList<object>();
// Create a proxy that is an instance of Person that has a
// dynamic implementation of IBrokenRuleConsumer,
// IDataErrorInfo, INotifyPropertyChanged, INotifyPropertyChanging
list.AddFirst(ProxyManager.Create<Employee>());
errorProvider = new ErrorProvider(this);
this.components = new System.ComponentModel.Container();
personBindingSource = new BindingSource(this.components);
((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).BeginInit();
personBindingSource.DataSource = list;
txtFirstName.DataBindings.Add(new Binding
("Text", this.personBindingSource, "FirstName", true));
txtLastName.DataBindings.Add(new Binding
("Text", this.personBindingSource, "LastName", true));
mtbDateOfBirth.DataBindings.Add(new Binding
("Text", this.personBindingSource, "DateOfBirth", true));
(list.First.Value as IBrokenRuleConsumer).EnforceConstraints();
errorProvider.BlinkStyle = ErrorBlinkStyle.NeverBlink;
errorProvider.DataSource = personBindingSource;
((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).EndInit();
}
They say a picture is worth a thousand words. Downloading and experimenting with the samples should be worth a million more. I hope you will enjoy. Cheers! History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||