Validation Rules
The Validation Rules library attempt to unify this disparate approach to validation, and to automate some of the simpler validation logic such as required fields and field length validation. Instead of putting validation through the tiers, we can simply implement IValidation on the business object. This interface attaches a meta list of rules to the entity which we can build in the business layer and will propagate through down to the UI layer. At the UI layer, it integrates with JQuery and the Validation plugin to enable client side validation, which is automatically handled via Formbinder, but can easily be applied any which way. Once the user has posted the data back, the rules flow back to the business tier where more complex validation can be applied which could not be performed at the client. this means you get the best of both worlds with 1 implementation. This also means all the validation is together, and can be separated out into its own layer.
Once the rules have been attached to the entity, they can be validated by simply calling the ValidateRules extension method which will validate each property on the entity against the collection of validation rules. This also adds the benefit of message uniformity and simpler management of error messaging.
Here is a sample of the how to implement the IValidation on the entity
public class Entity : IValidation
{
private Rules _Rules;
public Rules Rules
{
get
{
if (_Rules == null)
{
_Rules = new Rules();
}
return _Rules;
}
set
{
_Rules = value;
}
}
}
The implementation is very simple, and can be added to a base class to enable validation for all entities.
public Entity GetEntityValidationRules(Entity entity)
{
entity.Rules.AddRule("EntityId", "My Entity Name", true, true, 225, 1);
entity.Rules.AddConditionError<Entity>(p => p.MyProperty == "Error Condition","My Property","My Property satisfies my error condition message");
entity.Rules.AddCustomHandler("MyIntProperty", "Error with MyIntProperty", CheckMyInProperty);
entity.Rules.AddRule("ChildEntity", "My child entity", true, true, 0, 2);
entity.ChildEntity.Rules.AddRule("ChildEntityProperty", "My child entity Property", true, true, 0, 2);
return entity;
}
public bool CheckMyIntProperty(object target, ValidationRuleArgs e)
{
int iValue=0;
if (e.PropertyValue !=null && int.TryParse(e.PropertyValue.ToString, out iValue))
{
if (iValue != 15)
{
e.Description = "This value does not equal 15";
return true;
}
}
return false;
}
To Create and attach the rules to the entity is also relatively simple. you simple add you rule to each propertytext via the string . when the rules get validated, the extension method goes through the object and validates it against the rule. The AddRule method is overloaded in various ways to enable various signatures to set different properties.
Listed below are the properties you can set on the property rule.
string PropertyName { get; set; }
string DisplayName { get; set; }
string DisplayMessage { get; set; }
bool IsReadOnly { get; set; }
bool IsEnabled { get; set; }
bool IsRequired { get; set; }
bool CanHaveDefaultValue { get; set; }
bool IsValid { get; set; }
bool IsVisible { get; set; }
int FieldLength { get; set; }
int Order { get; set; }
object InitialValue { get; set; }
List<RegularExpressionRules> RegularExpressionRules { get; set; }
List<CustomHandlerRules> CustomHandlerRules { get; set; }
List<ConditionValidation> ConditionValidations { get; set; }
List<string> ErrorMessages { get; set; }
PropertyName is field which will join the entity property and the rule, so this must be named as the property is named. This match is case insensitive.
DisplayName is the text used in the default error messages, so sometime you might want to validate an entity Id but it you might want the error message to say something like "Cashpoint is required"
DisplayMessage contains any specific error message you would like to display instead of the default message. this is useful, but a little limited because it will not change as different rules get validated.
IsReadonly can be used on the UI to enable or disable a control based on some business rule. This also means the validation will not fire. for example, when the field is required, but also readonly, so you cant modify the value, then it should not be validated. just think of a field on a web page which is disabled, and the validation message says that it is required, Very annoying. This logic also means you can separate the business rules from the entity state rules, making for much cleaner validation meta data building. So instead of a set of if statements to add rules based on the state of an entity, you just add the rules to the entity every time and set the readonly flag based on the state of the entity. This also means the rules meta list is always the same, and the only thing that changes is the values within, making for much more stable validation usage.
IsEnabled is just implemented to return the inverse of the IsReadonly. this is purely used to be able to use the enabled (positive) flag instead of readonly (negative) flag, since people find it easier to think in the positive.
CanHaveDefaultValue basically tells the validation enginge, that if a property value is set to the default value (0 for int) it will not cause the validation to error. This can be used in conjunction with InitialValue to allow say a -1 cause a validation error.
IsValid is the flag which gets set after the validation has occured and the rule is not satisfied. this can be used on the client also to help get errored rules.
IsVisible can be used on the client to hide or show a field. The same logic as IsReadonly applies here, in that if this rule is invisible, then the rule will not fire.
FieldLength will check the length of the value in the property, this is real handy when you limits in the database. the only other way is to put a max length on the control, which then means you might need to change this value on several controls.
Order simply orderes the rules so that the error message list generated gets generated in some kind of order. However there is a limitation here in that when a child entity is validated, the rules will not be ordered based on the entire set, but just for the rules on the child.
RegularExpressionRules can attach a simple regular expression validation rule to the property. I must say, I have not used this one a lot, but that because the CustomHandler and ConditionValidation is so useful.
CustomHandlerRules can store a delegate to a function which can do some more complex kind of validation such as connecting to the database or some complex kind of mathamatical check. The limitation here is that delegate are not serialisable, so if you are storing your entity in viewstate, you'll lose the delegate and will need to rebuild your rules.
ConditionValidations can take in a delegate Expression to validate. This is handy when the rule is a little complex but can be done in 1 line, such as checking a date is greater then today.
ErrorMessage is basically the store where the errors get placed when the rules are in error.
public Entity GetEntity()
{
Entity entity=new Entity();
entity = GetEntityValidationRules(entity);
return entity;
}
public Entity SaveEntity(Entity entity)
{
entity = GetEntityValidationRules(entity);
List<string> error=new List<string>)();
error = entity.ValidateRules();
if(error.Count=0){
SaveMyEntityToDatabase(entity);
}
return entity;
}
To call the validateRules method you must add using Common.Validation.Extensions, this will let your method see the validation method. The process should be;Call the get method which generates your rules and adds them to your entity rules collection. Then these rules can be used on the client to hide show control, enable disable controls, and add validation messages or checks to controls. This integrates really well with the validation plugin using the form binder, as the form binder will use reflection to fill the control with the value from the entity, then it can check the rules and add the required validation logic to the control.
Once the user clicks save on the UI, it comes back to SaveEntity. We first rebuild our rules, so that the new rule set is based on the current state of the entity. Then we validate the rules, and if there are no errors we can persist the entity to the database, otherwise we can simple return the errors to the requestor.
Comments
Post a Comment