Business Integration Solutions Documentation

Custom Pipeline Element

Business Integration Solution has been provided with a standard set of Endpoints, Events and Activities. The pipeline architecture has been provided with extension points which allows you to extend this standard set. Examples of this extension point are: a custom endpoint which connect with a specific web service, implement an unsupported format. It is possible to extend the set without changing the standard product.

Prerequisites

The following prerequisites are required

  • experience in developing in AL
  • understanding of Business Integrated Solutions and especially the concept of pipeline and messages
  • a Dynamics 365 Business Central development license
  • experience with the Dynamics 365 Business Central test framework

How To: Create a custom element?

Use this task when you want to extend the list of standard activities with custom types.

Steps

The pipeline uses a generic system which can be enhanced with a specific configuration for each activity.

Name Description Mandatory
Setup table Table with a fixed primary key which maintains the activity configuration No
Setup Page Page which maintains the activity configuration No
Handler codeunit The codeunit which implements the functionality of the activity Yes
Configuration check codeunit A codeunit which runs the activity specific tests, activated via the 'Check Configuration' function. No
Configuration xml port An xml port which exchanges the details of the Pipeline Element to a fixed format. No
  1. Develop a new setup table which implements the specific activity settings.
  2. Develop a new page based on the setup table
  3. Develop a handler codeunit.
  4. Develop a configuration check codeunit.
  5. Develop a configuration xml port.
  6. Create an Element Type record for the activity. The element type defines the settings for an activity. It connects the Dynamics 365 Business Central objects to the configuration.

Tip: Using the 'Register' function of the Handler Codeunit, can easily automate the registration process.

How To: Create a setup table for an element?

Use this task when you want create a setup table with specific settings for an endpoint, activity or event.

Steps

The setup table can be used to register specific settings for a pipeline element. This makes it possible to extend the framework by adding new settings with specific fields and validations.

  1. Create a new al table object.

Each activity has its own table which maintains the specific settings. This makes it possible to make validations which are relevant for this activity only. Since this table is an extension of the generic "BIS Pipeline Element" table, it must have the same primary key. The primary key consists of the following fields:

Field No. Field Name Data Type Length
1 Source Type Integer
2 Source No. Code 20
3 Activity Code Code 20
  1. Add the specific fields for the custom pipeline element.

It is recommended to use a specific range for the new fields, for example 50000 and more.

  1. Since a setup table is not a mandatory step when creating a custom pipeline element, in scenario you don't need one, the register function should use a standard STAEDEAN page "BIS No Settings Setup".

How To: Create a handler codeunit for an element?

Create a handler codeunit for a custom endpoint, activity or event to implement the behavior of the pipeline element.

Steps

The handler codeunit implements the actual behavior of a pipeline element. The 'OnRun' trigger must have a signature which matches the element type. The following signatures are supported.

Element Type Subtype Table
Endpoint Reader Job Queue Entry
Endpoint Writer BIS Message Queue
Activity BIS Message Queue
Event Job Queue Entry
  1. Create a new al codeunit.
  2. Add the appropriate to implement the behavior of the pipeline element.
  3. Optional: Add a new function called 'Register'. This function enables automated registration of the pipeline element, when your extension gets installed.

An install codeunit can be used to autoregister the custom elements.

//Example of register method for an endpoint
local procedure Register(sender: Codeunit "BIS API Pipeline Setup")
var
    CustomElementDescription_Txt: Label 'Description of what your element does';
    CustomElementCode: Code[20];
begin
    CustomElementCode := 'CustomElementCode';
    sender.RegisterEndpoint(
        CustomElementCode, CustomElementDescription_Txt, 'Setup Table Id', 'Setup Page Id', 'Handler codeunit id', 'Configuration check handler codeunit id', 'Xml port handler id', 'ElementSubType - Option that changes depending on what you are trying to register');
end;
  1. In case you want logging to be enabled for your new custom element, you have to subscribe to two events from codeunit 'BIS API Pipeline Setup'. Example below.
