Chapter 11. Business and Validation Rules

I've now walked through several aspects of the CSLA .NET implementation, including an overview of the framework, base classes, property declarations, object status management, parent-child relationships, and data binding.

This chapter will focus on how CSLA .NET supports business and validation rules in a standardized manner. In several previous chapters, I've made reference to the framework's support for business and validation rules. The rules are invoked when a property is set on an editable business object through the SetProperty() helper method. The business developer can also invoke the rules by explicitly calling the CheckRules() method of the ValidationRules object.

As validation rules are broken and unbroken, they are added to and removed from a list of broken rules. Each editable business object always has a list of the currently broken rules. If this list is empty, IsSelfValid is true; otherwise, it is false. Not only is the list used to drive the IsSelfValid property, but it is also used by the IDataErrorInfo implementation for data binding support. The list of broken rules is exposed as a property from the business object, so the list itself can be data bound and displayed to the end user if desired.

The CSLA .NET framework includes the infrastructure necessary to associate business and validation rules with properties, and to invoke those rules at appropriate points in the object's life cycle. That is the focus of this chapter.

Types of Rules

In the CSLA .NET model, business and validation rules are implemented as methods. Each rule is contained in its own method. Rules are then associated with properties of business objects. The BusinessBase class includes code to invoke these rules at appropriate points in the object's lifetime. For example, rules for a property are invoked when that property is changed, or when a dependent property is changed. They are also invoked when a new instance of the object is created, and they can be invoked explicitly by your code.

Normally, rules are associated with business objects on a per-type basis. This means that all instances of a specific business object type (such as CustomerEdit) will have the same rules associated with the same properties. This is a good thing, because it means that the work of associating the rules to properties is done once for that type, and the information is reused across all instances of the type. That saves both processing time and memory.

As an alternative, it is also possible to associate rules with a business object on a per-instance basis. This means that each instance of the business object has its own rules. The associations are set up as the object instance is created, and they are stored in memory for that specific object. Obviously, this can cause performance and memory-consumption issues if you have many instances of a type, so you should avoid this approach as a general rule. CSLA .NET supports this concept, because there are rare cases when rules must vary on a per-instance basis.

Per-type rules must be accessible to all instances of the business object type. Normally, per-type rule methods are static, so they are always available to any object instance. It is also possible to implement per-type rule methods as instance methods of a global singleton object, though this is a more complex technique and offers no clear benefit over the simpler static method approach. In this book, I implement static rule methods and recommend that approach.

Per-instance rule methods only need to be accessible to the specific object instance where they are used. Normally, they are implemented as private instance methods in the business class, although they can also be static methods. I won't use any per-instance rules in this book, and I recommend against their use.

Given this basic background on per-type and per-instance rule methods, I'll now move on to discuss the business and validation subsystem in CSLA .NET, starting with a deeper exploration of rule methods and how you implement them.

Csla.Validation Namespace

The Csla.Validation namespace contains types that assist the business developer in implementing and enforcing business rules. This includes managing a list of business rules for each of the object's properties and maintaining a list of currently broken business rules.

Obviously, the framework can't implement the actual business rules and validation code—that will vary from application to application. However, business rules follow a very specific pattern in that they are either broken or not. The result of a rule being checked is a Boolean value and a human-readable description of why the rule is broken. This makes it possible to check the rules and then maintain a list of broken rules—including human-readable descriptions of each rule.

RuleHandler Delegate

Given that rules follow a specific pattern, it is possible to define a method signature that covers virtually all business rules. In .NET, a method signature can be formally defined using a delegate; here's the definition for a rule method:

public delegate bool RuleHandler(object target, RuleArgs e);

Every rule is implemented as a method that returns a Boolean result: true if the rule is satisfied, false if the rule is broken. The object containing the data to be validated is passed as the first argument, and the second argument is a RuleArgs object that you can use to pass extra rule-specific information. This means that a business rule in a business class looks like this:

private static bool CustNameRequired(object target, RuleArgs e)
    {
      if (string.IsNullOrEmpty(((Customer)target).Name))
      {
        e.Description = "Customer name required";
        return false;
      }
      else
        return true;
    }

If the length of the target object's Name property is zero, then the rule is not satisfied, so it returns false. It also sets the Description property of the RuleArgs object to a human-readable description of why the rule is broken.

This illustrates a rule that you could implement within a single business class. By using reflection, you could write entirely reusable rule methods that any business class can use. You'll see some examples of this in the "Common Validation Rules" section later in this chapter, when I discuss the CommonRules class.

Rule methods can also be generic and, thus, strongly typed. In that case, they follow this signature:

public delegate bool RuleHandler<T, R>(T target, R e) where R : RuleArgs;

There are a couple of variations on how to use this delegate type to create a rule method. The most common is to provide only the type of T.

private static bool CustNameRequired<T>(T target, RuleArgs e)
    where T : Customer
    {
    if (string.IsNullOrEmpty(target.Name))
      {
        e.Description = "Customer name required";
        return false;
      }
      else
        return true;
    }

The highlighted lines show the differences from the previous implementation. Notice how the method is generic, and the generic type T is constrained to the type of the business object. This allows the compiler to realize that the target parameter is of type Customer (or a subclass), so you're able to use all the properties of that class in the method implementation.

You can also supply both generic type parameters.

