The base Adapdev namespace contains classes that cover several different areas, ranging from command line inputs to object validation and object comparison.
Object validation is often needed for objects - especially those in the Business Layer.
In order to accomplish validation, Adapdev provides an object for rules and for processing validation. Below is a UML diagram showing the various objects and their relationship.
The IValidationRule interface should be inherited by any class that is applying a rule. The IValidationRule simply takes the input and validates whether it meets the requirements. It then returns a ValidationResult. Below is an example of a rule for non-empty strings.
public class NonEmptyStringRule : IValidationRule
{
private string _s = String.Empty;
private string _message = String.Empty;
public NonEmptyStringRule(string s)
{
if(s != null) this._s = s;
else s = String.Empty;
}
#region IValidationRule Members
public ValidationResult Validate()
{
ValidationResult vr = new ValidationResult();
if(this._s.Length == 0)
{
vr.IsValid = false;
vr.AddMessage("String cannot be empty.");
}
return vr;
}
#endregion
}
This rule can then be processed by any class that implements IValidator including the built-in CompositeValidator class.
Any class that implements IValidator is one that processes an IValidationRule. Below is a simple example of a Customer object, which must have a CustomerId property set in order to be valid.
public class Customer : IValidator
{
private string _custId = "";
public string CustomerId
{
get{return this._custId;}
set{this._custId = value;}
}
public ValidationResult Validate()
{
NonEmptyStringRule rule = new NonEmptyStringRule(
this.CustomerId,
"CustomerId name cannot be empty.");
return rule.Validate();
}
}The one problem with IValidator is that it only supports the validation of one rule. In order to validate multiple rules, use the ICompositeValidator interface or the CompositeValidator implementation. The ICompositeValidator allows you to add multiple rules. The CompositeValidator implementation provides out of the box multi-rule validation.
public class Customer : CompositeValidator
{
private string _custId = "";
private string _name = "";
public string CustomerId
{
get{return this._custId;}
set{this._custId = value;}
}
public string Name
{
get{return this._name;}
set{this._name = value;}
}
public ValidationResult Validate()
{
this.AddRule(new NonEmptyStringRule(this.CustomerId, "CustomerId cannot be empty."));
this.AddRule(new NonEmptyStringRule(this.Name, "Name cannot be empty."));
return base.Validate();
}
}Below is an extended example that shows a Rectangle class, its validation rules, and a test to validate it.
Rectangle Class
public class Rectangle : CompositeValidator
{
public Rectangle()
{
this.AddRule(new RectangleNoSquareRule(this));
this.AddRule(new RectangleHeightRule(this));
this.AddRule(new RectangleWidthRule(this));
}
public int Height = 0;
public int Width = 0;
}
public class RectangleNoSquareRule : IValidationRule
{
private Rectangle _rectangle;
public RectangleNoSquareRule(Rectangle r)
{
this._rectangle = r;
}
#region IValidationRule Members
public ValidationResult Validate()
{
ValidationResult vr = new ValidationResult();
if(this._rectangle.Height == this._rectangle.Width)
{
vr.IsValid = false;
vr.AddMessage(CompositeValidatorTest.NoSquareMessage);
}
return vr;
}
#endregion
}
public class RectangleHeightRule : IValidationRule
{
#region IValidationRule Members
private Rectangle _rectangle;
public RectangleHeightRule(Rectangle r)
{
this._rectangle = r;
}
public ValidationResult Validate()
{
ValidationResult vr = new ValidationResult();
if(this._rectangle.Height <= 0)
{
vr.IsValid = false;
vr.AddMessage(CompositeValidatorTest.BadHeightMessage);
}
return vr;
}
#endregion
}
public class RectangleWidthRule : IValidationRule
{
#region IValidationRule Members
private Rectangle _rectangle;
public RectangleWidthRule(Rectangle r)
{
this._rectangle = r;
}
public ValidationResult Validate()
{
ValidationResult vr = new ValidationResult();
if(this._rectangle.Width <= 0)
{
vr.IsValid = false;
vr.AddMessage(CompositeValidatorTest.BadWidthMessage);
}
return vr;
}
#endregion
}Test
using System;
using System.Collections;
using NUnit.Framework;
using Adapdev;
namespace Adapdev.Tests
{
/// <summary>
/// Summary description for IValidatable.
/// </summary>
///
[TestFixture]
public class CompositeValidatorTest
{
public static string BadHeightMessage = "Height must be greater than zero.";
public static string BadWidthMessage = "Width must be greater than zero.";
public static string NoSquareMessage = "Must be a rectangle, not a square." +
"Please use the Square object for squares.";
[Test]
public void Invalid()
{
Rectangle rectangle = new Rectangle();
rectangle.Height = 2;
rectangle.Width = 2;
ValidationResult vr = rectangle.Validate();
Console.WriteLine(vr.Message);
Assert.IsFalse(vr.IsValid, "Object should not be valid.");
Assert.IsTrue(vr.Message.Length > 0);
Assert.IsTrue(vr.Message.IndexOf(CompositeValidatorTest.NoSquareMessage) >= 0);
Assert.AreEqual(CompositeValidatorTest.NoSquareMessage + Environment.NewLine +
Environment.NewLine, vr.Message);
}
[Test]
public void Valid()
{
Rectangle rectangle = new Rectangle();
rectangle.Height = 3;
rectangle.Width = 2;
ValidationResult vr = rectangle.Validate();
Assert.IsTrue(vr.IsValid);
Assert.IsTrue(vr.Message.Length == 0,
"Object should not have a validation message.");
}
}
}
The Microsoft .NET framework only provides limited abilities for object comparison. For example, the following will fail:
DateTime now = DateTime.Now; SuppliersEntity e1 = new SuppliersEntity(); e1.Address = "Test"; e1.SupplierID = 12; e1.Created = now; SuppliersEntity e2 = new SuppliersEntity(); e2.Address = "Test"; e2.SupplierID = 12; e2.Created = now; bool areEqual = e1.Equals(e2); // returns false
Although the values in the objects are exactly the same, the Equals method fails because of some low-level issues regarding the runtime stack and object references, etc. In order to deal with this, the ObjectComparer object was created to provide accurate object comparison.
One of the easier ways to compare objects is by property only, disregarding all fields.
ObjectComparer.ArePropertiesEqual(object1, object2);If we take the original example, the comparison will now pass.
DateTime now = DateTime.Now;
SuppliersEntity e1 = new SuppliersEntity();
e1.Address = "Test";
e1.SupplierID = 12;
e1.Created = now;
SuppliersEntity e2 = new SuppliersEntity();
e2.Address = "Test";
e2.SupplierID = 12;
e2.Created = now;
bool areEqual = ObjectComparer.ArePropertiesEqual(e1, e2); // returns true
You can also compare objects by field only, disregarding all properties.
ObjectComparer.AreFieldsEqual(object1, object2);You can also compare objects taking all fields and properties into account.
ObjectComparer.AreEqual(object1, object2); // compares fields and propertiesIf you want to compare two serializable objects, you use the built-in comparison which will do a byte-by-byte comparison.
// object1 and object2 must implement ISerializable or
// have the [Serializable] attribute
ObjectComparer.AreBytesEqual(object1, object2); // does binary comparison