[EventSubscriber(ObjectType::Codeunit, codeunit::"BIS API Pipeline Setup", 'OnEnableChangeLog', '', true, true)]
local procedure OnChangelogEnable(var sender: codeunit "BIS API Pipeline Setup")
begin
    Sender.AddTableToChangeLog("Your setup table id");
end;

[EventSubscriber(ObjectType::Codeunit, codeunit::"BIS API Pipeline Setup", 'OnDisableChangeLog', '', true, true)]
local procedure OnChangeLogDisable(var sender: codeunit "BIS API Pipeline Setup")
begin
    Sender.RemoveFromChangelog("Your setup table id");
end;

How To: Create a configuration check codeunit for an element?

Use this task when you want to create a configuration check codeunit to verify the settings for an endpoint, event or activity.

Steps

A configuration check codeunit is a Dynamics 365 Business Central codeunit which is executed by a special Test Runner codeunit. The advantage of this construction is that the tests can be implemented in separate functionals which are executed individually. Errors which might occur during this run are captured by the test handler and written to a log table.

  1. Create a new al codeunit
  2. Declare global variables
var
    CustomPipelineElement: Record "Your custom setup table";
    ConfigTestRunner: Codeunit "BIS Configuration Test Runner";
  1. Optional: Add initialization code to the OnRun trigger
trigger OnRun()
var
    RecRef: RecordRef;
begin 
    ConfigTestRunner.CurrentRecord(RecRef);
    RecRef.SetTable(CustomPipelineElement);
    RunTests(); //In run tests, call your test cases, full example at step 6
end;
  1. Add your try functions to test your configuration.

When we ported to cloud, we had to replace test functions (not supported in SaaS) with try functions, you can see a full example at step 5 5. Ensure the configuration check codeunit is included in the 'Register' function of the handler codeunit See 'How To: Create a handler codeunit for an element?'

Example of a check configuration codeunit, from the 'Attachment Generator' pipeline element

codeunit 11070201 "BIS Attachment Generator Test"
{
    trigger OnRun()
    var
        RecRef: RecordRef;
    begin
        ConfigTestRunner.CurrentRecord(RecRef);
        RecRef.SetTable(AttachmentGenerator);

        AttachmentLine.SetRange("Source Type", AttachmentGenerator."Source Type");
        AttachmentLine.SetRange("Source No.", AttachmentGenerator."Source No.");
        AttachmentLine.SetRange("Activity Code", AttachmentGenerator."Activity Code");
        RunTests();
    end;

    var
        AttachmentGenerator: Record "BIS Attachment Generator";
        AttachmentLine: Record "BIS Attachment Generator Line";
        ConfigTestRunner: Codeunit "BIS Configuration Test Runner";

    local procedure RunTests()
    var
        CheckAttachmentsLbl: Label 'CheckAttachments', Locked = true;
        CheckAttachmentGeneratorTypeLbl: Label 'CheckAttachmentGeneratorType', Locked = true;
    begin
        if not CheckAttachments() then
            ConfigTestRunner.LogErrorMessage(CheckAttachmentsLbl, GetLastErrorText());
        if not CheckAttachmentGeneratorType() then
            ConfigTestRunner.LogErrorMessage(CheckAttachmentGeneratorTypeLbl, GetLastErrorText());
    end;

    [TryFunction]
    procedure CheckAttachments()
    begin
        AttachmentLine.FindSet();

        repeat
            case AttachmentLine."Attachment Type" of
                AttachmentLine."Attachment Type"::Report:
                    begin
                        AttachmentLine.TestField("Object No.");
                        AttachmentLine.TestField("Attachment Name");
                    end;
                AttachmentLine."Attachment Type"::File:
                    AttachmentLine.TestField("Attachment Content");
            end;
        until AttachmentLine.Next() = 0;
    end;

    [TryFunction]
    procedure CheckAttachmentGeneratorType()
    var
        GeneratorEnum: Enum "BIS Attachment Generator";
        GeneratorNumber: Integer;
        LineGeneratorNotValidErr: Label 'Attachment line %1 generator not valid. Please select from the available options.', Comment = '%1 Line No.';
    begin
        AttachmentLine.FindSet();
        repeat
            Evaluate(GeneratorNumber, Format(AttachmentLine.Generator, 0, 2));
            if not GeneratorEnum.Ordinals.Contains(GeneratorNumber) then
                Error(LineGeneratorNotValidErr, AttachmentLine."Line No.");
        until AttachmentLine.Next() = 0;
    end;
}