private static bool CustNameRequired<T, R>(T target, R e)
    where T : Customer, R : MyRuleArgsSubclass
    {
    if (target.Name.Length > e.MaxLength)
      {
      e.Description = "Customer name too long";
        return false;
      }
      else
        return true;
    }

This approach is less common, but it allows you to have strongly typed access to a custom subclass of RuleArgs that you've created for your rule method.

RuleArgs Class

The RuleHandler delegates specify the use of the RuleArgs object as a parameter to every rule method. This follows the general pattern used throughout .NET of passing an EventArgs parameter to all event handlers. Business rules aren't event handlers, so RuleArgs doesn't inherit from EventArgs, but it follows the same basic principle.

The goal is to be able to pass data into and out of the rule method in a clearly defined manner. At a minimum, RuleArgs passes the name of the property to be validated into the rule method, and it passes any broken rule description back out of the rule method. To do this, it simply contains a read-only PropertyName property and a read-write Description property.

More important is the fact that the author of a rule method can create a subclass of RuleArgs to provide extra information. For instance, implementing a maximum value rule implies that the maximum allowed value could be provided to the rule. To do this, the rule author would create a subclass of RuleArgs.

DecoratedRuleArgs Class

CSLA .NET includes one subclass of RuleArgs, called DecoratedRuleArgs. This class uses the decorator design pattern to attach an arbitrary list of name/value pairs to the RuleArgs class.

The intent of DecoratedRuleArgs is to make it possible to write rule methods that accept parameters, but without needing a custom RuleArgs subclass for each different rule method. To do this, DecoratedRuleArgs acts like a Dictionary, so it stores name/value pairs and allows them to be retrieved.

This simplifies the use of code generation for business classes, because all of your rule methods can accept exactly the same argument type, just with different name/value pairs in the list. For example, when associating a rule with a property where the rule needs a strongly typed argument, the code in the business object looks like this:

ValidationRules.AddRule(MyCustomRule,
      new MyCustomRuleArgs(NameProperty, "Arg1", "Arg2"));

To generate this code, a code generation tool would need to know the type of the custom RuleArgs subclass and the parameters needed for its constructor.

Using DecoratedRuleArgs, the code is more standardized.

var parameters = new Dictionary<string, object>();
    parameters.Add("FirstArg", "Arg1");
    parameters.Add("OtherArg", "Arg2");
    ValidationRules.AddRule(MyCustomRule,
      new DecoratedRuleArgs(NameProperty, parameters));

The code generator still needs to know the names of the parameters required by the rule method, but it doesn't need to know the type of a custom RuleArgs subclass, nor does it need to worry about the order of the parameters in a constructor or other details that make code generation difficult.

RuleMethod Class

The ValidationRules class maintains a list of rules for each property. This implies that ValidationRules has information about each rule method. This is the purpose of the RuleMethod classes. There are three classes: RuleMethod, RuleMethod<T, R>, and AsyncRuleMethod.

They all work much the same way, with minor variations that I'll discuss. Here's the declaration of the most basic RuleMethod class:

internal class RuleMethod :
    IRuleMethod, IComparable, IComparable<IRuleMethod>

It stores information about each rule, including a delegate reference to the rule method itself, a unique name for the rule, and any custom RuleArgs object that should be passed to the rule method. This information is stored in a set of fields with associated properties. The fields are declared like this:

private RuleHandler _handler;
  private string _ruleName = String.Empty;
  private RuleArgs _args;
    private int _priority;

The RuleMethod class is scoped as internal, as it is used by other classes in the Csla.Validation namespace, but shouldn't be used by code outside the framework.

The unique rule name associated with each rule is derived automatically by generating a URI with the rule:// prefix. The URI is created by combining the name of the rule method with the string representation of the RuleArgs object. The rule method is expressed as class/method to provide a unique value even if the same rule method name is used in different classes. The RuleArgs object, at a minimum, includes the target property name with which the rule is associated. It also includes any other argument values maintained by the RuleArgs object. For instance, a rule will appear as

rule://ruleClass/ruleMethod/targetProperty?arg1=value&arg2=value

When a RuleMethod object is created, it initializes its RuleName property value as follows:

_ruleName = string.Format(@"rule://{0}/{1}/{2}",
      Uri.EscapeDataString(_handler.Method.DeclaringType.FullName),
      _handler.Method.Name,
        _args.ToString());

The FullName property of the type includes the namespace and type name. Combining this with the name of the rule method provides unique identification of the specific rule method. Because the rule name must be unique, any custom subclasses of RuleArgs should be sure to override ToString() to return a value that includes any custom data that is part of the argument object in the form of

targetProperty?arg1=value&arg2=value&...

Note

The RuleDescription class in the Csla.Validation namespace can be used to parse the rule:// URI string into its constituent parts, much like you'd use System.Uri to parse any other URI.

When the business developer associates a rule method with a property, ValidationRules creates a RuleMethod object to maintain all this information. This RuleMethod object is what's actually associated with the property, thus providing all the information needed to invoke the rule when appropriate.

One interesting bit of information is a priority value. Each rule association has a priority with a default value of 0. When rules are invoked by CSLA .NET, they are invoked in priority order, starting with the lowest value and counting up. So priority 0 rules run first, then priority 1, 2, and so forth. A business developer can use this priority scheme to gain some control over the order in which the rules are invoked.

