Implement Custom Rules

The eXpressApp Framework supplies a number of rules. You can apply them to business classes and their properties easily, by using a corresponding rule attribute. To get the list of available rules and their attributes, refer to the Validation Rules topic. Being quite universal, the supplied rules may still be inappropriate in some scenarios. In these instances, you can implement a custom validation rule. This topic details two approaches: implementing a typical XAF rule that will be applied via a specific rule attribute, and implementing a rule that will be automatically applied to a specified business class.

Implement a Regular Validation Rule

To implement a validation rule, inherit from any XAF validation rule type (listed below). Some of these types are abstract, and they represent a base for rules. To implement a custom rule, inherit from them. To modify the behavior of a particular rule, inherit directly from it. When implementing a rule, you should be aware of two auxiliary entities that accompany any rule:

  • Rule Attribute

    To create an instance of a rule of a particular type for a business class or a property, a rule attribute should be applied to this class or property. Each rule type has a specific attribute. Basically, an attribute establishes a set of parameters that are required to check the rule. When implementing a custom rule, you should implement a custom attribute for it. For this purpose, you can inherit either from the RuleBaseAttribute, or from the attribute that corresponds to the rule you inherit from. In your attribute, you should, at a minimum, override the protected RuleType property, returning your rule type.

  • Rule Properties

    Information on the rules to create in the application is saved to the Application Model. Each validation rule exposes a RuleBaseProperties class descendant via the RuleBase.Properties property. This descendant specifies the rule's properties exported to the corresponding Validation | Rules | Rule child node. When implementing a custom rule, you have the following options:

    1. If you need to extend the Properties class used by the rule's base rule, perform the following actions. First, declare a new interface derived from the interface implemented by the base Properties class. Declare the required additional properties in this interface. Second, declare the new Properties class by inheriting from the base Properties class. The new class must implement the newly declared interface. To make your rule, use the custom Properties class, override the PropertiesType property of the rule and the rule's attribute classes. In the overridden properties, return an instance of the descendant Properties class. Hide the base class' Properties property via the new modifier. The property should have the type of the newly declared interface.

    2. If you are satisfied with the properties presented in the Properties type that is set for your rule's base rule, you do not have to override the PropertiesType properties.

The following table lists the available rule types, corresponding attributes and Properties types:

Rule Type

Rule Description

Properties Type

Attribute

RuleBase

Represents an abstract class implementing the IRule interface.

Used as a base class for all rules.

RuleBaseProperties

Implements the IRuleBaseProperties interface.

-

RuleCombinationOfPropertiesIsUnique

Represents the RuleSearchObject class descendant.

This rule demands that a particular properties' values combination is unique.

RuleCombinationOfPropertiesIsUniqueProperties

Derived from the RuleSearchObjectProperties class. Implements the IRuleCombinationOfPropertiesIsUniqueProperties interface.

RuleCombinationOfPropertiesIsUniqueAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleCombinationOfPropertiesIsUniqueProperties interface.

RuleCriteria

Represents the RuleBase class descendant.

This rule demands that an object of a particular type satisfy a specified criteria.

RuleCriteriaProperties

Derived from the RuleBaseProperties class. Implements the IRuleCriteriaProperties interface.

RuleCriteriaAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleCriteriaProperties interface.

RuleFromBoolProperty

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a Boolean type property should have a True value.

RuleFromBoolPropertyProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleFromBoolPropertyProperties interface.

RuleFromBoolPropertyAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleFromBoolPropertyProperties interface.

RuleIsReferenced

Represents the RuleSearchObject class descendant.

This rule demands that an object should be referenced in objects of a specified type.

RuleIsReferencedProperties

Derived from the RuleSearchObjectProperties class. Implements the IRuleIsReferencedProperties interface.

RuleIsReferencedAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleIsReferencedProperties interface.

RuleObjectExists

Represents the RuleSearchObject class descendant.

This rule demands that an object of a particular type that satisfies a specified criteria, exists in the database.

RuleObjectExistsProperties

Derived from the RuleSearchObjectProperties class. Implements the IRuleObjectExistsProperties interface.

RuleObjectExistsAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleObjectExistsProperties interface.

RulePropertyValue

Represents an abstract class derived form the RuleBase class.

Used as a base class for the rules that check a specified property's value.

RulePropertyValueProperties

Derived from the RuleBaseProperties class. Implements the IRulePropertyValueProperties interface.

-

RulePropertyValue<TPropertyValue>

Represents an abstract class derived from the RulePropertyValue class.

Used as a base class for the rules that checks a particular type of specified property's value.

RulePropertyValueProperties

Derived from the RuleBaseProperties class. Implements the IRulePropertyValueProperties interface.

-

RuleRange

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a particular property's value should be within the specified value range.

RuleRangeProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleRangeProperties interface.

RuleRangeAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleRangeProperties interface.

