Parameter

From FlexRule Wiki
Jump to: navigation, search

Parameters

A logic may have some parameters. Parameters are gateway to communicate to the logic. It enables both application-to-logic and logic-to-logic communication. This communication enables your application pass some information of any type to the logic or read some information from logic. There is no limitation on numbers of parameters and type of values that you pass and retrieve back from a logic. A parameter can be in any types: Internal and custom types i.e. any CLR types can be used as well as dynamic and anonymous types. These types can be defined in different assemblies and be referenced in rules.

In FlexRule there are two types of parameters:

  1. Variable parameters (place holder for values)
  2. Type parameters

Input

Input parameters allows your application send some input and let the logic process the them.

Output

After processing is finished then your application can collect the result from output parameters as well. These parameters can define the decision model of logic to be collected when the process is finished.

InOut

Sometimes one parameter must act as Input and Output then this direction type can be used.

Local

In some scenarios processing requires some private, local parameters to store the result into those slots temporarily and then collect them in some other time. These parameters that are not part of your logic decision are called local parameters.

How to define

In all logic e.g. Flow, Procedural, Workflow, Validation... can define parameters by adding Declaration. In this section then your logic is able to define parameters by using Define.

By setting the direction of the Define command you can specify what types of parameter is being defined. And type setting of the parameter would define the types of parameters. However, it is not required to define the type of the parameters at all in most cases.

No types

Using literals can define the values type when they are being assigned.

  1. <Declaration>
  2.   <Define name="StarPrice" direction="out" value="100i" />
  3.   <Define name="StarQty" direction="in" value="0i" />
  4.   <Define name="StarQtyDiscount" direction="in" value="0d" />
  5.   <Define name="result" direction="out" />
  6. </Declaration>

Primitive types

  1. <Declaration>
  2.   <Define name="StarPrice" type="System.Int32" direction="out" value="100"/>
  3.   <Define name="StarQty" type="System.Int32" direction="in"/>
  4.   <Define name="StarQtyDiscount" type="System.Double" direction="in"/>
  5. </Declaration>


Complex types

When your logic requires complex type, you can use assembly and type to define the parameter.

  1. <Declaration>
  2.   <Using assembly="InvoiceData.dll" path="InvoiceSample.Data" />
  3.   <Define name="dataInfo" assembly="InvoiceData.dll" type="InvoiceSample.Data" direction="Local" />
  4. </Declaration>


Directions

There are three directions supported with the framework. Directions are for encapsulating the parameters are being defined. It can be set by direction setting of Define command. Bear in mind when your logic has defined some input parameters, they have to be passes to the logic during execution other wise when the logic gets executed you receive an exception regarding the missing values for those defined input parameters.

Passing Values to Input Parameters

All engines provide Run method to be used to execute a logic. Those Run methods has two different overrides in the API:

  1. public object Run(IDictionary<string, object> inputParameters);
  2. public object Run(params object[] inputParameters);

Run method accepts the values and references for your logic Input Parameter. You can pass the input as an array of an object or by name of input parameters on the logic.

This example shows you how to pass input parameters to your logic

  1. public void RunEngine(int mixed, int gold, int silver)
  2. {
  3.     // This method creates and returns an engine e.g. ProcedureEngine, FlowEngine,...
  4.     var engine = GetEngine();
  5.     // And we can simply pass the input parameters to the engine
  6.     // by passing all of them to run method of engine
  7.     engine.Run(mixed, gold, silver);
  8. }

Passing an Array into rules

Because engines accept input parameters as params object[] when your input parameters themselves are in array then you have to wrap them in an other array of object.

Let's consider a logic that has the following signature

  1. <Declaration>
  2.   <Define name="intArg" direction="in" />
  3.   <!-- array of string -->
  4.   <Define name="arrayArg" direction="in" />
  5.   <Define name="dateArg" direction="in" />
  6. </Declaration>

In your application when you are going to call Run or Validate methods to execute the logic, you need to pass the input parameters.

In this scenario you need to pass the following object to the execution method. e.g. Run, Validate...

  1. var inputs = new object[]
  2.     {
  3.         10, 
  4.         new object[] {"test1", "test2"},
  5.         DateTime.Now
  6.     };

Please note in modeling stage, there is no difference between a parameter as an array, or with a single value. The values that are passed in will determine what type of parameters are they.

Reading Values from Output Parameters