The priority concept is important, because it works in concert with a short-circuiting feature. This feature allows CSLA .NET to stop invoking rules of a higher priority if any lower-priority rule has failed. By default, if any priority 0 rule fails, then no priority 1 or higher rules will be invoked. All priority 0 rules always run in any case.

There's also a generic version of RuleMethod, which is declared like this:

internal class RuleMethod<T, R>
  : IRuleMethod, IComparable, IComparable<IRuleMethod>
    where R : RuleArgs

It does the same thing as RuleMethod, but it stores strongly typed values based on the generic type parameters.

And there's the asynchronous version, which is declared like this:

internal class AsyncRuleMethod
    : IAsyncRuleMethod, IComparable, IComparable<IRuleMethod>

This is the same as RuleMethod, but it is designed to store a reference to an asynchronous rule method. That's a rule method that runs code on a background thread and calls back into ValidationRules when it is complete to report on whether the condition is met or the rule is broken.

All RuleMethod classes implement IRuleMethod, so CLSA .NET can treat them polymorphically. In particular, as rules are associated with properties of a business object, the IRuleMethod objects are maintained in a list for each property, and that list uses the IRuleMethod type to do this.

Any type of RuleMethod object handles the invocation of the rule method itself by exposing an Invoke() method.

public bool Invoke(object target)
    {
      return _handler.Invoke(target, _args);
      }

When ValidationRules is asked to check the business rules, it merely loops through its list of RuleMethod objects, asking each one to invoke the rule it represents. As you can see, the Invoke() method simply invokes the method via the delegate reference, passing in a reference to the object to be validated (the business object) and the RuleArgs object associated with the rule.

If you look at the declarations of RuleMethod and RuleMethod<T, R>, you'll see that they both implement IComparable<T>. This interface is a standard .NET interface that you can implement to control how your objects are compared for operations such as sorting.

This is important for these objects, because they will be sorted, and that sorting must be based on the value of the Priority property. To this end, here's the implementation of the interface:

int IComparable.CompareTo(object obj)
  {
    return Priority.CompareTo(((IRuleMethod)obj).Priority);
  }

  int IComparable<IRuleMethod>.CompareTo(IRuleMethod other)
  {
    return Priority.CompareTo(other.Priority);
    }

This ensures that when the rules are sorted, they'll be sorted in ascending order by priority.

RuleDescription Class

As I discussed earlier, rules are described by use of a URI. A rule URI looks like this:

rule://ruleClass/ruleMethod/targetProperty?arg1=value&arg2=value

The RuleDescription class understands how to parse a rule:// URI for easier use. Rather than manually writing code to parse the URI, or using the more generic System.Uri class, you can use the RuleDescription class to easily get at the parts of the URI.

Csla.Validation.RuleDescription desc = new Csla.Validation.RuleDescription(
        "rule://typeName/methodName/propertyName?arg1=value&arg2=value");
      string scheme = desc.Scheme;
      string methodTypeName = desc.MethodTypeName;
      string methodName = desc.MethodName;
      string propertyName = desc.PropertyName;
      List<string> args = new List<string>();
      foreach (var item in desc.Arguments)
        args.Add(item.Key + ", " + item.Value);

Table 11-1 lists the properties available from RuleDescription.

This class is particularly useful to UI framework authors, as it allows a UI framework to easily parse the rules associated with a business object.

Table 11.1. Properties Available from RuleDescription

Property

Description

Scheme

Returns the URI scheme, which is always rule://

MethodTypeName

Returns the type that implements the rule method

MethodName

Returns the rule method name specified in the URI

PropertyName

Returns the name of the business object property with which this rule is associated

Arguments

Returns a Dictionary of name/value pairs representing the arguments passed to the rule method

ValidationRules Class

The ValidationRules class is the primary class in the Csla.Validation namespace. Every business object that uses the validation rules functionality will contain its own ValidationRules object. ValidationRules relies on the other classes in Csla.Validation to do its work. Together, these classes maintain the list of rules for each property and the list of currently broken rules.

ValidationRulesManager and SharedValidationRulesManager Classes

You've already seen how a business rule is defined based on the RuleHandler delegate. A key part of what ValidationRules does is to keep a list of such rule methods for each of the business object's properties. To do this, it relies on two other classes: ValidationRulesManager and SharedValidationRulesManager.

The ValidationRulesManager keeps a list of rules for each property. One way that ValidationRulesManager is used is to keep a list of rules that is unique for each instance of a business object. This means that each time a business object is created, a set of rules can be associated with the properties of that particular instance. This has a pretty big impact on performance and memory consumption, and it's usually not the right approach. However, sometimes objects really do need unique rules, and this class enables that scenario.

Another way ValidationRulesManager is used is by the SharedValidationRulesManager, as it keeps a list of rules for each business type. The SharedValidationRulesManager keeps a list of rules for each property that is common across all instances of a business object type. This means that the first time an instance of a given business class is created, the rules are associated with the properties of that type. The resulting associations are cached for the lifetime of the application and are shared by all instances of that business class. This has good performance and minimal memory consumption, and it's the recommended approach.

Note

CLSA .NET version 2.1 introduced the shared rules concept in response to performance and memory issues caused by the original per-instance rule technique.

As you'll see in Chapter 17 when I create the example business library, I typically only use shared rules.