RuleRegularExpression

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a particular property should match a specified pattern.

RuleRegularExpressionProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleRegularExpressionProperties interface.

RuleRegularExpressionAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleRegularExpressionProperties interface.

RuleRequiredField

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a particular property should have a value.

RuleRequiredFieldProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleRequiredFieldProperties interface.

RuleRequiredFieldAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleRequiredFieldProperties interface.

RuleSearchObject

Represents an abstract class derived form the RuleBase class.

Used as a base class for the RuleObjectExists, RuleUniqueValue and RuleIsReferenced rules. This rule's checking algorithm is based on searching an object by a particular criteria.

RuleSearchObjectProperties

Derived from the RuleBaseProperties class. Implements the IRuleSearchObjectProperties interface.

-

RuleStringComparison

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a value of a particular String type property should satisfy a specified condition.

RuleStringComparisonProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleStringComparisonProperties interface.

RuleStringComparisonAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleStringComparisonProperties interface.

RuleUniqueValue

Represents the RuleSearchObject class descendant.

This rule demands that a particular property's value should be unique.

RuleUniqueValueProperties

Derived from the RuleSearchObjectProperties class. Implements the IRuleUniqueValueProperties interface.

RuleUniqueValueAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleUniqueValueProperties interface.

RuleValueComparison

Represents the RulePropertyValue<TPropertyValue> class descendant.

This rule demands that a particular property's value satisfy a specified condition.

RuleValueComparisonProperties

Derived from the RulePropertyValueProperties class. Implements the IRuleValueComparisonProperties interface.

RuleValueComparisonAttribute

Derived from the RuleBaseAttribute class. Implements the IRuleValueComparisonProperties interface.

Note

All the rules, Property types and attributes that are presented in the table above are declared in the DevExpress.Persistent.Base.v18.1.dll assembly, in the DevExpress.Persistent.Validation.RuleBase namespace.

The following code demonstrates the key members that you should have in your rule, Properties interface, Properties type and attribute:

//Declare the new Properties interface
[GenerateMessageTemplatesModel("MyRule")]
public interface IRuleMyProperties : IRuleSearchObjectProperties {
    [Category("Data")]
    string AdditionalProperty { get; set; }
}
[DomainComponent] //Here the RuleSearchObjectProperties is used as a base class
public class MyRuleProperties : RuleSearchObjectProperties, IRuleMyProperties {
    private string additionalProperty;
    public string AdditionalProperty {
        get { return additionalProperty; }
        set { additionalProperty = value; }
    }
}
//Here the RuleSearchObject is used as a base class
public class MyRule : RuleSearchObject {
    //A custom rule must have at least two constructors
    //The default constructor
    public MyRule() : base() { }
    //A constructor which takes an IRuleBaseProperties-descendant interface type parameter
    public MyRule(IRuleSearchObjectProperties properties) : base(properties) { }
    protected override bool IsValidInternal(object target, out string errorMessageTemplate) {
        //Check whether the rule is satisfied
    }
    public override ReadOnlyCollection<string> UsedProperties {
        get {
            //Specify the properties that will be highlighted when the rule is broken
        }
    }
    //If you have a custom Properties type, return the Properties object
    //cast to the corresponding interface type
    protected new IRuleMyProperties Properties {
        get {
            return (IRuleMyProperties)base.Properties;
        }
    }
    //If you have a custom Properties type, return it here
    public override Type PropertiesType {
        get {
            return typeof(MyRuleProperties);
        }
    }
}
//Here an abstract RuleBaseAttribute class is used as a base class
public class MyRuleAttribute : RuleBaseAttribute {
    //Return the type of the rule for which you implement this attribute
    protected override Type RuleType {
        get { return typeof(MyRule); }
    }
    //If you have a custom Properties type, return it
    protected override Type PropertiesType {
        get {
            return typeof(MyRuleProperties);
        }
    }
    public MyRuleAttribute(string id, string targetContextIDs, string additionalProperty)
        : base(id, targetContextIDs) {
        Properties.AdditionalProperty = additionalProperty;
    }
    public MyRuleAttribute(string id, DefaultContexts targetContexts, string additionalProperty)
        : base(id, targetContexts) {
        Properties.AdditionalProperty = additionalProperty;
    }
    //If you have a custom Properties type, return the Properties object
    //cast to the corresponding interface type
    protected new IRuleMyProperties Properties {
        get { return (IRuleMyProperties)base.Properties; }
    }
    public string AdditionalProperty {
        get { return Properties.AdditionalProperty; }
    }
}
Note

Note that attributes which derive from the RuleBaseAttribute class must have the Properties property marked as Protected. This is necessary to avoid problems with attribute comparisons made by XAF core classes.

