Parameter

From FlexRule Wiki
Jump to: navigation, search

Parameters

A logic may have some parameters. Parameters are a gateway with which to communicate with the logic. These enable both application-to-logic and logic-to-logic communication. This communication enables your application to pass information of any type to the logic or to read some information from the logic.

There is no limit on the number of parameters and type of values that you can pass to and retrieve back from the logic. A parameter can be both an internal and custom type (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 holders for values)
  2. Type parameters

Input

Input parameters allow your application to send some input and let the logic process it.

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 both the 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 some other time. The parameters that are not part of your logic decision are called local parameters.

How to define

All logic (e.g., Flow, Procedural, Workflow, Validation, etc.) can define parameters by adding a Declaration. In this section, your logic is able to define parameters by using Define.

By setting the direction of the Define command, you can specify what type of parameter is being defined. While the type setting of the parameter defines the type of parameter, it is not necessary to define this in most cases.

No types

Using literals can define the value type when this is 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 a 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 the direction setting of Define command. Bear in mind that when your logic has defined some input parameters, they have to be passed to the logic during execution, otherwise when the logic gets executed, you will receive an exception regarding the missing values for those defined input parameters.

Passing Values to Input Parameters

All engines provide a Run method to be used to execute the logic. Those Run methods have 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 your logic Input Parameter. You can pass the input as an array of an object or by the 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, etc.)
  4.     var engine = GetEngine();
  5.     // And we can simply pass the input parameters to the engine
  6.     // by passing all of them to the run method of the 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 an array, then you have to wrap them in another array of an object.

Let's consider 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, etc.):

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

Please note that in the 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 their type of parameter.

Reading Values from Output Parameters

When the execution of the engine is finished, all of the output parameters of the 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 of 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 a 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 of these parameters can be accessed in a container called Variable Container which exists in your execution context. Once an engine is created, all of 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 can see in this example, a variable container is used to hold the values and types that are used for evaluating an expression.

Flexible Typing

Parameters that are defined in a 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 the model of the business logic (e.g., rules, decision, etc.). 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, the p.Length == 8 the p identifier is just a placeholder for whatever has a member (property or field) named Length. As highlighted in the above example, different types of value have been assigned to the parameter p in the variable container.

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 such as a Flow (parent) may contain some other logic, such as a 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 also set the context for the child to be Shared.

For example, in the example below we have a Flow (as a parent) in order 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 two Parameters (Age and Category) in the Flow, and for purposing the shared context, we have created two Parameters (Age and Category) in our Decision Table.