The definition of a business rule typically involves taking data and seeing if it meets certain criteria. In Code Effects, the rule is the criteria expressed as an XML document, and the source object is the data implemented either as a public .NET class, an XML document, or even as a collection of values of defined types.
-
Attributes. Although it is perfectly fine to use a "plain" .NET class as a source object, you would probably want to customize it when used in real life business rules in order to better suit the requirements of your project. Almost all elements of a source object can be customized with Code Effects attributes located in the CodeEffects.Rule.Attributes namespace. For example, consider a Car class:
using CodeEffects.Rule;
namespace Test
{
[Source(MaxTypeNestingLevel = 3)]
[ExternalMethod(typeof(Helper), "HadAccidents")]
[ExternalAction(typeof(Helper), "ChangeOil")]
public class Car
{
}
}
Three optional attributes were applied to this class:
- The MaxTypeNestingLevel property of the SourceAttribute tells Rule Editor that it should not go deeper than the third level when it reflects all reference type properties of the source object during the search for rule fields. For example, Rule Editor will use all public value type fields of the Car.Powertrain.Engine but not the Car.Powertrain.Transmission.Manufacturer because the Manufacturer property would be a fourth level of search. The SourceAttribute is optional.
- ExternalMethodAttribute tells Rule Editor that it should use the method HadAccidents of the external class Helper as an in-rule method.
- ExternalActionAttribute tells Rule Editor that it should use the method ChangeOil of the same external class Helper as a rule action.
-
Properties. Rule Editor uses all public non-static value type properties and fields of the source object as rule fields, unless those members are of type System.Guid, nullable enum or marked with ExcludeFromEvaluationAttribute. Read more about data types in Code Effects here.
Rule Editor performs a full search of all reference type members of the source object and uses all their value type members not marked with ExcludeFromEvaluationAttribute as rule fields. The optional SourceAttribute (used in the above code sample) contains the property MaxTypeNestingLevel. The search for value type members is performed up to the level set by this property, with a default value of 4. Setting MaxTypeNestingLevel to a higher number may slow the performance of Rule Editor while it renders itself on the page.
To see this in action, add a new property to your source object, give it a type of some large class, add Rule Editor to the page and run the page. Experiment with the value of SourceAttribute.MaxTypeNestingLevel to see the difference in load performance.
To simplify things for rule authors, Rule Editor converts fields and return types of in-rule methods into six simple types: numeric, string, date, time, boolean and enum. For example, if the property of the source object is of type System.Int16, Rule Editor will convert it into numeric when rendering its UI value on the page. During rule evaluation, though, this value will be boxed back into the most appropriate .NET type in order to perform an accurate evaluation. Properties of System.DateTime type will be converted into date fields, System.TimeSpan into time fields, enumerators into enum fields, and so on. This conversion happens automatically.
For strings and nullable properties or method return types, Rule Editor automatically adds isNull and isNotNull operators to the operator menus. The default English display names for those operators are has no value and has any value. Default display names of rule elements such as operators, clauses, and flows can be changed by the use of Help XML. Display names of rule fields can be changed by creating a source object using Source XML or by changing the value of the DisplayName property of the FieldAttribute.
You can use properties of enumerator types as fields. If you don't want certain enumerator items to be used in rules, decorate them with the ExcludeFromEvaluationAttribute class.
Let's add several properties to our Car class:
[Field(ValueInputType = ValueInputType.User,
DateTimeFormat = "MMM dd, yyyy", DisplayName = "Date of sale")]
public DateTime? Sold { get; set; }
[Field(Max = 17, DisplayName = "VIN number")]
public string VIN { get; set; }
[Field(Min = 0, Max = 1000000)]
public decimal Price { get; set; }
public Color Color { get; set; }
Even though the FieldAttribute topic contains detailed descriptions of each of its properties, several things are worth noting here:
- The Sold property is of nullable System.DateTime type. Therefore, the isNull and isNotNull operators will show up in the operators menu. Rule Editor will convert this property into a date field. It will be displayed as "Date of sale" in the Rule Editor and its value will be formatted with the MMM dd, yyyy pattern. The value of the FieldAttribute.ValueInputType property instructs Rule Editor to allow only user input (in this case, a date picker) for the value of this field.
- The VIN property is of System.String type. Strings are nullable reference types, so the isNull and isNotNull operators will appear in the operators menu as well. The maximum length of values for this field will be limited to 17 characters when the rule author types in the value.
- The Price property will be converted into a numeric field. Rule Editor will allow input of values between 0 and 1,000,000. Because this property is of System.Decimal type, Rule Editor will also allow decimal numbers if the value is typed in by the rule author. This field will be displayed as "Price" because the value of FieldAttribute.DisplayName was not set.
- The Color property will be converted into an enum field. The value menu will display items of the Color enum (declared elsewhere).
-
In-Rule Methods. Each rule condition must have three elements: field, operator and value:
... Brand is equal to Ford ...
In the example above, the Brand is a rule field. Code Effects rules engine implements a feature called in-rule methods, which allow you to use return values of methods as condition fields or values. To qualify, the method must be public and must return a string or a value type (except for System.Guid and nullable enum). If it's not parameterless, the method must take only value types (except for Sytem.Guid) or the source object as parameters, otherwise, the method will be ignored by Rule Editor. Get details by reading the ParameterAttribute topic. In-rule methods can be used in both execution and evaluation type rules.
If Had accidents ( VIN ) is True then Ask for discount
In this short rule, HadAccidents is a public method of the source object used as a rule field. It can be declared like this:
[Method("Had accidents")]
[return: Return(ValueInputType = ValueInputType.User)]
public bool HadAccidents( [Parameter(ValueInputType.Fields)] string vin)
{
List<Accident> list = VehicleService.GetAccidents(vin);
return list != null && list.Count > 0;
}
Several things to notice in this declaration:
- The optional MethodAttribute tells Rule Editor to display this method as "Had accidents".
- Notice the use of ReturnAttribute on the return value of the method. The value of the ValueInputType parameter limits rule authors to manually input the value with the help of a menu that contains items True and False. This is the default behavior of bool fields. Of course, the default English labels True and False in the menu can be changed to anything by using Help XML.
- The optional ParameterAttribute tells Rule Editor that rule authors should only pass other string fields to the method, i.e. Rule Editor should not allow user input such as "... Had accidents ( "ABCD1234" ) ..."
One of the goals of Rule Editor is to simplify things for rule authors. For example, if an in-rule method has no parameters or if it takes the source object type as a parameter, the author won't even know that this is a method and not a field. Consider the simple rule:
If Resale price is less than 100 then Contact junkyard
To the rule author, the element Resale price appears to be a rule field when in fact it's a method that takes a source object as its only parameter:
[Method("Resale price")]
public decimal GetResalePrice(Car sourceObject)
{
decimal? price = VehicleService.GetPriceByVIN(sourceObject.VIN);
return price == null || price < 0 ? 0 : (decimal)price;
}
-
Actions. Actions are public methods that return System.Void. They can be declared in a source object or in any other public class. If it's not parameterless, the action must take only value types (except for Sytem.Guid) or the source object as parameters. Otherwise, the method will be ignored by Rule Editor. Get details by reading the ParameterAttribute topic. Actions can be used only in execution type rules.
Refer to the code that declares the Car class. The ExternalActionAttribute "added" the ChangeOil method to the source object as a rule action. That method is declared in the Helper class:
namespace Test
{
public class Helper
{
public Helper() { }
[Action("Change oil")]
public void ChangeOil(Car car)
{
Oil oil = VehicleService.GetOil();
car.Oil.Remove();
car.Oil.Add(oil);
}
}
}