BINDSUBSCRIPTION is a function in Dynamics NAV and Business Central that is not very well know (yet) but it is very handy on several situations.
If you’re already embracing the beautiful (and challenging) world of events then you’ll surely use the BINDSUBSCRIPTION function.
In this article I will present you with 3 usage examples. None of them are real business cases and a few coding best practices were ignored for sake of simplicity.
Usage Examples
Example 1 – Activate subscribers on demand
I have a specific process where I want to print an invoice but I want to use a different report than the report configured in reports selection. Please note that I want to “replace” the report only when this process is being executed.
To do the replacement you can subscribe the OnAfterSubstituteReport event published in codeunit ReportManagement. For more info, please check this url: https://robertostefanettinavblog.com/2020/01/23/substituting-reports/ (thanks Roberto Stefanetti).
So, you’re already using events, right? Then you know that when you subscribe an event, the subscriber is called by the publisher every time, right? Well, sort of… There’s a property called EventSubscriberInstance (https://docs.microsoft.com/en-us/dynamics-nav/eventsubscriberinstance-property) that allows you to indicate if the subscribers should be bound to events automatically or manually. To bound subscribers to events manually the property must be set to Manual.
I’ve created a new codeunit for the purpose of replacing the report to be used, that subscribes event OnAfterSubstituteReport in codeunit ReportManagement.
codeunit 50101 "Substitute The Report"
{
EventSubscriberInstance = Manual;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"ReportManagement", 'OnAfterSubstituteReport', '', true, true)]
local procedure "ReportManagement_OnAfterSubstituteReport"(ReportId: Integer; RunMode: Option; RequestPageXml: Text; RecordRef: RecordRef; var NewReportId: Integer)
begin
IF ReportId = REPORT::"Sales - Invoice" THEN
NewReportId := REPORT::"Standard Sales - Invoice";
end;
}
Please note that the property EventSubscriberInstance is set to Manual so these subscribers won’t be called unless they’re activated. To activate the subscribers I must bind the subscription.
I’ve created two codeunits to show two different behaviors. I believe the codeunit name is self explanatory.
Codeunit -> Will Print Original Report
codeunit 50102 "Will Print Original Report"
{
trigger OnRun()
var
SalesInvoiceHeader: Record "Sales Invoice Header";
begin
SalesInvoiceHeader.FindFirst();
REPORT.RunModal(REPORT::"Sales - Invoice", true, true, SalesInvoiceHeader);
end;
}
Codeunit -> Print the Substitute Report
codeunit 50103 "Print the Substitute Report"
{
trigger OnRun()
var
SalesInvoiceHeader: Record "Sales Invoice Header";
SubstituteTheReport: Codeunit "Substitute The Report";
begin
SalesInvoiceHeader.FindFirst();
BindSubscription(SubstituteTheReport);
REPORT.RunModal(REPORT::"Sales - Invoice", true, true, SalesInvoiceHeader);
UnbindSubscription(SubstituteTheReport); // Not necessary because when SubstituteTheReport codeunit goes out of scope, all bindings are removed.
end;
}
I’ve extended the Customer List page to add two actions to test the codeunits:
pageextension 50101 "CustomerListExt" extends "Customer List"
{
actions
{
addlast(Creation)
{
group(MyActionGroup)
{
Action(MyAction1)
{
ApplicationArea = All;
Caption = 'Print Report - No Bind';
RunObject = codeunit "Will Print Original Report";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
Action(MyAction2)
{
ApplicationArea = All;
Caption = 'Print Report - Bind';
RunObject = codeunit "Will Print Substituted Report";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
}
}
}
}
Here’s the output when I run codeunit Will Print Original Report. “Sales – Invoice” report is printed.
But if I run codeunit Will Print Substituted Report, then “Standard Sales – Invoice” report is printed instead because the subscribers in codeunit are bound.
Example 2 – Set global information in the subscribers codeunit
In this example I want to post a gen. journal and use the last entry no. later on.
I’ve created the following objects to demonstrate this.
Codeunit Example 2 – Aux Methods -> The codeunit is used to create and post a gen. journal line.
codeunit 50107 "Example 2 - Aux Methods"
{
procedure CreateAndPostGenJnlLine()
var
GenJournalLine: Record "Gen. Journal Line";
GenJnlPostBatch: Codeunit "Gen. Jnl.-Post Batch";
begin
// For demo purposes only - Values hardcoded.
GenJournalLine.INIT;
GenJournalLine.SetHideValidation(TRUE);
GenJournalLine.VALIDATE("Source Code", 'GENJNL');
GenJournalLine.VALIDATE("Journal Template Name", 'GENERAL');
GenJournalLine.VALIDATE("Journal Batch Name", 'DEFAULT');
GenJournalLine.VALIDATE("Line No.", 10000);
GenJournalLine.VALIDATE("Account Type", GenJournalLine."Account Type"::"G/L Account");
GenJournalLine.VALIDATE("Document No.", '.');
GenJournalLine.VALIDATE("Account No.", '2910');
GenJournalLine.VALIDATE("Document Date", WorkDate());
GenJournalLine.VALIDATE("Posting Date", WorkDate());
GenJournalLine.VALIDATE(Amount, 100);
GenJournalLine.VALIDATE("Bal. Account Type", GenJournalLine."Bal. Account Type"::"G/L Account");
GenJournalLine.VALIDATE("Bal. Account No.", '2910');
GenJournalLine.VALIDATE("Bal. Gen. Posting Type", GenJournalLine."Bal. Gen. Posting Type"::" ");
GenJournalLine.VALIDATE("Bal. Gen. Bus. Posting Group", '');
GenJournalLine.VALIDATE("Bal. Gen. Prod. Posting Group", '');
GenJournalLine.VALIDATE("Bal. VAT Prod. Posting Group", '');
GenJournalLine.VALIDATE("Bal. VAT Bus. Posting Group", '');
GenJournalLine.INSERT(TRUE);
GenJnlPostBatch.Run(GenJournalLine);
end;
}
Codeunit Storage -> Created to subscribe the OnAfterGLFinishPosting event and save the Last Entry No. in variable global PostedEntryNo.
Please note the the EventSubscriberInstance property is set to Manual.
codeunit 50106 "Storage"
{
EventSubscriberInstance = Manual;
var
PostedEntryNo: Integer;
procedure GetPostedEntryNo(): Integer
begin
exit(PostedEntryNo);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnAfterGLFinishPosting', '', true, true)]
local procedure "Gen. Jnl.-Post Line_OnAfterGLFinishPosting"(GLEntry: Record "G/L Entry"; var GenJnlLine: Record "Gen. Journal Line"; IsTransactionConsistent: Boolean; FirstTransactionNo: Integer; var GLRegister: Record "G/L Register"; var TempGLEntryBuf: Record "G/L Entry"; var NextEntryNo: Integer; var NextTransactionNo: Integer)
begin
PostedEntryNo := NextEntryNo;
end;
}
Codeunit Example 2 – No Bind. This is the codeunit that will be executed to show the first scenario. I didn’t use the BINDSUBSCRIPTION so the saved value in variable PostedEntryNo, in codeunit Storage, is lost and not retrieved when GetPostedEntryNo method is called.
codeunit 50104 "Example 2 - No Bind"
{
var
Storage: Codeunit "Storage";
Aux: Codeunit "Example 2 - Aux Methods";
trigger OnRun()
begin
Aux.CreateAndPostGenJnlLine();
// Do additional processing...
MESSAGE(Format(Storage.GetPostedEntryNo()));
end;
}
Codeunit Example 2 – Bind -> This is the codeunit that will be executed to show the second scenario. This time I used the BINDSUBSCRIPTION so the saved value in variable PostedEntryNo, in codeunit Storage, is not lost and is correctly retrieved when GetPostedEntryNo method is called.
When BINDSUBSCRIPTION is used, you control the lifespan of the subscriber codeunit so the instance is stored and the content is not cleared and can be accessed until the codeunit subscription is unbound.
codeunit 50105 "Example 2 - Bind"
{
var
Storage: Codeunit "Storage";
Aux: Codeunit "Example 2 - Aux Methods";
trigger OnRun()
begin
BindSubscription(Storage);
Aux.CreateAndPostGenJnlLine();
MESSAGE(Format(Storage.GetPostedEntryNo()));
UnbindSubscription(Storage);
end;
}
Once again, I’ve extended the Customer List page to add two actions to test the codeunits:
pageextension 50102 "CustomerListExt" extends "Customer List"
{
actions
{
addlast(Creation)
{
group(MyActionGroup)
{
Action(MyAction1)
{
ApplicationArea = All;
Caption = 'Post Gen. Jnl. Line - No Bind';
RunObject = codeunit "Example 2 - No Bind";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
Action(MyAction2)
{
ApplicationArea = All;
Caption = 'Post Gen. Jnl. Line - Bind';
RunObject = codeunit "Example 2 - Bind";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
}
}
}
}
When I press the Post Gen. Jnl. Line – No Bind action, the codeunit Example 2 – No Bind is executed and the following message is retrieved because the posted G/L Entry No. stored in variable PostedEntryNo was lost:
But if I press the Post Gen. Jnl. Line – Bind action, the codeunit Example 2 – Bind is executed and the entry no. is correctly retrieved from the PostedEntryNo variable because the codeunit was bound and the instance is stored until the codeunit is unbound.
Example 3 – Reusing the subscriber but changing behavior
Another scenario I’ve tried was to have a subscriber that I want to be called every time. But on some specific processes I want it to have a different behavior.
Imagine that you want to log in a new table the last entry no. inserted in the G/L Entry, every time a post occurs. You can do that subscribing the OnAfterGLFinishPosting publisher event on codeunit Gen. Jnl.-Post Line. But on some specific scenarios you want the subscriber to have a different behavior. For example, showing a message to the user with the entry no. inserted in the Logging table.
First I’ve created a new table for the log that will keep the last G/L Entry No. inserted on each post.
table 50100 "Logging"
{
DataClassification = ToBeClassified;
fields
{
field(1; "Entry No."; Integer)
{
DataClassification = ToBeClassified;
AutoIncrement = true;
}
field(2; "G/L Entry No."; Integer)
{
DataClassification = ToBeClassified;
}
}
keys
{
key(PK; "Entry No.")
{
Clustered = true;
}
}
}
Next, I’ve created the codeunit that will subscribe OnAfterGLFinishPosting on codeunit Gen. Jnl.-Post Line. This subscriber will be called every time a gen. journal line is posted.
I’ll have to do some changes in this codeunit but this would be an example of the codeunit if I just wanted to save the log.
codeunit 50109 "G/L Logger Subscriber"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnAfterGLFinishPosting', '', true, true)]
local procedure "Gen. Jnl.-Post Line_OnAfterGLFinishPosting"(GLEntry: Record "G/L Entry"; var GenJnlLine: Record "Gen. Journal Line"; IsTransactionConsistent: Boolean; FirstTransactionNo: Integer; var GLRegister: Record "G/L Register"; var TempGLEntryBuf: Record "G/L Entry"; var NextEntryNo: Integer; var NextTransactionNo: Integer)
begin
AddGLEntryToLog(GLEntry."Entry No.");
end;
procedure AddGLEntryToLog(GLEntryNo: Integer): Integer
var
Logging: Record Logging;
begin
Logging.Init();
Logging."G/L Entry No." := GLEntryNo;
Logging.Insert();
Message('New entry added to Logging');
exit(Logging."Entry No.");
end;
}
I will need an additional codeunit for the specific processes. In some specific processes I want to do the logging but I also want to show a message to the user with the logging entry no.
codeunit 50110 "G/L Logger/Message Subscriber"
{
EventSubscriberInstance = Manual;
var
GLLogger: Codeunit "G/L Logger Subscriber";
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnAfterGLFinishPosting', '', true, true)]
local procedure "Gen. Jnl.-Post Line_OnAfterGLFinishPosting"(GLEntry: Record "G/L Entry"; var GenJnlLine: Record "Gen. Journal Line"; IsTransactionConsistent: Boolean; FirstTransactionNo: Integer; var GLRegister: Record "G/L Register"; var TempGLEntryBuf: Record "G/L Entry"; var NextEntryNo: Integer; var NextTransactionNo: Integer)
begin
Message(Format(GLLogger.AddGLEntryToLog(GLEntry."Entry No.")));
end;
}
Please note that the EventSubscriberInstance property is set to Manual.
What’s going to happen if I bind the subscription in codeunit G/L Logger/Message Subscriber? The log will occur twice because both events will be called. To avoid it I will have to make some changes in both codeunits.
Codeunit G/L Logger/Message Subscriber will be changed to:
codeunit 50110 "G/L Logger/Message Subscriber"
{
EventSubscriberInstance = Manual;
var
GLLogger: Codeunit "G/L Logger Subscriber";
IsBound: Boolean;
procedure SetIsBound()
begin
IsBound := true;
end;
procedure GetIsBound(): Boolean
begin
exit(IsBound);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnAfterGLFinishPosting', '', true, true)]
local procedure "Gen. Jnl.-Post Line_OnAfterGLFinishPosting"(GLEntry: Record "G/L Entry"; var GenJnlLine: Record "Gen. Journal Line"; IsTransactionConsistent: Boolean; FirstTransactionNo: Integer; var GLRegister: Record "G/L Register"; var TempGLEntryBuf: Record "G/L Entry"; var NextEntryNo: Integer; var NextTransactionNo: Integer)
begin
Message(Format(GLLogger.AddGLEntryToLog(GLEntry."Entry No.")));
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"G/L Logger Subscriber", 'OnCheckSubscriberIsBound', '', true, true)]
local procedure IsBound_OnCheckSubscriberIsBound_GLLoggerSubscriber(var CodeunitIsBound: Boolean);
begin
CodeunitIsBound := IsBound;
end;
}
Codeunit G/L Logger Subscriber will be changed to:
codeunit 50109 "G/L Logger Subscriber"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnAfterGLFinishPosting', '', true, true)]
local procedure "Gen. Jnl.-Post Line_OnAfterGLFinishPosting"(GLEntry: Record "G/L Entry"; var GenJnlLine: Record "Gen. Journal Line"; IsTransactionConsistent: Boolean; FirstTransactionNo: Integer; var GLRegister: Record "G/L Register"; var TempGLEntryBuf: Record "G/L Entry"; var NextEntryNo: Integer; var NextTransactionNo: Integer)
var
IsBound: Boolean;
begin
OnCheckSubscriberIsBound(IsBound);
if IsBound then
exit;
AddGLEntryToLog(GLEntry."Entry No.");
end;
procedure AddGLEntryToLog(GLEntryNo: Integer): Integer
var
Logging: Record Logging;
begin
Logging.Init();
Logging."G/L Entry No." := GLEntryNo;
Logging.Insert();
Message('New entry added to Logging');
exit(Logging."Entry No.");
end;
[IntegrationEvent(false, false)]
local procedure OnCheckSubscriberIsBound(var CodeunitIsBound: Boolean)
begin
end;
}
I’ve created two codeunits to simulate the behavior on both situations.
codeunit 50112 "Example 3 - No Bind"
{
var
Aux: Codeunit "Example 3 - Aux Methods";
trigger OnRun()
begin
Aux.CreateAndPostGenJnlLine();
end;
}
codeunit 50113 "Example 3 - Bind"
{
var
Aux: Codeunit "Example 3 - Aux Methods";
LoggerMessage: Codeunit "G/L Logger/Message Subscriber";
trigger OnRun()
begin
BindSubscription(LoggerMessage);
Aux.CreateAndPostGenJnlLine();
UnbindSubscription(LoggerMessage);
end;
}
I’ve extended the Customer List page to add two actions to test the codeunits:
pageextension 50102 "CustomerListExt" extends "Customer List"
{
actions
{
addlast(Creation)
{
group(MyActionGroup)
{
Action(MyAction1)
{
ApplicationArea = All;
Caption = 'Post Gen. Jnl. Line - No Bind';
RunObject = codeunit "Example 3 - No Bind";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
Action(MyAction2)
{
ApplicationArea = All;
Caption = 'Post Gen. Jnl. Line - Bind';
RunObject = codeunit "Example 3 - Bind";
Promoted = true;
PromotedIsBig = true;
PromotedCategory = Process;
}
}
}
}
}
When I press the Post Gen. Jnl. Line – No Bind action, the codeunit Example 3 – No Bind is executed and the following message is retrieved:
A new record is added to Logging table but that’s it.
If I press the Post Gen. Jnl. Line – Bind action, the codeunit Example 3 – Bind is executed and the following messages are retrieved:
A new record is added to the Logging table and a message is shown with the inserted logging entry no..
I hope you enjoyed this article and found it useful.
Please feel free to comment on this.
Source code available in Github: https://github.com/ricardopaiva/NAVBC_bindsubscription_example
Source: https://docs.microsoft.com/en-us/dynamics-nav/bindsubscription-function