HomeGuidesRecipesAPI
HomeGuidesAPILog In

An Action Extension (generally referred to as an Action) supports an event such as: storing web form data in a database, delivering to a document management system (DMS), displaying an on-screen message or sending an alert (e.g. email or SMS). Actions can be trigged at virtually any point along the web form/document generation process.

Infiniti is packaged with several Actions to handle the majority of everyday tasks. But for situations where Infiniti is to integrate with custom systems or support unique circumstances customised Actions can be developed to suit the business. The definition of these custom Action is an Action Extension in version 10.

Action Features and Characteristics

Actions are summarised in the points below

  • Actions are added and removed to Infiniti projects by dragging and dropping them onto a project’s question set in Design.
  • Individual Actions can process the data output by Infiniti and display customized user interfaces on screen, however, an Action usually targets one or the other.
  • Actions are most commonly triggered by the completion of a web form or document generation but can be called during a workflow process as well.
  • Because Actions run after the project answers have been saved, Actions cannot be used in workflow transitions to update project properties.
  • Multiple Actions of the same type can be added to a project, for example, send two emails.
  • Actions can be conditioned so that they are only run when appropriate, for instance, send an email only if the user has indicated.
  • Actions can source attribute and settings dynamically from the question set, for example, retrieve an email address from a data source.
  • Actions can be configured to handle error conditions, for instance, save to temporary store if a database is down.
  • Multiple actions run in a specific order.
  • Actions can output results to pending Actions, for example, a ‘ticket number’ created by the first Action could be sent via email in another Action.
1263

Adding an Action in Design

Action Extension Development Walkthrough

Action Extensions can be developed in-house or by a third party to handle custom or unique situations when necessary. Custom Actions are developed as a module that is deployed to an existing Infiniti environment.

The walkthroughs below have been created using Microsoft’s Visual Studio 2017. All sample code is based version 7.0 of the C# language and version 4.6.2 of the .NET Framework.

1. Open Visual Studio and create a new Class Library Project and give it a meaningful name. For this example, we will use SampleActionExtension. Ensure that the .NET Framework 4.6.2 is selected.

942

2. Rename Class1 to something more meaningful such as Action.

313

3. Click "Yes" to rename all references prompt.

469

4. Add the following references to the project.

.Net References:

  • System.Configuration
  • System.Web

Infiniti References, usually located C:\inetpub\wwwroot\Infiniti\Produce\bin

  • Intelledox.Extension.dll
  • Intelledox.Extension.Action.dll
  • Intelledox.QAWizard.dll
  • Intelledox.QAWizard.Design.dll
  • Intelledox.Model.dll
320

👍

Good Practice

  1. Reference Paths point to correct Infiniti deployment path.
  2. Copy Local property should be set to False for all Infiniti References, as this could corrupt your instance if an older reference is copied to an upgraded site.
1217

Reference Path

320

Copy Local = False

5. Inherit the Intelledox.Extension.Action.ActionConnector and override necessary ActionConnector methods, as per the sample below.

using System;
using System.Threading.Tasks;
using Intelledox.Extension.Action;
using Intelledox.QAWizard;

namespace SampleActionExtension
{
    class Action : Intelledox.Extension.Action.ActionConnector
    {
        public override ExtensionIdentity ExtensionIdentity { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); }

        public override Task<ActionResult> RunAsync(ActionProperties properties)
        {
            throw new NotImplementedException();
        }

        public override bool SupportsRun()
        {
            throw new NotImplementedException();
        }

        public override bool SupportsUI()
        {
            throw new NotImplementedException();
        }
    }
}

👍

Build Successfully

The project should build at this point without error.

6. Implement the ExtensionIdentity property so that it initializes the ExtensionIdentity object containing an Id and a Name to register the Action within Infiniti. The Id needs to be unique and the name is displayed to the user in Design.

public override ExtensionIdentity ExtensionIdentity { get; protected set; }
            = new ExtensionIdentity()
            {
                Id = new Guid("F2B5F504-D3E4-44F2-B978-AC33FA43F038"),
                Name = "Infiniti Simple Action Extension"
 };

📘

Optional Parameter

ModuleId is an optional parameter that provides a licensing module identifier

7. An individual Action can process the data it receives from Infiniti and/or return a UI. For this walkthrough, the Action will concentrate on the web form and generated document binary and not return a UI. Code the SupportsRun() and SupportsUI() to behave as such.

public override bool SupportsRun() => true;
public override bool SupportsUI() => false;

👍

Good Practice

It is best practice to code Action Extensions so that they target the processing of the data provided by Infiniti or return a User Interface but not both.
Usually, in situations where both processing and a UI is required, separate Actions are used.

8. For an Infiniti action, the RunAsync() method is the main entry point into the action where it will perform its specific operations. This method will be called after the form has been submitted or after each document generation for a repeating template.

The RunAsync() method’s return type is an ‘ActionResult’ object containing success flags and messages and information to include in the Infiniti management logs.

Implement the RunAsync() method to return an ‘ActionResult’ object with success flag as follows.

