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 |
- Develop a new setup table which implements the specific activity settings.
- Develop a new page based on the setup table
- Develop a handler codeunit.
- Develop a configuration check codeunit.
- Develop a configuration xml port.
- 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.
- 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 |
- 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.
- 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 |
- Create a new al codeunit.
- Add the appropriate to implement the behavior of the pipeline element.
- 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;
- 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.
- Create a new al codeunit
- Declare global variables
var
CustomPipelineElement: Record "Your custom setup table";
ConfigTestRunner: Codeunit "BIS Configuration Test Runner";
- 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;
- 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.
- Create a new al XmlPort
- 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).
- 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;
- 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;
}