1. Home
  2. FlexRule Designer
  3. Extensions
  4. Create Custom Extensions
  5. Developer Guide to Creating a FlexRule Extension

Developer Guide to Creating a FlexRule Extension

◷ Reading Time: 15 minutes

Whenever we want to add new functionality to FlexRule Designer, we create a class library using .NET Standard (latest version). The namespace of every extension is FlexRule.Extensions.{NameOfExtension}.

Getting Started

Project and Solution Structure

Make sure to have a workspace folder in your preferred directory to easily manage your code repository, for example:

Also, we are using GIT and it is up to you which tool you want to use, but we suggest using SourceTree for that (https://www.sourcetreeapp.com/).

As for the development, you may download your preferred IDE whether it is the latest Visual Studio Community or Visual Studio Code.

When creating your project, add a scr folder that contains the SDK project and the unit test project.

As we are using .NET standard for our SDKs, you may opt to use either nUnit or xUnit for testing.

Simple Calculator Extension

For this tutorial, we will create a simple Calculator Extension that does Addition and Division. (Visual Studio Community)

1. Create a .NET standard class library project

2. Name the project and solution to FlexRule.Extensions.Calculator

3. Go to your project directory, create src folder then move the solution file and the project folder to src.

4. Create the required folders as shown below.

5. Create CalculatorService class under Core folder

6. Create your unit test, for this example I used xUnit, and name it to FlexRule.Extensions.Calculator.Tests.

7. Add CalculatorServiceTest class and implement your unit tests. Once done run and check if there are issues.

    using FlexRule.Extensions.Calculator.Core;
    using System;
    using Xunit;
      
    namespace FlexRule.Extensions.Calculator.Tests
    {
         public class CalculatorServiceTest
         {
             [Fact]
             public void Will_Compute_Sum()
             {
                 int num1 = 5;
                 int num2 = 2;
                 var calculator = new CalculatorService();
      
                 int sum = calculator.Add(num1, num2);
      
                 Assert.Equal(7, sum);
             }
      
             [Fact]
             public void Will_Compute_Quotient()
             {
                 int num1 = 10;
                 int num2 = 2;
                 var calculator = new CalculatorService();
      
                 int quotient = calculator.Divide(num1, num2);
      
                 Assert.Equal(5, quotient);
             }
      
             [Fact]
             public void Will_Error_Compute_Quotient()
             {
                 int num1 = 10;
                 int num2 = 0;
                 var calculator = new CalculatorService();
      
                 Action act = () => calculator.Divide(num1, num2);
      
                 Assert.Throws<DivideByZeroException>(act);
             }
         }
     }

8. We will now create files for FRE integration. Download the Flexrule.Runtime NuGet package.

9. Under Flows.Nodes > ActiveElements, create CalculatorActiveElement class.

using FlexRule.Core.Model;
using System;

namespace FlexRule.Extensions.Calculator.Flows.Nodes.ActiveElements
{
    abstract class CalculatorActiveElement : ActiveElement, IElementExecutableItem
    {
        protected CalculatorActiveElement(ActiveElement parent, IElementModel elementSource)
            : base(parent, elementSource)
         {
         }
  
         public object Execute(IActiveElementExecutor executor)
         {
             try
             {
                 return Run(executor.ContextProvider.Context.VariableContainer);
             }
             catch (Exception)
             {
                 throw;
             }
     }

         public void Finialize(IActiveElementExecutor executor)
         {
         }

         public abstract object Run(IVariableContainer vc);
    }
 }

This class is an abstract class which inherits from ActiveElement and IElementExecutableItem. This will serve as the entry point for executing your Commands or what we call an ActiveElement. Let us say for this example, the Calculator Extension has the functionalities to Add and Divide. Thus, we need to create each active element that inherits from the Base Active Element (CalculatorActiveElement).

10. Create the active elements for Add, Divide and the utility ValueTypeAttribute.

DivisionActiveElement will be created by you for practice.

using FlexRule.Core.Model;
using FlexRule.Extensions.Calculator.Core;

namespace FlexRule.Extensions.Calculator.Flows.Nodes.ActiveElements
{
    class AdditionActiveElement : CalculatorActiveElement
    {
        // Name of this active element
        public const string ElementName = "CalculatorAdd";
  
         // Parameters
         public const string ParamReturn = "return";
         public const string ParamFirstNumber = "firstNumber";
         public const string ParamSecondNumber = "secondNumber";
  
         // Parameter Types
         public const string ParamFirstNumberType = "firstNumberType";
         public const string ParamSecondNumberType = "secondNumberType";
  
         // Value Types 
         private readonly ValueTypeAttribute _firstNumberType;
         private readonly ValueTypeAttribute _secondNumberType;
  
         // Private values to resolve parameter names
         private string Return { get; set; }
         private string FirstNumber { get; set; }
         private string SecondNumber { get; set; }
  
         public AdditionActiveElement(ActiveElement parent, IElementModel elementSource)
             : base(parent, elementSource)
         {
             // resolve parameter names
             Return = elementSource.Parameters[ParamReturn];
             FirstNumber = elementSource.Parameters[ParamFirstNumber];
             SecondNumber = elementSource.Parameters[ParamSecondNumber];
  
             // Resolve if what value type (for example : String, Expression or FormattedString)
             _firstNumberType = new ValueTypeAttribute(elementSource, ParamFirstNumberType);
             _secondNumberType = new ValueTypeAttribute(elementSource, ParamSecondNumberType);
  
             // Validation for required parameters
             AssertHelper.MissingParam(string.IsNullOrWhiteSpace(Return), this, "Parameter the Sum result will be stored to is required");
             AssertHelper.MissingParam(string.IsNullOrWhiteSpace(FirstNumber), this, "FirstNumber parameter name is required");
             AssertHelper.MissingParam(string.IsNullOrWhiteSpace(SecondNumber), this, "SecondNumber parameter name is required");
         }
  
         public override object Run(IVariableContainer vc)
         {
             var firstNumber = _firstNumberType.EvaluateValue(vc, FirstNumber);
             var secondNumber = _secondNumberType.EvaluateValue(vc, SecondNumber);
             var calculatorService = new CalculatorService();
  
             vc[Return] = calculatorService.Add(int.Parse(firstNumber), int.Parse(secondNumber));
  
             return null;
         }
    }
 }

11. Create a ValueTypeAttribute class, this is for evaluating the value types for example if it is string it will just use the literal value and if it is expression then it can retrieve a value from a variable.

using FlexRule.Core.Model;
using System;
using System.Collections;
using System.Collections.Generic;

namespace FlexRule.Extensions.Calculator.Flows.Nodes.ActiveElements
{
    class ValueTypeAttribute : ValueTypeBehaviour
    {
        public IElementModel Model { get; private set; }
        public ValueTypeAttribute(IElementModel model, string valueType)
            : base(model.Parameters[valueType] ?? "string")
        {
            Model = model;
        }
    }

    public class ValueTypeBehaviour
    {
        private string ValueType { get; set; }
        public bool IsExpression { get { return String.Compare(ValueType, "expression", StringComparison.OrdinalIgnoreCase) == 0; } }
        public bool IsString { get { return String.Compare(ValueType, "string", StringComparison.OrdinalIgnoreCase) == 0; } }
        public bool IsFormattedString { get { return String.Compare(ValueType, "formattedstring", StringComparison.OrdinalIgnoreCase) == 0; } }

        public ValueTypeBehaviour(string valueType)
        {
            if (valueType == null) throw new ArgumentNullException("valueType");
            ValueType = valueType;
        }

        public string EvaluateValue(IVariableContainer vc, string value)
        {
            if (IsString)
                return value;
            if (IsExpression)
            {
                var res = vc.Compute(value);
                if (res == null)
                    throw new Exception("Value expression cannot be evaluated to null for parameter: " + value);
                return res.ToString();
            }
            if (IsFormattedString)
            {
                var res = InlineMessageFormatter.GetFormattedStringtValue(value, vc);
                if (res == null)
                    throw new Exception("FormattedString Value cannot be evaluated to null for parameter: " + value);
                return res;
            }

            throw new Exception("Invalid value type: " + ValueType);
        }
    }
}

12. Under Flows.Nodes create the Factory class named CalculatorFactory. This class will inherit from AbstractElementActivatorFactory which is responsible for finding the element from the registry and activating it to be an Active Element.

using FlexRule.Core.Model;

namespace FlexRule.Extensions.Calculator.Flows.Nodes
{
    public class CalculateFactory : AbstractElementActivatorFactory
    {
        public CalculateFactory()
        {
        }
  
         public override ActiveElement Create(ActiveElement parent, IElementModel source, object[] arguments)
         {
             switch (source.Name)
             {
                 case ActiveElements.AdditionActiveElement.ElementName:
                     return new ActiveElements.AdditionActiveElement(parent, source);
                     // Add DivisionActiveElement here
                 default:
                     return null;
             }
         }
    }
 }

13. Create the NodeAdapter class (CalculateNodeAdapter) which inherits from ElementExecutableItemAdapter. This is responsible for executing the Factory.

using FlexRule.Core.Model;
using FlexRule.Flows;
using FlexRule.Flows.ActivityNodeExecutors;
using FlexRule.Flows.Model.Nodes;
using System.Linq;

namespace FlexRule.Extensions.Calculator.Flows.Nodes
{
    class CalculateNodeAdapter : ElementExecutableItemAdapter
     {
         private readonly CalculateFactory _calculateFactory
             ;
  
         public CalculateNodeAdapter()
             : base(null)
         {
             _calculateFactory = new CalculateFactory();
         }
  
         protected override string GetElementName(IActiveElementExecutor executor, Transition path, INodeExecutableItem node)
         {
             var n = node.Node.Model.Childs.FirstOrDefault(x => x.Name != "Transition" && x.Name != "Handler");
             if (n == null)
                 throw new ActiveElementException("Could not find twitter activity command");
             return n.Name;
         }

         protected override IElementExecutableItem Create(Node parent, IElementModel source)
         {
             return _calculateFactory.Create(parent, source) as IElementExecutableItem;
         }
    }
 }

Your project now should look like this.

14. On your unit test project, create a class named CalculatorModelTest.

using System.Text;
using Xunit;

namespace FlexRule.Extensions.Calculator.Tests
{
    public class CalculatorModelTest
    {
        [Fact]
        public void Will_Execute_Addition_Flow()
         {
             int n1 = 5;
             int n2 = 2;
             var flow = $@"<Flow name=""GenericFlow"">
                               <Declaration>
 	                            <Define name=""n2"" direction=""In"" type=""int"" />
 	                            <Define name=""sum"" direction=""out""  />
                               </Declaration>
                               <Nodes>
                                 <Start name=""Start2"" >
                                   <Transition name= ""Transition5"" to = ""ActivityAddition"" />
                                 </Start>
                                 <End name= ""End3"" />
                                 <Activity name= ""ActivityAddition"" >
 	                              <Handler assembly=""FlexRule.Extensions.Calculator.dll"" type=""FlexRule.Extensions.Calculator.Flows.Nodes.CalculateNodeAdapter"" />	
 	                              <CalculatorAdd firstNumber=""{n1}"" firstNumberType=""string"" secondNumber=""n2"" secondNumberType=""Expression"" return=""sum"" />
                                   <Transition name=""Transition6"" to=""End3"" />
                                 </Activity>
                               </Nodes>
                             </Flow>";
  
             var engine = RuntimeEngine.FromXml(Encoding.UTF8.GetBytes(flow));
             var result = engine.Run(n2);
  
             var sum = result.Context.VariableContainer["sum"];
  
             Assert.Equal(7, sum);
         }
    }
 }

15. Create CustomLicenseProvider class. This is needed so that will Flexrule.Runtime will execute your commands.

using FlexRule.Extensions.Calculator.Tests;
using FlexRule.License;

[assembly: LicenseProvider(typeof(CustomLicenseProvider))]
namespace FlexRule.Extensions.Calculator.Tests
{
    public class CustomLicenseProvider : ILicenseProvider
    {
        public string ReadLicense()
         {
             // return your license context, shown below as a string, copy and paste from your license file.
             return "";
         }
     }
 }

16. Debug and check how things work.

Now let’s discuss how this works and the components of the xml we created in the unit test.

The Declaration node is the Variable Definition in FRD, while Flow is the type of flow.

Start and End are required in creating a project, without it will not run. Setting the transitions identifies the flow of your activities.

The Activity node contains the Handler and your Active Element. Handler is where you specify the assembly and its node type.

CalculatorAdd is the AdditionActiveElement we created. The attributes (ex: firstNumber, firstNumberType, etc.) are the parameters we declared in the class.

Integrating with FRD

You will have to manually create a NuGet package of your extension.

1. Create a nuspec file (FlexRule.Extensions.Calculator.nuspec).

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>FlexRule.Extensions.Calculator</id>
    <version>1.0.0</version>
    <title>FlexRule.Extensions.Calculator</title>
    <authors>Pliant Framework - FlexRule</authors>
    <owners>FlexRule</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <licenseUrl>http://www.flexrule.com/legal/</licenseUrl>
    <projectUrl>http://wiki.flexrule.com/index.php?title=Extensions/Calculator</projectUrl>
    <description>Introduces the Calculator Functionality</description>
    <summary>Introduces the Calculator Functionality</summary>
    <releaseNotes></releaseNotes>
    <copyright>(c) FlexRule</copyright>
    <tags>FLEXRULE EXTENSION</tags>
  </metadata>
  <files>
	  <file src="bin\Release\netstandard2.0\FlexRule.Extensions.Calculator.dll" target="bin\FlexRule.Extensions.Calculator.dll" />
          <file src="Extension.xml" target="Extension.xml" />
          <file src="Icons\**" target="Icons" />    
   </files>
</package>

2. Create Toolbox.Calculator.xml and Extension.xml (this is only needed for local testing since the CI already does this for us)

Toolbox.Calculator.xml

<Add name="Calculator" toolbox="Calculator" documents="Flow" visible="true">
  <Item name="BasicMath" displayName="BasicMath" separator="true" />
  <Item name="CalculatorAdd" wikiId="calculator/#addition" displayName="Addition" iconToolbox="~\Icons\add.png" iconActivity="~\Icons\add.png">
    <Template command="Activity, NodeHandler">
      <Parameters description="Please enter details for Calculator Addition below">
        <Section title="Settings">
	  <Add name="firstNumber" label="First Number" description="Enter First Number" field="TextBox" defaultValue="" />
	  <Add name="secondNumber" label="Second Number" description="Enter Second Number" field="TextBox" defaultValue="" />
	  <Add name="return" label="Copy Sum To" description="Target parameter to hold the Sum result" field="TextBox" />
	</Section>
      </Parameters>
    <Properties>
      <Add name="firstNumber" displayName="First Number" description="First Number to add" linkToParameter="true" />
        <Add name="firstNumberType" attribute="firstNumberType" displayName="Text Value" visible="false" typeOf="firstNumber" type="string" nullable="true" defaultValue="string" options="String,FormattedString,Expression" description="Value type be used on evaluating value of First Number" />
        <Add name="secondNumber" displayName="Second Number" description="Second Number to add" linkToParameter="true" />
	<Add name="secondNumberType" attribute="secondNumberType" displayName="Text Value" visible="false" typeOf="secondNumber" type="string" nullable="true" defaultValue="string" options="String,FormattedString,Expression" description="Value type be used on evaluating value of Second Number" />
	<Add name="return" displayName="Copy Sum To" description="Name of the parameter that holds the Sum result" linkToParameter="true" />
      </Properties>
      <Activity name= "ActivityAddition" >
      <Handler assembly="FlexRule.Extensions.Calculator.dll" type="FlexRule.Extensions.Calculator.Flows.Nodes.CalculateNodeAdapter" />
        <CalculatorAdd firstNumber="#firstNumber#" secondNumber="#secondNumber#" return="#return#" />
      </Activity>
    </Template>
    <References>
      <Model>
        <Declaration>
	  <Define name="#return#" direction="local" />
	</Declaration>
      </Model>
    </References>
  </Item>
</Add>

Extension.xml

<Extension>
	<CustomToolbox>
		<File name="Toolbox.Calculator.xml">
 		    ** copy paste the value of your toolbox xml here **
		</File>
	</CustomToolbox>
</Extension>

3. Create Icons Folder and add this image :

Icons should be PNG file and 16×16 dimensions

4. Download nuget.exe (Windows x86 Commandline) https://www.nuget.org/downloads and save to C:\Windows

5. On your Visual Studio, build your project in Release. Then enter this command to Package Manage Console: nuget pack FlexRule.Extensions.Calculator.nuspec.

Make sure you are in the right project directory

6. Create Nugets folder in your D: drive and paste the NuGet package

7. Open FlexRule Designer, create a project, and install your extension (Tools > Extensions Manager).

8. Enable the extension (Project > Properties > Extensions > Toolbox)

9. Create the flow of your project

10. Run or Debug and inspect the result.

Toolbox XML

As explained earlier we created the value type parameters such as firstNumberType to allow users to use Expression or Formatted String options.

To demonstrate, create a variable on your project.

Edit the properties of the Addition Activity.

After Clicking First Number, you will be able to edit the value. Change the value to FirstNumber and edit the Value Type to Expression.

The linkToParameter enables the helpers as well as the Intellisense.

Now Run or Debug to verify that it did use the value of the FirstNumber. If we didn’t change the Value Type to Expression and have String instead, it will literally use the value “FirstNumber”.

Debugging

When you install an extension, it will copy all the files you declared in your nuspec file to the project’s bin folder.

Thus, it is possible to attach the Designer’s process to your Visual Studio. Just copy and paste your DLL and it’s PDB (symbol) then on Visual studio attach (ctrl + alt + p) the process to Flexrule.Designer.exe

Managing Dependencies

If your extension has dependencies with other NuGet packages, then you will have to include them in your .nuspec file.

For example, this is the bin folder of a Twitter project.

While the nuspec file is this.

Updated on June 18, 2021

Was this article helpful?

Related Articles