Shared Rules and Threading

The big challenge with sharing a list of validation rules across all instances of a business type is that those instances could be running in parallel on different threads. In particular, on a web or application server, many client requests may be running simultaneously, and they all need access to that same set of cached RuleMethod objects.

Within SharedValidationRules, a Dictionary is used to cache all the rules for all the business object types.

private static Dictionary<Type, ValidationRulesManager> _managers =
      new Dictionary<Type, ValidationRulesManager>();

It is indexed by Type, which is the type of the business objects used by the application. Each business object type has its own ValidationRulesManager, which stores the rules for that particular business type.

To safely gain access to the ValidationRulesManager object for a type, the GetManager() method is used.

internal static ValidationRulesManager GetManager(
    Type objectType, bool create)
  {
    ValidationRulesManager result = null;
    if (!_managers.TryGetValue(objectType, out result) && create)
    {
      lock (_managers)
      {
        if (!_managers.TryGetValue(objectType, out result))
        {
          result = new ValidationRulesManager();
          _managers.Add(objectType, result);
        }
      }
    }
    return result;
  }

This method implements a simple but effective locking scheme. Remember that multiple threads may be executing this code at exactly the same time, and those threads may all attempt to get a value from the Dictionary.

if (!_managers.TryGetValue(objectType, out result) && create)

If this succeeds, then there's no problem. Multiple threads can read from the Dictionary at once without causing an issue. Things get more complex if the result field comes back as null, because that means a new ValidationRulesManager must be added to the Dictionary. Only one thread can be allowed to do this, so the lock statement is used to ensure that only one thread can run the next bit of code at a time.

Note my careful choice of words: only one at a time. Many threads may run the code inside the lock statement, because many threads may have gotten a null value in result. So the code in the lock statement must ensure that only the first thread does any real work. It does this by rechecking to see if the value is in the Dictionary.

if (!_managers.TryGetValue(objectType, out result))
          {
            result = new ValidationRulesManager();
            _managers.Add(objectType, result);
          }

Only the first thread gets a null value for result here, and then it add a new value to the Dictionary. Every subsequent thread gets a non-null value for result, which is the desired outcome.

Of course, the lock statement is hit only when the application is first run. Once this process completes for a business type, that first TryGetValue() always returns the requested value, so no locking occurs for the rest of the application's lifetime.

Associating Rules with Properties

The ValidationRulesManager stores the actual relationships between rules and properties for a specific business object type or instance. These values are stored in a Dictionary, which is indexed by the property name and contains a list of rules for each property.

private Dictionary<string, RulesList> _rulesList;

The RulesList object contains a list of IRuleMethod objects associated with the property, and a list of properties that are dependent on this property. A dependent property is one where its rules are checked any time this property's rules are checked. For example, if property B is dependent on property A, and you check the rules for property A, then the rules for property B are also checked automatically.

In RulesList then, there are two lists.

private List<IRuleMethod> _list = new List<IRuleMethod>();
    private List<string> _dependentProperties;

When a rule is associated with a property, the IRuleMethod object for that association is added to _list. When a dependent property is added to this property, the name of the dependent property is added to _dependentProperties.

While the rules may be added in any order, you must sort them before you use them. This is important, because you must invoke them in priority order, from priority 0 to 1 to 2 and so forth. This allows the business developer to have some control over the order in which the rules execute, and it enables the concept of short-circuiting.

Short-circuiting is a feature that stops the processing of rules partway through. The result is that not all rules for a property are invoked. There are two ways to short-circuit rule processing for a property: a rule method can stop the processing explicitly, or CLSA .NET can be told to stop processing rules if any previous (higher-priority) rule has already returned false.

This feature is useful, because it allows the business developer to check all the inexpensive, easily checked rules first and only invoke expensive rules (such as those that might hit the database) if all previous rules were satisfied (returned true). The rule priority feature is a key part of this capability, because it allows the business developer to control the order in which rules are invoked.

When the list of rules is retrieved, a flag is checked to see if the list has been sorted. If it has not been sorted, the list is sorted and the flag is set to true. Since multiple threads could request the list at the same time, this code is protected with a lock scheme similar to the one I discussed earlier.

public List<IRuleMethod> GetList(bool applySort)
  {
    if (applySort && !_sorted)
    {
      lock (_list)
      {
        if (applySort && !_sorted)
        {
          _list.Sort();
          _sorted = true;
        }
      }
    }
    return _list;
  }

The result is that the ValidationRules object always gets a list of IRuleMethod objects for a property, sorted by priority.

The combination of the RuleMethod class, the Dictionary and List object combination, and the AddRule() methods covers the management of the rules associated with each property.

Checking Validation Rules

Once a set of rule methods has been associated with the properties of a business object, there needs to be a way to invoke those rules. Typically, when a single property is changed on a business object, only the rules for that property need to be checked. At other times, the rules for all the object's properties need to be checked. This is true when an object is first created, for instance, since multiple properties of the object could start out with invalid values.

To cover these two cases, ValidationRules implements two CheckRules() methods.

Checking Rules for One Property

The first checks the rules for a specific property.