public override Task<ActionResult> RunAsync(ActionProperties properties)
{
  ActionResult results = new ActionResult()
  {
  	Result = Intelledox.QAWizard.Design.ActionResultType.Success
  };
  try
  {
    // Implement custom action details here. Call services, save documents, etc
  }
  catch (Exception ex)
  {
    properties.AddMessage(ex.Message, "Document Action Error");
    results.Result = Intelledox.QAWizard.Design.ActionResultType.Fail;
  }
  return Task.FromResult(results);
}

9. Build your Action to ensure it compiles without error. The Action can now be deployed and tested.

Deploying an Action

Actions are deployed to an Infiniti environment by copying the Action Extension dll file to the Produce bin directory and referencing it within Produce’s appsettings.json file.

  1. Locate your SampleActionExtension.dll file and copy it to the Produce bin directory (usually located C:\inetpub\wwwroot\Infiniti\Produce\bin).

  2. Open the produce appsettings.json file and locate the “Extensions” section of the file. It will most likely contain references to other Actions already.

  3. Add a new Action extension element using the following syntax to the appsettings.json

"ClassName (including namespace), AssemblyName"

Example:

"Extensions": [
    "Intelledox.Extension.DatasourceBuiltin.OleDbDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.SqlServerDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.CsvDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.OdbcDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.RSSDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.WebserviceDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.XmlDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.InfinitiDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.JsonDatasource, Intelledox.Extension.DatasourceBuiltin",
    "Intelledox.Extension.DatasourceBuiltin.RestDatasource, Intelledox.Extension.DatasourceBuiltin",
    "SampleActionExtension.Action, SampleActionExtensions"
  ],
  1. Save the appsettings.json file.

  2. Navigate to Produce in your browser, an absence of error messages suggests the Action has installed correctly.

  3. Open an existing project in Infiniti Design and add the new Action to the question set.

🚧

Load Actions

Make sure appsettings.json file is saved and Produce successfully opens in the browser.

At this stage, the Action doesn’t accomplish anything, or at least it may seem so. Save, publish and run the project.

After the form has been submitted, you will likely just see a Finish page with “No documents were generated.” message. That is because the Action doesn't do anything yet.

Add following code inside the try{} statement of Task<ActionResult> RunAsync(ActionProperties properties) method.

foreach (var doc in properties.Documents)
    {
      using (var docStream = await properties.GetDocumentStreamAsync(doc))
      using (var destStream = new MemoryStream())
      {
        // Write or send the document to a file, external service, etc
        await docStream.CopyToAsync(destStream);
      }
    }

📘

More Example

More examples are available in Intelledox Github account

Debugging Actions

After deploying an Action it can be debugged by attaching Visual Studio to the w3wp.exe process and triggering the Action from Produce.

838

👍

Azure PaaS

Remote Debug Azure App Service

Action Inputs and Outputs

Most Actions will require some input to behave as intended each time it is run. For example, if an email is to be sent the recipient would most likely be sourced from the web form or a data source.

Action Inputs allow the configuration of such values in Design so that they can be referenced within the code. Some actions may also want to return one or many values to Infiniti, they are called Action Outputs and could be easily referenced in a form. For example, if a reference number was produced after the creation of some entity it could be returned to Infiniti and included on screen, in an email, SMS etc.

1537

Action Inputs and Outputs are identified by a GUID; it is best practice to declare this GUID as a global variable.

private readonly Guid _basicInput = new Guid("9B1B2C85-841E-4F85-841F-93244B2D4C29");
private readonly Guid _multiValueInput = new Guid("8FE0739D-ABB1-4600-B6C9-E149A05E418F");
private readonly Guid _keyValueInput = new Guid("A71C3613-2F4A-4114-B71A-1A068D6D92D2");
private readonly Guid _output = new Guid("3CD44B70-C70E-48FA-91A6-EF5B4A955550");

Action Inputs

Infiniti provides a set of new methods that facilitate the development process. To get Action Inputs as a List, override the following method:

public override List<AvailableInput> GetAvailableInputs()
{
  return new List<AvailableInput>()
  {
    new AvailableInput()
    {
      Id = _basicInput,
      Name = "Basic"
    },

    new AvailableInput()
    {
      Id = _multiValueInput,
      Name = "Multi",
      InstanceLimit = 0
    },

    new AvailableInput()
    {
      Id = _keyValueInput,
      Name = "KeyValue",
      InstanceLimit = 0,
      IsKeyValue = true
    }
  };
}

There are 6 different Available Input properties:

  • Id: Unique id for an input.
  • Name: Display name for an input.
  • InstanceLimit: InstanceLimit is used to configure an unlimited number of uses of input for a given action by setting InstanceLimit property value to 0. You can also have a Multi-value Input with a limit on how many times it can be passed in. It is rare but it can be defined.
  • IsKeyValue: IsKeyValue is used to configure inputs that will display a name and a value in Design, by setting IsKeyValue property value to true. Useful for configurable input names or matching key/value parameters in external.
  • Required: By settings Required property value to true, an input will be mandatory.
  • AllowTranslation: By settings AllowTranslation property value to true, the input will be included in the project translation file.