How To: Create a Configuration XmlPort for an Element?

Use this task when you want to create a configuration xmlport for an endpoint, event or activity

Steps

The configuration xmlport allows specific xml elements to be exchanged for a pipeline element. Using an xmlport for importing / exporting configuration settings makes it possible to decouple the internal table structure from an external representation of the data.

  1. Create a new al XmlPort
  2. Define the layout of the XmlPort

The xml port layout doesn't need to include all the data from the setup table. STAEDEAN does not export/import client sensitive data (ex. passwords, credit card information).

  1. Add a new function named 'GetTargetRecord' and call it in trigger 'OnAfterInitRecord' of the root table in the port. This function is mandatory for importing data. This function is called by the generic part of the configuration package reader and is used to set the primary key fields of the pipeline element.
...
trigger OnAfterInitRecord()
begin
    GetTargetRecord();
    "Your custom table" := TargetRecord;
end;
...
var
    TargetRecord: Record "Your custom table id";

procedure GetTargetRecord()
var
    PackageEvents: codeunit "BIS Package Events";
    NewTarget: RecordRef;
begin
    PackageEvents.GetTargetRecord(NewTarget);
    if newtarget.number = "Your custom table id" then
        newtarget.settable(TargetRecord);
end;
  1. Ensure the configuration xmlport is included in the Register function of the handler codeunit.

See 'How To: Create a handler codeunit for an element?'

Example of an xml port, from the 'Azure File Reader' pipeline element

xmlport 11070207 "BIS Az File Endpoint"
{
    FormatEvaluate = Xml;

    schema
    {
        tableelement("BIS Az File Reader Setup"; "BIS Az File Endpoint Setup")
        {
            XmlName = 'AzFileReader';
            textattribute(Version)
            {

                trigger OnBeforePassVariable()
                begin
                    Version := '1.0';
                end;
            }
            fieldelement(FolderName; "BIS Az File Reader Setup"."Folder Name")
            {
            }
            fieldelement(LeaveFilesOnFIleSystem; "BIS Az File Reader Setup"."Leave Files")
            {
            }
            fieldelement(ReadNewFilesOnly; "BIS Az File Reader Setup"."Read New Files Only")
            {
            }
            fieldelement(FileNameFilter; "BIS Az File Reader Setup"."File Name")
            {
            }
            fieldelement(FileShare; "BIS Az File Reader Setup"."File Share")
            {
            }
            fieldelement(StorageAccount; "BIS Az File Reader Setup"."Storage Account")
            {
            }
            fieldelement(Overwrite; "BIS Az File Reader Setup".Overwrite)
            {
            }
            fieldelement(UseOriginalMessage; "BIS Az File Reader Setup"."Use Original Message")
            {
            }


            trigger OnAfterInitRecord()
            begin
                GetTargetRecord();
                "BIS Az File Reader Setup" := TargetRecord;
            end;

        }
    }

    requestpage
    {

        layout
        {
        }

        actions
        {
        }
    }

    var
        TargetRecord: Record "BIS Az File Endpoint Setup";

    procedure GetTargetRecord()
    var
        PackageEvents: codeunit "BIS Package Events";
        NewTarget: RecordRef;
    begin
        PackageEvents.GetTargetRecord(NewTarget);
        if newtarget.number = Database::"BIS Az File Endpoint Setup" then
            newtarget.settable(TargetRecord);
    end;
}