public void CheckRules(Csla.Core.IPropertyInfo propertyInfo)
  {
    CheckRules(propertyInfo.Name);
  }

  public string[] CheckRules(string propertyName)
  {
    if (_suppressRuleChecking)
      return new string[] {};

    var result = new List<string>();
    result.Add(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<IRuleMethod> list = rulesList.GetList(true);
        if (list != null)
          CheckRules(list);
        List<string> dependencies = rulesList.GetDependencyList(false);
        if (dependencies != null)
        {
          for (int i = 0; i < dependencies.Count; i++)
          {
            string dependentProperty = dependencies[i];
            result.Add(dependentProperty);
            CheckRules(rules, dependentProperty);
          }
}
      }
    }
    return result.ToArray();
    }

There's a lot going on here, so I'll break it down.

There are two overloads: one takes an IPropertyInfo, and the other is a simple string. When a business object calls this method directly, the business developer will typically provide an IPropertyInfo, but internally all the work is done based on the property name as a string value.

Note

The only reason the overload accepting a string parameter is public is for backward compatibility with older versions of CLSA .NET.

The first thing the method does is check to see if rule checking is suppressed:

if (_suppressRuleChecking)
        return new string[] {};

A business object can set ValidationRules.SuppressRuleChecking to true to prevent CheckRules() from doing any work. This is often useful when a lot of interdependent properties must be loaded all at once (such as behind a web page or XML service). In that case, an explicit call to CheckRules() is typically made after all property values have been loaded so the rules can be executed in a more efficient manner.

The methods return a string array. That array contains a list of the property names for which rules were checked. If a rule has dependent properties, then this call may check the rules for more than one property. The code in BusinessBase uses this string array to determine what PropertyChanged events should be raised, as I discussed in Chapter 10.

Of course, it is clear that at least the requested property's rules will be checked.

var result = new List<string>();
      result.Add(propertyName);

This method gets the list of rules for this property by calling the RulesToCheck property.

ValidationRulesManager rules = RulesToCheck;
      if (rules != null)

The RulesToCheck property is interesting, because it provides a consolidated list of the rules for this property. The list is a combination of the per-instance and per-type rules. Usually only per-type rules exist, but if there are per-instance rules, they are merged into the list as well, and the list is sorted by priority. Look at the property in the ValidationRules class to see how this is done.

Obviously, CheckRules() only continues to do work if the rules field is not null; if it is null, then no rules are associated with this property and the method can just exit. Assuming there are rules for this property, the list of rules is retrieved from the ValidationRulesManager, and the GetList() method is used to get the sorted list of IRuleMethod objects.

