using System; using System.Collections.Generic; namespace Csla.Validation { /// /// Tracks the business rules broken within a business object. /// [Serializable()] public class ValidationRules { // list of broken rules for this business object. private BrokenRulesCollection _brokenRules; // threshold for short-circuiting to kick in private int _processThroughPriority; // reference to current business object [NonSerialized()] private object _target; // reference to per-instance rules manager for this object [NonSerialized()] private ValidationRulesManager _instanceRules; // reference to per-type rules manager for this object [NonSerialized()] private ValidationRulesManager _typeRules; // reference to the active set of rules for this object [NonSerialized()] private ValidationRulesManager _rulesToCheck; internal ValidationRules(object businessObject) { SetTarget(businessObject); } internal void SetTarget(object businessObject) { _target = businessObject; } private BrokenRulesCollection BrokenRulesList { get { if (_brokenRules == null) _brokenRules = new BrokenRulesCollection(); return _brokenRules; } } private ValidationRulesManager GetInstanceRules(bool createObject) { if (_instanceRules == null) if (createObject) _instanceRules = new ValidationRulesManager(); return _instanceRules; } private ValidationRulesManager GetTypeRules(bool createObject) { if (_typeRules == null) _typeRules = SharedValidationRules.GetManager(_target.GetType(), createObject); return _typeRules; } private ValidationRulesManager RulesToCheck { get { if (_rulesToCheck == null) { ValidationRulesManager instanceRules = GetInstanceRules(false); ValidationRulesManager typeRules = GetTypeRules(false); if (instanceRules == null) { if (typeRules == null) _rulesToCheck = null; else _rulesToCheck = typeRules; } else if (typeRules == null) _rulesToCheck = instanceRules; else { // both have values - consolidate into instance rules _rulesToCheck = instanceRules; foreach (KeyValuePair de in typeRules.RulesDictionary) { RulesList rules = _rulesToCheck.GetRulesForProperty(de.Key, true); List instanceList = rules.GetList(false); instanceList.AddRange(de.Value.GetList(false)); List dependancy = de.Value.GetDependancyList(false); if (dependancy != null) rules.GetDependancyList(true).AddRange(dependancy); } } } return _rulesToCheck; } } /// /// Returns an array containing the text descriptions of all /// validation rules associated with this object. /// /// String array. /// public string[] GetRuleDescriptions() { List result = new List(); ValidationRulesManager rules = RulesToCheck; if (rules != null) { foreach (KeyValuePair de in rules.RulesDictionary) { List list = de.Value.GetList(false); for (int i = 0; i < list.Count; i++) { IRuleMethod rule = list[i]; result.Add(rule.ToString()); } } } return result.ToArray(); } #region Short-Circuiting /// /// Gets or sets the priority through which /// CheckRules should process before short-circuiting /// processing on broken rules. /// /// Defaults to 0. /// /// All rules for each property are processed by CheckRules /// though this priority. Rules with lower priorities are /// only processed if no previous rule has been marked as /// broken. /// public int ProcessThroughPriority { get { return _processThroughPriority; } set { _processThroughPriority = value; } } #endregion #region Adding Instance Rules /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// public void AddInstanceRule(RuleHandler handler, string propertyName) { GetInstanceRules(true).AddRule(handler, new RuleArgs(propertyName), 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddInstanceRule(RuleHandler handler, string propertyName, int priority) { GetInstanceRules(true).AddRule(handler, new RuleArgs(propertyName), priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// /// Type of the business object to be validated. public void AddInstanceRule(RuleHandler handler, string propertyName) { GetInstanceRules(true).AddRule(handler, new RuleArgs(propertyName), 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// /// /// The priority of the rule, where lower numbers are processed first. /// /// Type of the business object to be validated. public void AddInstanceRule(RuleHandler handler, string propertyName, int priority) { GetInstanceRules(true).AddRule(handler, new RuleArgs(propertyName), priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// public void AddInstanceRule(RuleHandler handler, RuleArgs args) { GetInstanceRules(true).AddRule(handler, args, 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddInstanceRule(RuleHandler handler, RuleArgs args, int priority) { GetInstanceRules(true).AddRule(handler, args, priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// Type of the target object. /// Type of the arguments parameter. /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// public void AddInstanceRule(RuleHandler handler, R args) where R : RuleArgs { GetInstanceRules(true).AddRule(handler, args, 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// Type of the target object. /// Type of the arguments parameter. /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddInstanceRule(RuleHandler handler, R args, int priority) where R : RuleArgs { GetInstanceRules(true).AddRule(handler, args, priority); } #endregion #region Adding Per-Type Rules /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// public void AddRule(RuleHandler handler, string propertyName) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, new RuleArgs(propertyName), 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddRule(RuleHandler handler, string propertyName, int priority) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, new RuleArgs(propertyName), priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// public void AddRule(RuleHandler handler, string propertyName) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, new RuleArgs(propertyName), 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The propertyName may be used by the method that implements the rule /// in order to retrieve the value to be validated. If the rule /// implementation is inside the target object then it probably has /// direct access to all data. However, if the rule implementation /// is outside the target object then it will need to use reflection /// or CallByName to dynamically invoke this property to retrieve /// the value to be validated. /// /// /// The method that implements the rule. /// /// The property name on the target object where the rule implementation can retrieve /// the value to be validated. /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddRule(RuleHandler handler, string propertyName, int priority) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, new RuleArgs(propertyName), priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// public void AddRule(RuleHandler handler, RuleArgs args) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, args, 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddRule(RuleHandler handler, RuleArgs args, int priority) { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, args, priority); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// Type of the target object. /// Type of the arguments parameter. /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// public void AddRule(RuleHandler handler, R args) where R : RuleArgs { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, args, 0); } /// /// Adds a rule to the list of rules to be enforced. /// /// /// A rule is implemented by a method which conforms to the /// method signature defined by the RuleHandler delegate. /// /// Type of the target object. /// Type of the arguments parameter. /// The method that implements the rule. /// /// A RuleArgs object specifying the property name and other arguments /// passed to the rule method /// /// /// The priority of the rule, where lower numbers are processed first. /// public void AddRule(RuleHandler handler, R args, int priority) where R : RuleArgs { ValidateHandler(handler); GetTypeRules(true).AddRule(handler, args, priority); } private bool ValidateHandler(RuleHandler handler) { return ValidateHandler(handler.Method); } private bool ValidateHandler(RuleHandler handler) where R : RuleArgs { return ValidateHandler(handler.Method); } private bool ValidateHandler(System.Reflection.MethodInfo method) { if (!method.IsStatic && method.DeclaringType.Equals(_target.GetType())) throw new InvalidOperationException( string.Format("{0}: {1}", Properties.Resources.InvalidRuleMethodException, method.Name)); return true; } #endregion #region Adding per-type dependancies /// /// Adds a property to the list of dependencies for /// the specified property /// /// /// The name of the property. /// /// /// The name of the depandent property. /// /// /// When rules are checked for propertyName, they will /// also be checked for any dependant properties associated /// with that property. /// public void AddDependantProperty(string propertyName, string dependantPropertyName) { GetTypeRules(true).AddDependantProperty(propertyName, dependantPropertyName); } /// /// Adds a property to the list of dependencies for /// the specified property /// /// /// The name of the property. /// /// /// The name of the depandent property. /// /// /// If then a /// reverse dependancy is also established /// from dependantPropertyName to propertyName. /// /// /// When rules are checked for propertyName, they will /// also be checked for any dependant properties associated /// with that property. If isBidirectional is /// then an additional association /// is set up so when rules are checked for /// dependantPropertyName the rules for propertyName /// will also be checked. /// public void AddDependantProperty(string propertyName, string dependantPropertyName, bool isBidirectional) { ValidationRulesManager mgr = GetTypeRules(true); mgr.AddDependantProperty(propertyName, dependantPropertyName); if (isBidirectional) { mgr.AddDependantProperty(dependantPropertyName, propertyName); } } #endregion #region Checking Rules /// /// Invokes all rule methods associated with /// the specified property and any /// dependant properties. /// /// The name of the property to validate. public void CheckRules(string propertyName) { // get the rules dictionary ValidationRulesManager rules = RulesToCheck; if (rules != null) { // get the rules list for this property RulesList rulesList = rules.GetRulesForProperty(propertyName, false); if (rulesList != null) { // get the actual list of rules (sorted by priority) List list = rulesList.GetList(true); if (list != null) CheckRules(list); List dependancies = rulesList.GetDependancyList(false); if (dependancies != null) { for (int i = 0; i < dependancies.Count; i++) { string dependantProperty = dependancies[i]; CheckRules(rules, dependantProperty); } } } } } private void CheckRules(ValidationRulesManager rules, string propertyName) { // get the rules list for this property RulesList rulesList = rules.GetRulesForProperty(propertyName, false); if (rulesList != null) { // get the actual list of rules (sorted by priority) List list = rulesList.GetList(true); if (list != null) CheckRules(list); } } /// /// Invokes all rule methods for all properties /// in the object. /// public void CheckRules() { ValidationRulesManager rules = RulesToCheck; if (rules != null) { foreach (KeyValuePair de in rules.RulesDictionary) CheckRules(de.Value.GetList(true)); } } /// /// Given a list /// containing IRuleMethod objects, this /// method executes all those rule methods. /// private void CheckRules(List list) { bool previousRuleBroken = false; bool shortCircuited = false; for (int index = 0; index < list.Count; index++) { IRuleMethod rule = list[index]; // see if short-circuiting should kick in if (!shortCircuited && (previousRuleBroken && rule.Priority > _processThroughPriority)) shortCircuited = true; if (shortCircuited) { // we're short-circuited, so just remove // all remaining broken rule entries BrokenRulesList.Remove(rule); } else { // we're not short-circuited, so check rule if (rule.Invoke(_target)) { // the rule is not broken BrokenRulesList.Remove(rule); } else { // the rule is broken BrokenRulesList.Add(rule); if (rule.RuleArgs.Severity == RuleSeverity.Error) previousRuleBroken = true; } if (rule.RuleArgs.StopProcessing) shortCircuited = true; } } } #endregion #region Status Retrieval /// /// Returns a value indicating whether there are any broken rules /// at this time. /// /// A value indicating whether any rules are broken. internal bool IsValid { get { return BrokenRulesList.ErrorCount == 0; } } /// /// Returns a reference to the readonly collection of broken /// business rules. /// /// /// The reference returned points to the actual collection object. /// This means that as rules are marked broken or unbroken over time, /// the underlying data will change. Because of this, the UI developer /// can bind a display directly to this collection to get a dynamic /// display of the broken rules at all times. /// /// A reference to the collection of broken rules. public BrokenRulesCollection GetBrokenRules() { return BrokenRulesList; } #endregion } }