|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn Enterprise Library - Validation Application Block we have attributes to define complex validation expressions. But it is too complicated and slow, because it will use a lot of casting and boxing code under the hood. In C# 3.0, we have strongly typed lambda expressions so why not use them for validation logic? BackgroundImagine this business class: public class Foo
{
public string Name { get; set; }
public int Index { get; set; }
}
Let's say that the [Validatable]
public class Foo
{
Func< Foo, bool > NameRule = f => !string.IsNullOrEmpty(f.Name);
Func< Foo, bool > IndexRule = f => f.Index >= 0 && f.Index <= 100;
public string Name { get; set; }
public int Index { get; set; }
}
And we only need a solution to do the validation checks, this is why I am here for today. ;) The Validation LibraryAttributes
Properties (all get/set)
Examples[Validatable]
public class Foo
{
[Rule(Name = "NameRule")]
Func< Foo, bool > RName = f => !string.IsNullOrEmpty(f.Name);
[Rule(Name = "IndexRule", Enabled = false)
Func< Foo, bool > RIndex = f => f.Index >= 0 && f.Index <= 100;
public string Name { get; set; }
public int Index { get; set; }
}
[Validatable]
public class Foo
{
[Rule(AssociatedProperies = new string[] { "Name", "Index" })]
Func< Foo, bool > FooRule = f => !string.IsNullOrEmpty(f.Name) &&
f.Index >=0 && f.Index<=100;
public string Name { get; set; }
public int Index { get; set; }
}
Rule name will be " class Validator
This class validates any objects and using rules defined on them by Properties
Methods
Note: Enabled state context only applies to the Validator instance which these methods called on. For performance reasons, the Validator will not check whether the specified rule exists. It only registers these keys to an internal hashtable (see Validator.cs for implementation details). Events
[DataContract] public class ValidateResults : IEnumerable< ValidateResult >
This class describes a validation result Properties
Methods
[DataContract] public sealed class ValidateResult : IComparable< ValidateResult >
This class describes a validation fail. Properties
public struct RuleKey : IEquatable< RuleKey >, IComparable< RuleKey >
It is a closed structure. Overrides the Constructor
Example[Validatable]
public class Foo
{
Func< Foo, bool > NameRule = f => !string.IsNullOrEmpty(f.Name);
[Rule(Name = "IndexRule")
Func< Foo, bool > RIndex = f => f.Index >=0 && f.Index<=100;
public string Name { get; set; }
public int Index { get; set; }
}
// ...
RuleKey keyForNameRule = new RuleKey(typeof(Foo), "NameRule");
RuleKey keyIndexRule = new RuleKey(typeof(Foo), "IndexRule");
Properties
Using the CodeHere is a sample business class from our highly normalized partner registry service: [Validatable]
[DataContract]
[Serializable]
public class PublicPlaceModel : EntityModel
{
#region Rules
public static Func< PublicPlaceModel, bool > PublicPlaceUIDRule =
m => m.PublicPlaceUID != Guid.Empty;
public static Func< PublicPlaceModel, bool > SettlementUIDRule =
m => m.SettlementUID != Guid.Empty;
public static Func< PublicPlaceModel, bool > PublicPlaceNameIDRule =
m => m.PublicPlaceNameID > 0;
public static Func< PublicPlaceModel, bool > PublicPlaceTypeIDRule =
m => m.PublicPlaceTypeID > 0;
public static Func< PublicPlaceModel, bool > PostalCodeRule =
m => GeoRules.IsValidPostalCode(m.PostalCode);
//Complex business rule from two properties
[Rule(AssociatedProperties = new string[] { "PostalCode", "SettlementPart" })]
public static Func< PublicPlaceModel, bool > PublicPlaceStateRule =
m => GeoRules.CheckPublicPlaceState(m.PostalCode, m.SettlementPart);
#endregion
//Validated on base class
public Guid? PublicPlaceUID
{
get { return base.EntityUID; }
set { base.EntityUID = value; }
}
[DataMember]
public Guid SettlementUID;
[DataMember]
public int PublicPlaceNameID;
[DataMember]
public int PublicPlaceTypeID;
[DataMember]
public int? PostalCode;
[DataMember]
public string SettlementPart;
}
// Somewhere on service facade implementation:
// We have a request WCF message (message contract)
// which has a public property of type PublicPlaceModel:
// SomeResult, SomeRequest are WCF message contracts
public SomeResult DoSomething(SomeRequest request)
{
Validator v = new Validator();
v.ValidateToSOAPFault(request);
// If SOAP request is invalid a FaultException< ValidateResults >
// will be thrown and returned to consumer.
// Ok. Request is valid.
// Do stuff.
return result; // SomeResult message
}
Validating Complex Object GraphsIt is possible. Every object instance will be validated, but the property and rule paths will only indicate the first occurrence of failed validation. Rules will be checked in an object instance order by names followed by instance's properties and fields which type is Classes to be validated: [Validatable]
public class AB
{
public static Func< AB, bool > aRule = abc => abc.a != null;
public static Func< AB, bool > a2Rule = abc => abc.a2 != null;
public static Func< AB, bool > fooRule = abc => !string.IsNullOrEmpty(abc.foo);
public B[] bs;
public A a;
public A a2;
public string foo;
}
[Validatable]
public class A
{
public static Func< A, bool > bRule = ac => ac.b != null;
public B b;
}
[Validatable]
public class B
{
public static Func< B, bool > nameRule = cb => !string.IsNullOrEmpty(cb.name);
public string name;
public AB ab;
}
[Validatable]
public class C
{
public B b;
public A a;
}
Unit Test[TestMethod()]
public void ComplexObjectGraphTest()
{
// Make a complicated graph of object instances:
A aTest = new A { b = new B() };
A aTest2 = new A { b = new B() };
AB abTest = new AB { a = aTest, a2 = aTest2 };
C cTest = new C { b = aTest.b, a = aTest };
aTest.b.ab = abTest;
abTest.bs = new B[] { new B { name = "helo" }, new B { ab = abTest } };
// Create a validator instance:
Validator v = new Validator();
// Test 'em!
ValidateResults abResults = v.Validate(abTest);
Assert.IsFalse(abResults.IsValid);
// Check property paths. This will be same as rule paths so it will be enough.
// Two rule failed.
Assert.AreEqual(2, abResults.Results.Length);
// First instance occurrences using the search rule above:
// fooRule on foo field of AB class instance abTest.
Assert.AreEqual(1, abResults.Results[0].PropertyPaths.Length);
// 3 B class instance nameRule on name field.
Assert.AreEqual(3, abResults.Results[1].PropertyPaths.Length);
Assert.IsTrue(abResults.IsPropertyFailed("foo"));
Assert.IsTrue(abResults.IsPropertyFailed("a.b.name"));
Assert.IsTrue(abResults.IsPropertyFailed("a2.b.name"));
Assert.IsTrue(abResults.IsPropertyFailed("bs[1].name"));
// And so on with this logic:
//A Test
ValidateResults aResult = v.Validate(aTest);
Assert.IsFalse(aResult.IsValid);
Assert.AreEqual(2, abResults.Results.Length);
Assert.AreEqual(1, abResults.Results[0].PropertyPaths.Length);
Assert.AreEqual(3, abResults.Results[1].PropertyPaths.Length);
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.foo"));
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.a2.b.name"));
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.bs[1].name"));
Assert.IsTrue(aResult.IsPropertyFailed("b.name"));
//C Test
ValidateResults cResult = v.Validate(cTest);
Assert.IsFalse(cResult.IsValid);
Assert.AreEqual(2, abResults.Results.Length);
Assert.AreEqual(1, abResults.Results[0].PropertyPaths.Length);
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.foo"));
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.a2.b.name"));
Assert.IsTrue(aResult.IsPropertyFailed("b.ab.bs[1].name"));
Assert.IsTrue(aResult.IsPropertyFailed("b.name"));
}
If the object instance graph is a tree where every instance is identical (like WCF message contracts), this path information will be very useful of course. Validating IEnumerablesThis can be done. If you pass instances of // Path starts with an indexer:
Assert.AreEqual("[0].name", results.Results[0].PropertyPath[0]);
Assert.AreEqual("[2].name", results.Results[0].PropertyPath[1]);
Points of InterestThe implementation relies heavily on LINQ, so feel welcome to analyze it. For example, here is a code snippet of looking for rule expressions using reflection along with LINQ (ValidationRegistry.cs): private static RuleMetadata[] GetRulesOf(Type type)
{
// Get rule expression properties query :
var piQuery = from pi in type.GetProperties(BindingFlags.Public |
BindingFlags.DeclaredOnly |
BindingFlags.GetProperty |
BindingFlags.Instance |
BindingFlags.Static) // Reflect them
// Only looking for delegates
where pi.PropertyType.IsSubclassOf(typeof(Delegate))
// Getting built-in Invoke method
let invoke = pi.PropertyType.GetMethod("Invoke")
let pars = invoke.GetParameters() // Getting Invoke parameters
where invoke.ReturnType == typeof(bool) &&
pars.Length == 1 &&
pars[0].ParameterType == type // Only selecting Func< T, bool> ones
select new RuleMetadata(pi); // Generating metadata from property info
// Same query for fields :
var miQuery = from mi in type.GetFields(BindingFlags.Public |
BindingFlags.DeclaredOnly |
BindingFlags.GetField |
BindingFlags.Instance |
BindingFlags.Static)
where mi.FieldType.IsSubclassOf(typeof(Delegate))
let invoke = mi.FieldType.GetMethod("Invoke")
let pars = invoke.GetParameters()
where invoke.ReturnType == typeof(bool) &&
pars.Length == 1 &&
pars[0].ParameterType == type
select new RuleMetadata(mi);
// Run queries, concat the results, sort them and
// return an array from the result set.
return piQuery.Concat(miQuery).OrderBy(meta => meta.ruleKey).ToArray();
}
ConclusionFirst of all, sorry about my funny English (which is rather Hungrish). :) I've been reading tons of English documentation but I have not got enough opportunity to speak in it. I hope this will be understandable enough. There is nothing easier than using this code. Simply define I've included some common rule definitions to my project (email, URL, string length). See Rules.cs for details. E.g.: [Validatable]
public class SomeClass
{
public static Func< SomeClass, bool > EMailRule = sc => Rules.EmailRule(sc.EMail);
public string EMail { get; set; }
}
ReferencesHistory
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||