RulesList rulesList = rules.GetRulesForProperty(propertyName, false);
        if (rulesList != null)
        {
          // get the actual list of rules (sorted by priority)
          List<IRuleMethod> list = rulesList.GetList(true);
          if (list != null)
            CheckRules(list);

The sorted list is passed to another overload of CheckRules(). That overload is responsible for looping through the list and invoking each rule. It is also responsible for adding and removing items from the list of broken rules, which I'll discuss later in this chapter.

Finally, if there are any dependent properties associated with the current property, their rules are checked too.

for (int i = 0; i < dependencies.Count; i++)
            {
              string dependentProperty = dependencies[i];
              result.Add(dependentProperty);
              CheckRules(rules, dependentProperty);
            }

Another overload of CheckRules() is called here. It simply checks the rules for one specific property, without doing further checks for dependent properties. In other words, the dependent property concept isn't recursive. That's important because otherwise this code would have to check for circular dependency loops, and it would become too easy for a business developer to accidentally trigger checking too many properties when one property is changed.

Checking Rules for All Properties

The other public overload of CheckRules() allows the business developer to request that the rules for all properties be invoked. This method is commonly called when an object is first created and sometimes right after an object is loaded with data from the database. It is also commonly called when rule checking is suppressed, as a lot of properties are loaded. All rules are checked when the load process is complete.

This method is relatively short.

public void CheckRules()
  {
    if (_suppressRuleChecking)
      return;

    ValidationRulesManager rules = RulesToCheck;
    if (rules != null)
    {
      foreach (KeyValuePair<string, RulesList> de in rules.RulesDictionary)
        CheckRules(de.Value.GetList(true));
    }
    }

Like the property-specific overload, this one honors the SuppressRuleChecking property and immediately exits if rule checking is suppressed.

If rule checking is enabled, which is the default, then it retrieves all the rules for the entire object by calling RulesToCheck, and it loops through each entry in the Dictionary, executing the list of rules for each property.

At this point, it should be clear how ValidationRules associates rule methods with properties and is then able to check those rules for a specific property or for the business object as a whole.

Maintaining a List of Broken Rules

The ValidationRules object also maintains a list of currently broken validation rules for the object. This is used to implement IDataErrorInfo in BusinessBase, allowing each business object to easily indicate whether the object is valid. Because the broken rule objects in the list include the property name that is invalid, the broken rules list is also used to determine whether each individual property is valid or invalid.

The BrokenRulesCollection class maintains the list of broken rules for a business object, and is declared in ValidationRules like this:

private BrokenRulesCollection _brokenRules;

  private BrokenRulesCollection BrokenRulesList
  {
    get
    {
      if (_brokenRules == null)
        _brokenRules = new BrokenRulesCollection();
      return _brokenRules;
    }
    }

Notice that the _brokenRules field is not adorned with either the NotUndoable or NonSerialized attributes. The list of currently broken rules is directly part of a business object's state, so it is subject to n-level undo operations and to being transferred across the network along with the business object.

This way, if a business developer transfers an invalid object across the network or makes a clone, the object will remain invalid, with its list of broken rules intact.

The BrokenRulesList value is also exposed via a public method on BusinessBase. To any external consumer, such as code in the UI, this is a read-only collection.

[Browsable(false)]
  [EditorBrowsable(EditorBrowsableState.Advanced)]
  public virtual Validation.BrokenRulesCollection BrokenRulesCollection
  {
    get { return ValidationRules.GetBrokenRules(); }
    }

The reason the collection is exposed publicly is to allow UI developers to use the list of broken rules as they see fit. Remember that a broken rule includes a human-readable description of the rule, so it is perfectly reasonable to display this list to the end user in some circumstances.

BrokenRule Class

When a rule method is invoked by the CheckRules() method, it returns true or false. If it returns false, the broken rule will be recorded into a BrokenRulesCollection. Here's the code from CheckRules() that implements this behavior:

lock (BrokenRulesList)
          {
            if (ruleResult)
            {
              // the rule is not broken
              BrokenRulesList.Remove(rule);
            }
            else
            {
              // the rule is broken
              BrokenRulesList.Add(rule);
              if (rule.RuleArgs.Severity == RuleSeverity.Error)
                previousRuleBroken = true;
            }
            }

The BrokenRulesCollection object exposed by the BrokenRulesList property contains a list of BrokenRule objects, each representing a single broken business rule. The Add() and Remove() methods accept an IRuleMethod and create the BrokenRule object internally.

The BrokenRule object exposes read-only properties for the rule name, a human-readable description of the broken rule, and the name of the property that is broken. The class is available in the code download for the book, available at both www.apress.com/book/view/1430210192 and www.lhotka.net/cslanet/download.aspx.

BrokenRulesCollection Class

The BrokenRulesCollection class is used by ValidationRules to maintain the list of currently broken rules. Each broken rule is represented by a BrokenRule object. The collection inherits from Csla.Core.ReadOnlyBindingList and so is a read-only collection.

[Serializable()]
  public class BrokenRulesCollection : Core.ReadOnlyBindingList<BrokenRule>

Though the collection is read-only, it does provide some internal methods to allow ValidationRules to add and remove items. These methods are used in the CheckRules() methods to ensure that broken rules are only in the list when appropriate.

The Add() methods are pretty straightforward.

internal void Add(IAsyncRuleMethod rule, AsyncRuleResult result)
  {
    Remove(rule);
    IsReadOnly = false;
    BrokenRule item = new BrokenRule(rule, result);
    IncrementCount(item);
    Add(item);
    IsReadOnly = true;
  }

  internal void Add(IRuleMethod rule)
  {
    Remove(rule);
    IsReadOnly = false;
    BrokenRule item = new BrokenRule(rule);
    IncrementCount(item);
    Add(item);
    IsReadOnly = true;
    }

Both overloads do essentially the same thing, but for asynchronous and synchronous rule methods.

To avoid possible duplicate object issues, they first ensure that the broken rule isn't already in the list by calling the Remove() method. Then they change the collection to be read-write, add the rule to the collection, and set the collection back to be read-only.

While they could just see if the collection contains the broken rule, removing and re-adding the rule is better, because it ensures that the human-readable description for the rule is current. The rule method could have changed the description over time.

The IncrementCount() method is used to maintain counters for the number of Error, Warning, and Information severity rules that are broken. Each broken rule has one of these severities, and as an optimization, BrokenRulesCollection maintains a running count of the number of each severity that is broken at any point in time.

The Remove() method is a bit more complex.

internal void Remove(IRuleMethod rule)
  {
    IsReadOnly = false;
    for (int index = 0; index < Count; index++)
      if (this[index].RuleName == rule.RuleName)
      {
        DecrementCount(this[index]);
        RemoveAt(index);
        break;
      }
    IsReadOnly = true;
    }

It has to scan through the collection to find a rule with the same rule name. Notice that no exception is thrown if the item isn't in the collection. If it isn't there, that's fine—then there's just no need to remove it.

The DecrementCount() method is used to maintain counters for the number of Error, Warning, and Information severity rules that are broken. Each broken rule has one of these severities, and as an optimization, BrokenRulesCollection maintains a running count of the number of each severity that is broken at any point in time.

There are a few other methods in BrokenRulesCollection worth mentioning. They provide information about the contents of the collection and are listed in Table 11-2.

Table 11.2. Methods and Properties Providing Information About Broken Rules

Property/Method

Description

ErrorCount

Returns the current number of Error severity broken rules

WarningCount

Returns the current number of Warning severity broken rules

InformationCount

Returns the current number of Information severity broken rules

GetFirstMessage(string)

Scans the list and returns the first broken rule of any severity (if any) for a specified property

GetFirstMessage(string, RuleSeverity)

Scans the list and returns the first broken rule of the specified severity (if any) for a specified property

GetFirstBrokenRule(string)

Scans the list and returns the first broken rule of Error severity (if any) for a specified property; this method is used in BusinessBase to implement the IDataErrorInfo interface

ToArray()

Returns an array of broken rule descriptions for the business object

ToArray(RuleSeverity)

Returns an array of broken rule descriptions for a specific severity of broken rules in the business object

ToString()

Concatenates the human-readable descriptions of all broken rules into a single string value, using Environment.NewLine as a separator; this too is used in the IDataErrorInfo implementation to return all the errors for the entire object

ToString(string)

Concatenates the human-readable descriptions of all broken rules into a single string value, using the string parameter as a separator; this is used in the IDataErrorInfo implementation to return all the errors for the entire object

ToString(string, RuleSeverity)

Concatenates the human-readable descriptions of all broken rules of the specified severity into a single string value, using the string parameter as a separator

All of these methods are available to the business and UI developer, and provide a great deal of flexibility for use of the broken rules information.

ValidationException

The ValidationException class allows CLSA .NET to throw a custom exception to indicate that a validation problem has occurred. This exception is thrown by the Save() method in BusinessBase.

This exception class doesn't add any new information to the base Exception class from the .NET Framework. Thus, its code is very simple, since it merely declares a set of constructors, each of which delegates to the Exception base class. You can look at the class in the code download for the book.

The reason ValidationException exists is to allow UI code to easily catch a ValidationException as being separate from other exceptions that might be thrown by the Save() method. For instance, UI code might look like this:

try
  {
    customer = customer.Save();
  }
  catch (ValidationException ex)
  {
    // handle validation exceptions
  }
  catch (Exception ex)
  {
    // handle other exceptions
  }

Even if they offer no extra information, custom exceptions are often valuable in this way.

You should now have a high-level understanding of how ValidationRules consolidates the association of rules with properties, and the tracking of broken rules. It provides a single entry point for use of the business and validation rule subsystem. You'll see this used in Chapter 17, and you'll see examples of some rule methods in the next section of this chapter.

Common Validation Rules

Most applications use a relatively small, common set of validation rules—such as that a string value is required or has a maximum length, or that a numeric value has a minimum or maximum value. Using reflection, it is possible to create highly reusable rule methods—which is the purpose behind the Csla.Validation.CommonRules class.

Obviously, using reflection incurs some performance cost, so these reusable rule methods may or may not be appropriate for every application. However, the code reuse offered by these methods is powerful, and most applications won't be adversely affected by this use of reflection. In the end, whether you decide to use these rule methods or not is up to you.

Tip

If reflection-based rules are problematic for your application, you can implement hard-coded rule methods on a per-type basis.

If you find the idea of these reusable rules appealing and useful, you may opt to create your own library of reusable rules as part of your application. In that case, you can add a class to your project similar to CommonRules, and you can use the rule methods from CommonRules as a guide for building your own reusable rule methods.

CommonRules

The RuleHandler delegate specifies that every rule method accepts two parameters: a reference to the object containing the data, and a RuleArgs object that is used to pass extra information into and out of the rule method.

The base RuleArgs object has a PropertyName property that provides the rule method with the name of the property to be validated. It also includes a Description property that the rule method should set for a broken rule to describe why the rule was broken.

Table 11-3 lists the methods in the CommonRules class.

Table 11.3. Methods in the CommonRules Class

Method

Description

StringRequired

Ensures a string value is non-null and has a length greater than zero

StringMinLength

Ensures a string value has a minimum length

StringMaxLength

Ensures a string value doesn't exceed a maximum length

IntegerMinValue

Ensures an int value meets a minimum value

IntegerMaxValue

Ensures an int value doesn't exceed a maximum value

MinValue

Ensures any numeric value meets a minimum value

MaxValue

Ensures any numeric value doesn't exceed a maximum value

RegEx

Ensures a string value matches a regular expression

You can look at the code for each of these in the code download, but I do want to walk through a couple of them to explain how these methods work.

StringRequired

The simplest type of rule method is one that doesn't require any information beyond that provided by the basic RuleArgs parameter. For instance, the StringRequired() rule method only needs a reference to the object containing the value and the name of the property to be validated.

public static bool StringRequired(object target, RuleArgs e)
  {
    string value = (string)Utilities.CallByName(
      target, e.PropertyName, CallType.Get);
    if (string.IsNullOrEmpty(value))
    {
      e.Description = string.Format(
        Resources.StringRequiredRule, RuleArgs.GetPropertyName(e));
      return false;
    }
    return true;
    }

A CallByName() helper method is used to abstract the use of reflection to retrieve the property value based on the property name. It simply uses reflection to get a PropertyInfo object for the specified property, and then uses it to retrieve the property value.

If the property value is null or is an empty string, then the rule is broken, so the Description property of the RuleArgs object is set to describe the nature of the broken rule. Then false is returned from the rule method to indicate that the rule is broken. Otherwise, the rule method simply returns true to indicate that the rule is not broken.

Notice the use of the GetPropertyName() method on the RuleArgs class to retrieve the property name. While you can get the property name itself through e.PropertyName, the GetPropertyName() helper method will return the friendly name associated with the property if one exists, or the property name if there is no friendly name. In many cases, a friendly name such as Product name is a better value to show the user than the actual property name (like ProductName).

This rule is used within a business object by associating it with a property. A business object does this by overriding the AddBusinessRules() method defined by BusinessBase. Such code would look like this (assuming the developer adds a using statement for Csla.Validation to the top of the code):

[Serializable]
  public class Customer : BusinessBase<Customer>
  {
    protected override void AddBusinessRules()
    {
      ValidationRules.AddRule(CommonRules.StringRequired, NameProperty);
    }
    // rest of class...
  }

This associates the rule method with the property defined by the NameProperty field (a PropertyInfo<string> value) so that the SetProperty() call within the property's set block will invoke the rule automatically. You'll see this and other rule methods used in Chapter 17 within the sample application's business objects.

StringMaxLength

A slightly more complex variation is where the rule method needs extra information beyond that provided by the basic RuleArgs parameter. In these cases, the RuleArgs class must be subclassed to create a new object that adds the extra information. A rule method to enforce a maximum length on a string, for instance, requires the maximum length value.

Custom RuleArgs Class

I recommend using DecoratedRuleArgs, which is a subclass of RuleArgs. I say this because DecoratedRuleArgs stores the custom argument values in a Dictionary, and thus provides a standardized way by which the code in AddBusinessRules() can create the RuleArgs parameter. This is particularly useful if you use code generation for your business classes, as standardization is critical when building code generation templates.

The custom RuleArgs classes in CommonRules are a hybrid approach. I do implement custom classes, but they subclass DecoratedRuleArgs. This means that the code in AddBusinessRules() can use either the strongly typed custom class, or DecoratedRuleArgs. Either approach works, so you can use StringMaxLength like this:

ValidationRules.AddRule(
    Csla.Validation.CommonRules.StringMaxLength,
    new Csla.Validation.CommonRules.MaxLengthRuleArgs(NameProperty, 5));

or like this:

var args = new Csla.Validation.DecoratedRuleArgs(NameProperty);
      args["MaxLength"] = 5;
      ValidationRules.AddRule(Csla.Validation.CommonRules.StringMaxLength, args);

You get the same end result. The first approach is better for hand-coding, because it is strongly typed. The second approach is better for code generation, because it is a standard approach that works with any rule method that accepts a DecoratedRuleArgs parameter. By implementing a custom subclass of DecoratedRuleArgs in CommonRules, I enable both scenarios.

Tip

You don't need to create a custom subclass like this. It is possible to just use DecoratedRuleArgs directly and avoid all this work, though the code in AddBusinessRules() will then be loosely typed.

Here's a subclass of DecoratedRuleArgs that provides the maximum length value:

public class MaxLengthRuleArgs : DecoratedRuleArgs
  {
    public int MaxLength
    {
      get { return (int)this["MaxLength"]; }
    }

    public MaxLengthRuleArgs(
      string propertyName, int maxLength)
      : base(propertyName)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = string.Empty;
    }

    public MaxLengthRuleArgs(Core.IPropertyInfo propertyInfo, int maxLength)
      : base(propertyInfo)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = string.Empty;
    }