Within RunAsync method, use following code to get Action Inputs:

bool basicInputValue = properties.GetInputValue(_basicInput, false);

IList<int> multiInputValue = properties.GetInputValueList<int>(_basicInput);

IDictionary<string, string> keyValueInputValue = properties.GetInputKeyValues<string>(_basicInput);

Action Outputs

Action Outputs is required to override the following method:

public override List<AvailableOutput> GetAvailableOutputs()
{
  return new List<AvailableOutput>()
  {
    new AvailableOutput()
    {
      Id = _output,
      Name = "Custom Output"
    }
  };

After some code has been executed, some results may require being returned in Infiniti Design. Add following code within RunAsync() method.

var result = new ActionResult()
{
  Result = Intelledox.QAWizard.Design.ActionResultType.Success,

  Outputs = new List<Intelledox.Model.ActionOutput> {
    // Return an output value as part of the results.
    new Intelledox.Model.ActionOutput()
    {
      ID = _output,
      Name = "Custom Output",
      Value = "myOutputValue"
    }
  }
};

🚧

Handling Concurrency

It is important to note Infiniti creates a single Action object at runtime and only local variables or global variables with constant values should be implemented. If multiple Actions are sharing a single global variable at runtime conflicts can occur.

Final Full Code

using Intelledox.Extension.Action;
using Intelledox.QAWizard;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace SampleActionExtensions
{
    public class DocumentAction : ActionConnector
    {
        private readonly Guid _basicInput = new Guid("9B1B2C85-841E-4F85-841F-93244B2D4C29");
        private readonly Guid _multiValueInput = new Guid("8FE0739D-ABB1-4600-B6C9-E149A05E418F");
        private readonly Guid _keyValueInput = new Guid("A71C3613-2F4A-4114-B71A-1A068D6D92D2");
        private readonly Guid _output = new Guid("3CD44B70-C70E-48FA-91A6-EF5B4A955550");

        public override ExtensionIdentity ExtensionIdentity { get; protected set; }
            = new ExtensionIdentity()
            {
                Id = new Guid("D2FC0284-BF5E-41E8-9B05-340378D61710"),
                Name = "Infiniti Document Action Extension"
            };
        public override List<AvailableInput> GetAvailableInputs()
        {
            return new List<AvailableInput>()
                  {
                      new AvailableInput()
                      {
                          Id = _basicInput,
                          Name = "Basic"
                      },

                      new AvailableInput()
                      {
                          Id = _multiValueInput,
                          Name = "Multi",
                          InstanceLimit = 0
                      },

                      new AvailableInput()
                      {
                          Id = _keyValueInput,
                          Name = "KeyValue",
                          InstanceLimit = 0,
                          IsKeyValue = true
                      }
                  };
        }

        public override List<AvailableOutput> GetAvailableOutputs()
        {
            return new List<AvailableOutput>()
            {
                new AvailableOutput()
                {
                    Id = _output,
                    Name = "Custom Output"
                }
            };
        }
        public async override Task<ActionResult> RunAsync(ActionProperties properties)
        {
            ActionResult results = new ActionResult()
            {
                Result = Intelledox.QAWizard.Design.ActionResultType.Success
            };

            try
            {
                bool basicInputValue = properties.GetInputValue(_basicInput, false);

                IList<int> multiInputValue = properties.GetInputValueList<int>(_basicInput);

                IDictionary<string, string> keyValueInputValue = properties.GetInputKeyValues<string>(_basicInput);

                //Implement custom action details here. Call services, save documents, etc

                foreach (var doc in properties.Documents)
                {
                    using (var docStream = await properties.GetDocumentStreamAsync(doc))
                    using (var destStream = new MemoryStream())
                    {
                        // Write or send the document to a file, external service, etc
                        await docStream.CopyToAsync(destStream);
                    }
                }

                var result = new ActionResult()
                {
                    Result = Intelledox.QAWizard.Design.ActionResultType.Success,

                    Outputs = new List<Intelledox.Model.ActionOutput> {
                    // Return an output value as part of the results.
                    new Intelledox.Model.ActionOutput()
                    {
                        ID = _output,
                        Name = "Custom Output",
                        Value = "myOutputValue"
                    }
                }
                };
            }
            catch (Exception ex)
            {
                properties.AddMessage(ex.Message, "Document Action Error");
                results.Result = Intelledox.QAWizard.Design.ActionResultType.Fail;
            }

            return results;
        }

        public override bool SupportsRun() => true;

        public override bool SupportsUI() => false;
    }
}

Additional Action Properties

All the action properties that are passed in to the RunAsync() method are available on the properties parameter in the above example. This parameter's class is Intelledox.QAWizard.ActionProperties and a range of action properties and methods are available.

Some examples include:

  • ActionInputs
  • ActionKey
  • Context
  • Documents
  • GetDocumentStream

You can use some of the methods on the Intelledox.QAWizard.ActionProperties class to manipulate returned objects, you can also access additional methods by looking at the various controllers available in the Intelledox.Controller library.