Business Integration Solutions documentation

Custom pipeline element

Business Integration Solutions provides a standard set of endpoints, events, and activities. The pipeline architecture includes extension points that let you extend this standard set, for example, a custom endpoint that connects to a specific web service or implements an unsupported format. You can extend the set without changing the standard product.

Prerequisites

The following prerequisites are required:

  • Experience developing in AL.
  • Understanding of Business Integration 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 that you can enhance with specific configuration for each activity.

Name Description Mandatory
Setup table Table with a fixed primary key that maintains the activity configuration. No
Setup page Page that maintains the activity configuration. No
Handler codeunit The codeunit that implements the functionality of the activity. Yes
Configuration check codeunit A codeunit that runs the activity-specific tests, activated via the Check Configuration function. No
Configuration XmlPort An XmlPort that exchanges the details of the pipeline element to a fixed format. No
  1. Develop a new setup table that 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 XmlPort.

  6. Create an Element Type record for the activity. The element type defines the settings for an activity and connects the Dynamics 365 Business Central objects to the configuration.

    Using the Register function of the Handler Codeunit can automate the registration process.

How to: Create a setup table for an element

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

Steps

The setup table registers specific settings for a pipeline element, letting you 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 that maintains the specific settings, enabling validations relevant only to that activity. 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
  2. Add the specific fields for the custom pipeline element.

    Use a specific range for new fields, for example, 50000 and higher.

  3. When a setup table is not needed for your custom pipeline element, the register function should use the 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 matching 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 code 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 installs.

    An install codeunit can auto-register 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;
    
  4. To enable logging for your new custom element, subscribe to two events from codeunit BIS API Pipeline Setup:

    [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 executed by a special test runner codeunit. This construction lets you implement tests in separate functions that execute individually. Errors that occur during this run capture in the test handler and write 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";
    
  3. (Optional) Add initialization code to the OnRun trigger:

    trigger OnRun()
    var
        RecRef: RecordRef;
    begin
        ConfigTestRunner.CurrentRecord(RecRef);
        RecRef.SetTable(CustomPipelineElement);
        RunTests(); // In RunTests, call your test cases, see step 5 for a full example
    end;
    
  4. Add try functions to test your configuration.

    When porting to cloud, replace test functions (not supported in SaaS) with try functions, see the full example below.

  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 configuration check 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 lets specific XML elements exchange for a pipeline element. Using an XmlPort for importing and exporting configuration settings decouples 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 XmlPort layout does not need to include all data from the setup table. STAEDEAN does not export or import client-sensitive data such as passwords or credit card information.

  3. Add a new function named GetTargetRecord and call it in the OnAfterInitRecord trigger of the root table in the port. This function is mandatory for importing data. The generic part of the configuration package reader calls it 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;
    
  4. 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 XmlPort 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;
        }
    }

    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;
}