◷ Reading Time: 6 minutes
Parameters are defined in the Declaration section of any logic by using Define command.
Each Define command must have a direction. 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.
<Declaration>
<Define name="StarPrice" direction="out" value="100i" />
<Define name="StarQty" direction="in" value="0i" />
<Define name="StarQtyDiscount" direction="in" value="0d" />
<Define name="result" direction="out" />
</Declaration>
Primitive types
<Declaration>
<Define name="StarPrice" type="System.Int32" direction="out" value="100"/>
<Define name="StarQty" type="System.Int32" direction="in"/>
<Define name="StarQtyDiscount" type="System.Double" direction="in"/>
</Declaration>
Complex types
When your logic requires a complex type, you can use assembly and type to define the parameter.
<Declaration>
<Using assembly="InvoiceData.dll" path="InvoiceSample.Data" />
<Define name="dataInfo" assembly="InvoiceData.dll" type="InvoiceSample.Data" direction="Local" />
</Declaration>
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:
public object Run(IDictionary<string, object> inputParameters);
public object Run(params object[] inputParameters);
The 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 in the logic.
This example shows you how to pass input parameters to your logic:
public void RunEngine(int mixed, int gold, int silver)
{
// This method creates and returns an engine (e.g., ProcedureEngine, FlowEngine, etc.)
var engine = GetEngine();
// And we can simply pass the input parameters to the engine
// by passing all of them to the run method of the engine
engine.Run(mixed, gold, silver);
}
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:
<Declaration>
<Define name="intArg" direction="in" />
<!-- array of string -->
<Define name="arrayArg" direction="in" />
<Define name="dateArg" direction="in" />
</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.):
var inputs = new object[]
{
10,
new object[] {"test1", "test2"},
DateTime.Now
};
Please note that in the modeling stage, there is no difference between a parameter as an array or as 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 successfully finished by checking the ‘Exception’ property of the context.
public void ReadValues(ActiveElementEngine engine)
{
// reading the paramters from context
var engineContext = engine.Context;
if (engineContext.Exception != null)
throw new Exception("Something went wrong.", engineContext.Exception);
// Getting the execution context variable container
var variables = engineContext.VariableContainer;
// reading the values from a variable container
// In this line we read a value of an output parameter named 'InvoiceTotalAmount' from the logic
decimal total = (decimal) variables["InvoiceTotalAmount"];
}
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 in the variable container. In code also, you can create a variable container manually and evaluate an expression based on the created container.
public void test_select_contains()
{
var list = new List<DecisionAgeTests.Person>()
{
new DecisionAgeTests.Person("arash", 38, DecisionAgeTests.Gender.Male),
new DecisionAgeTests.Person("Parsa", 6, DecisionAgeTests.Gender.Male),
new DecisionAgeTests.Person("arash", 38, DecisionAgeTests.Gender.Male),
new DecisionAgeTests.Person("Shah", 3, DecisionAgeTests.Gender.Female),
new DecisionAgeTests.Person("Shah", 31, DecisionAgeTests.Gender.Male)
};
var vc = new VariableContainer();
vc.RegisterVariable("people", list);
vc.RegisterVariable("$name");
vc["$name"]="arash";
var res = ExpressionEval.Default.Compute(vc, "people |groupBy('p', 'p.Name') |where ('g','g.Count>1') |select('d','d.Key') |contains($name)");
Assert.AreEqual(true, res);
}
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 placeholder 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.
class Table
{
public int Length { get; set; }
}
[TestMethod]
public void Expression_Eval_Flexible_Typing()
{
const string expression = "p.Length==8";
IVariableContainer container = new VariableContainer();
container.RegisterVariable("p");
// string type has Length property
container["p"] = "FlexRule";
Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
// an anonymous type with Length property
container["p"] = new { Length = 8 };
Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
// Array has Length property
container["p"] = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };
Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
// static type that has Length property
container["p"] = new Table {Length = 8};
Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
// Expando (dynamic) type that has a Length property
dynamic x = new ExpandoObject();
x.Length = 8;
container["p"] = x;
Assert.AreEqual(true, ExpressionEval.Default.Compute(container, expression));
}
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.