When the execution of engine is finished. All the output parameters of logic can be retrieved from the engine context. Also during the read you can make sure the process has been successfully finished by checking the 'Exception' property on the context.

  1. public void ReadValues(ActiveElementEngine engine)
  2. {
  3.     // reading the paramters from context
  4.     var engineContext = engine.Context;
  5.     if (engineContext.Exception != null)
  6.         throw new Exception("Something went wrong.", engineContext.Exception);
  7.  
  8.     // Getting the execution context variable container
  9.     var variables = engineContext.VariableContainer;
  10.  
  11.     // reading the values from variable container
  12.     // In this line we read a value of output parameter named 'InvoiceTotalAmount' from logic
  13.     decimal total = (decimal) variables["InvoiceTotalAmount"];
  14.  
  15. }

Variable Container

All these parameters can be accessed on a container called Variable Container which exists on your execution context. Once an engine is created, all the Variable and Type parameters will be registered on the variable container. In code also, you can create a variable container manually and evaluate an expression based on the created container.

  1. public void test_select_contains()
  2. {
  3.    var list = new List<DecisionAgeTests.Person>()
  4.    {
  5.        new DecisionAgeTests.Person("arash", 38, DecisionAgeTests.Gender.Male),
  6.        new DecisionAgeTests.Person("Parsa", 6, DecisionAgeTests.Gender.Male),
  7.        new DecisionAgeTests.Person("arash", 38, DecisionAgeTests.Gender.Male),
  8.        new DecisionAgeTests.Person("Shah", 3, DecisionAgeTests.Gender.Female),
  9.        new DecisionAgeTests.Person("Shah", 31, DecisionAgeTests.Gender.Male)
  10.    };
  11.  
  12.     var vc = new VariableContainer();
  13.     vc.RegisterVariable("people", list);
  14.     vc.RegisterVariable("$name");
  15.     vc["$name"]="arash";
  16.  
  17.     var res = ExpressionEval.Default.Compute(vc, "people |groupBy('p', 'p.Name') |where ('g','g.Count>1') |select('d','d.Key') |contains($name)");
  18.     Assert.AreEqual(true, res);
  19. }

As you see in this example, a variable container is used to holds values and types that is used for evaluating an expression.

Flexible Typing

Parameters that are defined on VariableContainer are not referencing any type, and can be used just as a placeholders within an expression. This allows you to decouple the entity definition of your application from model of the business logic (e.g. rules, decision,...). As long as the passing object is syntactically correct, the expression can be evaluated, therefore the logic model can be executed.

  1. class Table
  2. {
  3.     public int Length { get; set; }
  4. }
  5.  
  6. [TestMethod]
  7. public void Expression_Eval_Flexible_Typing()
  8. {
  9.     const string expression = "p.Length==8";
  10.     IVariableContainer container = new VariableContainer();
  11.     container.RegisterVariable("p");
  12.  
  13.     // string type has Length property
  14.     container["p"] = "FlexRule";
  15.     Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
  16.  
  17.     // an anonymous type with Length property
  18.     container["p"] = new { Length = 8 };
  19.     Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
  20.  
  21.     // Array has Length property
  22.     container["p"] = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };
  23.     Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
  24.  
  25.     // static type that has Length property
  26.     container["p"] = new Table {Length = 8};
  27.     Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
  28.  
  29.     // Expando (dynamic) type that has a Length property
  30.     dynamic x = new ExpandoObject();
  31.     x.Length = 8;
  32.     container["p"] = x;
  33.     Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
  34. }

As you can see in this example, in this expression p.Length == 8 the p identifier is just a placeholder for what ever has a member (property or field) named Length. As it is highlighted, different types of values has been assigned to parameter p in the variable container in the above example.

Now in any logic type, when you define input parameter with no specific type defined, then you can pass any of those values to the logic without restricting your logic with any type.

Types in FlexRule Designer

Please see details at here.

Logic to Logic

In a hierarchical logic document e.g. a Flow(parent) may contain some other logic e.g. Decision Table(child). To pass some values from parent to child logic, we need to add a Parameter with the same name in both logic documents(parent and child). And set the context for the child to Shared.

For example in the below example, we have a Flow(as a parent) to find the kid's category based on their age. Our Flow contains a Decision Table(a child) to carry our rules.

In this Flow we have an Input Parameter which is Age and an Output Parameter which is Category. Now if we define both these Parameters in our Decision Table, then their context would be shared. This is because the added Parameters have the same NAME as below:

We have created 2 Parameters (Age and Category) in Flow. And for purposing the shared context, we have created 2 Parameters (Age and Category) in our Decision Table.