Bind Subscription in Dynamics NAV / Business Central

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

Share this

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.