When implementing a custom Properties type, the following attributes can be useful:

  • RulePropertiesRequired

    Applied to a property of a Properties class. Indicates that the rule node's property that corresponds to this property cannot be empty.

  • RulePropertiesLocalized

    Applied to a property of a Properties class. Indicates that the rule node's property that corresponds to this property is localizable.

  • RulePropertiesReadonly

    Applied to a property of a Properties class. Indicates that the rule node's property that corresponds to this property is read-only.

  • RulePropertiesMemberOf

    Applied to a Properties class' property, specifying the name of a business class property that takes part in the rule's logic. This attribute's parameter passes the name of the Properties class' property, specifying the type containing the property that uses this attribute. For instance:

    [DomainComponent]
    public class MyRuleProperties : RuleSearchObjectProperties, IRuleMyProperties {
        private Type someType;
        public Type SomeType {
            get { return someType; }
            set { someType = value; }
        }
        private string property1Name;
        [RulePropertiesMemberOf("SomeType ")]
        public string Property1Name{
            get { return property1Name; }
            set { property1Name = value; }
        }
        private string property2Name;
        [RulePropertiesMemberOf("SomeType ")]
        public string Property2Name{
            get { return property2Name; }
            set { property2Name= value; }
        }
    }
    
  • RulePropertiesDefaultValue

    Applied to a rule to specify a default value of a particular property of the Properties object used by this rule. The property is passed as the attribute's first parameter, and the default value is passed as the second parameter.

  • RulePropertiesIndex

    Applied to properties of a Properties class to specify the order in which the corresponding properties will be shown in the Model Editor.

To register a custom validation rule, override a module's Setup method that takes an ApplicationModulesManager instance as a parameter. The following code snippet illustrates this.

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Validation;
//...
public sealed partial class MyModule : ModuleBase {
    //...
    public override void Setup(ApplicationModulesManager moduleManager) {
        base.Setup(moduleManager);
        ValidationRulesRegistrator.RegisterRule(moduleManager, typeof(MyRule), typeof(IRuleMyProperties));
    }
}

To see an example of implementing a custom rule, and the attribute and properties for it, refer to the FeatureCenter demo installed with XAF. The RuleStringLengthComparison, RuleStringLengthComparisonAttribute and RuleStringLengthComparisonProperties are implemented in it. This demo is located in the %PUBLIC%\Documents\DevExpress Demos 18.1\Components\eXpressApp Framework\FeatureCenter folder, by default.

Implement a Code Rule

With the Code Rule approach you do not implement a custom attribute, and do not apply it to business classes. The approach is useful when you need to apply a validation rule in code to a business class implemented in a referenced assembly, without using the Model Editor. You can also use this approach to check objects using a highly tailored algorithm.

The main idea of the Code Rule approach is that you implement a custom rule, as described above, and decorate it with the CodeRuleAttribute. This approach differs from the one described above by the technique used to collect validation rules. Typically, information on validation rules is loaded into the Application Model based on the rule attributes applied to business classes. With this approach however, information on validation rules is loaded into the Application Model based on validation rule classes decorated with the CodeRule attribute.

The rule's target class can be specified in the rule's declaration using one of the following techniques:

  • The target type is specified as a generic parameter for the rule's base class:

    [CodeRule]
    public class MyCodeRule : RuleBase<Contact> {
       protected override bool IsValidInternal(
          Contact target, out string errorMessageTemplate) {
          //Check whether the rule is satisfied
       }
        public MyCodeRule() : base("", "Save") { }
        public MyCodeRule(IRuleBaseProperties properties) : base(properties) { }
    }
    
  • The target type is passed as a parameter of the base rule's constructor:

    [CodeRule]
    public class MyCodeRule : RuleBase {
       protected override bool IsValidInternal(
          object target, out string errorMessageTemplate) {
          //Check whether the rule is satisfied
       }
        public MyCodeRule() : base("", "Save", typeof(Contact)) { }
        public MyCodeRule(IRuleBaseProperties properties) : base(properties) { }
    }
    

The rule's target properties (properties, highlighted in UI when the rule is broken) can be specified in the overridden IRule.UsedProperties property getter.

using System.Collections.ObjectModel;
// ...
public override ReadOnlyCollection<string> UsedProperties {
    get {
        return new ReadOnlyCollection<string>(new List<string>() { "Amount" });
    }
}

Do not forget to register the rule by overriding a module's Setup method that takes an ApplicationModulesManager instance as a parameter.

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Validation;
//...
public sealed partial class MyModule : ModuleBase {
    //...
    public override void Setup(ApplicationModulesManager moduleManager) {
        base.Setup(moduleManager);
        ValidationRulesRegistrator.RegisterRule(moduleManager, typeof(MyCodeRule), typeof(IRuleBaseProperties));
    }
}

To see an example of implementing a custom rule using the CodeRule attribute, refer to the FeatureCenter demo and the CodeRuleObjectIsValidRule implemented in this demo.

See Also