public MaxLengthRuleArgs(
      string propertyName, string friendlyName, int maxLength)
      : base(propertyName, friendlyName)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = string.Empty;
    }

    public MaxLengthRuleArgs(
      string propertyName, int maxLength, string format)
      : base(propertyName)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = format;
    }

    public MaxLengthRuleArgs(
      Core.IPropertyInfo propertyInfo, int maxLength, string format)
      : base(propertyInfo)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = format;
    }

    public MaxLengthRuleArgs(
      string propertyName, string friendlyName, int maxLength, string format)
      : base(propertyName, friendlyName)
    {
      this["MaxLength"] = maxLength;
      this["Format"] = format;
    }
  }

All the custom RuleArgs subclasses in CommonRules follow this basic structure.

Rule Method

With the custom RuleArgs class defined, you can use it to implement a rule method. The StringMaxLength() rule method looks like this:

public static bool StringMaxLength(
    object target, RuleArgs e)
  {
    DecoratedRuleArgs args = (DecoratedRuleArgs)e;
    int max = (int)args["MaxLength"];
    string value = (string)Utilities.CallByName(
      target, e.PropertyName, CallType.Get);
    if (!String.IsNullOrEmpty(value) && (value.Length > max))
    {
      string format = (string)args["Format"];
      string outValue;
      if (string.IsNullOrEmpty(format))
        outValue = max.ToString();
else
        outValue = max.ToString(format);
      e.Description = String.Format(
        Resources.StringMaxLengthRule,
        RuleArgs.GetPropertyName(e), outValue);
      return false;
    }
    return true;
    }

This is similar to the StringRequired() rule method, except that it casts the RuleArgs parameter to the DecoratedRuleArgs type so that it can retrieve the MaxLength value. That value is then compared to the length of the specified property from the target object to see if the rule is broken or not.

Note

It might seem like the RuleArgs parameter should just be of type DecoratedRuleArgs or MaxLengthRuleArgs, using the generic RuleHandler delegate. That would be ideal, but it would break backward compatibility with older versions of CLSA .NET, so I've chosen to take the approach shown here.

The CommonRules class includes other similar rule method implementations as listed in Table 11-3. You may choose to use them as they are, or as the basis for creating your own library of reusable rules for an application.

Conclusion

This chapter covered the business and validation rules subsystem of CLSA .NET. This is one of the most important parts of the framework, as it provides a standardized way by which you can implement business rules and have them execute as your object's properties are changed.

Chapter 12 will cover the important authorization subsystem, which provides similar capabilities for the authorization of property, method, and object access. Chapters 13 through 16 will continue discussing the implementation of CLSA .NET features, and then Chapters 17 through 21 will cover the implementation of the Project Tracker reference application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset