An on-demand subscriber with AL

Use Case

An end-user wants the company address on all company reports, but, when printing checks, the address should be a different one.

Background

I had this use case in a few instances and meant to write a few lines about it, but kept postponing, until now. You can add custom address fields on the company information page (and table) or any other custom table/page pair. The idea is that the sales invoices (and all other system reports) should display one address(company address in the company information page), whereas some other report, like the check report, should print an alternate address. Why? Well, say Accounts Payable is in Los Angeles and the warehouse and the head office is in Denver.

How

How can we accommodate this, while fully taking advantage of the framework Microsoft has made available via Format Address codeunit 365?

I decided to call it on-demand subscribing. On one hand, when I run the check report I want to subscribe to OnBeforeCompany event and execute some custom code, whereas when the sales invoice runs – and all other system reports – I want to bypass the code in my subscriber.

This is the base Company function code in “Format Address” codeunit:

    procedure Company(var AddrArray: array[8] of Text[100]; var CompanyInfo: Record "Company Information")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCompany(AddrArray, CompanyInfo, IsHandled);
        if IsHandled then
            exit;

        with CompanyInfo do
            FormatAddr(
              AddrArray, Name, "Name 2", '', Address, "Address 2",
              City, "Post Code", County, '');
    end;

And this would be my subscriber to the OnBeforeCompany event:

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Format Address", 'OnBeforeCompany', '', false, false)]
    local procedure CompanyAlternateAddress(var AddrArray: array[8] of Text[100]; var CompanyInfo: Record "Company Information"; var IsHandled: Boolean)
    var
        FormatAddr: Codeunit "Format Address";
    begin
        FormatAddr.FormatAddr(AddrArray, CompanyInfo."Alt. Name.SVI",
                                            CompanyInfo."Alt. Name 2.SVI",
                                            '',
                                            CompanyInfo."Alternate Address.SVI",
                                            CompanyInfo."Alternate Address2.SVI",
                                            CompanyInfo."Alternate City.SVI",
                                            CompanyInfo."Alternate Zip Code.SVI",
                                            '',
                                            CompanyInfo."Alternate Country.SVI");
        IsHandled := true;
    end;

How would I execute the subscriber only when coming from the check report and not on all other reports utilizing FormatAddr.Company function?

There might be other ways, but the way I always go with is using BindSubscription and UnBindSubscription platform functions.

Then, in the check report, wrap FormatAddr.Company call, with BindSubscription and UnBindSubscription like below:

var
        Codeunit50010: Codeunit "Check Address Manual Events";
//code for check report        
        BindSubscription(Codeunit50010);
        FormatAddr.Company(CompanyAddr, CompanyInfo);
        UnbindSubscription(Codeunit50010);

Do not forget to set the property EventSubscriberInstance, of the bound codeunit, to Manual.

codeunit 50010 "Check Address Manual Events"
{
    EventSubscriberInstance = Manual;
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Format Address", 'OnBeforeCompany', '', false, false)]
    local procedure CompanyAlternateAddress(var AddrArray: array[8] of Text[100]; var CompanyInfo: Record "Company Information"; var IsHandled: Boolean)
    var
        FormatAddr: Codeunit "Format Address";
    begin
        FormatAddr.FormatAddr(AddrArray, CompanyInfo."Alt. Name.SVI",
                                            CompanyInfo."Alt. Name 2.SVI",
                                            '',
                                            CompanyInfo."Alternate Address.SVI",
                                            CompanyInfo."Alternate Address2.SVI",
                                            CompanyInfo."Alternate City.SVI",
                                            CompanyInfo."Alternate Zip Code.SVI",
                                            '',
                                            CompanyInfo."Alternate Country.SVI");
        IsHandled := true;
    end;
}

Conclusions:

  • on-demand subscribers need to live in a codeunit whose EventSubscriberInstance = Manual;
  • on-demand subscribers are brought in memory with BindSubscription
  • Codeunits having EventSubscriberInstance = Manual should contain event subscribers serving one specific use case only.

Hope this helps!

Invoking Azure Functions to replace DOT NET calls in C/AL or AL

Recently Microsoft announced that dotnet can still be used with installations on premise of Dynamics 365 Business Central.

However, if our extension is to make it in the cloud the code leveraging dot net needs to be replaced with http api calls.

In this example I will show how a legacy C/AL code using dot net can be replaced with a call to an Azure function to achieve the original goal of validating a posting code.

Premise

  • Either Table 18 was modified and additional code was added in “Post Code” Validate trigger with Regex class entities to perform validation on post codes.
  • Or, the additional validation is executed when the Post Code Validate in standard is finished and a subscriber to Post Code Validate exists in our extension and is triggered, but still contains dot net code(RegularExpressions class entitites) as we’re only dealing with on-premise (target=internal in app.json)

Objective

I want the additional validation to be executed when the standard validation is finished and the additional validation to not contain dotnet calls.

Design

  1. In a new AL project add a new codeunit:

add_al_codeunit

2. The codeunit itself contains an event subscriber to Table18.Validate.PostCode.

(Use “teventsub” snippet to get the quick scaffolding of the event subscriber)

codeunit_content

When the subscriber is triggered we are executing an Azure Function call: azfnvalidate-us-ca-zipcode. We’re retrieving a json object whose content is : {“PCValid” : true} or {“PCValid” : false}.

3. Write the Azure Function with Visual Studio Code

Pre-requisites:

  • Azure subscription
  • install C# extension
  • Azure Function Core Tools extension
  • install .net core (use npm or chocolatey)
  • Azure CLI Tools

VSCodeExtensions

A good guide to get you started with Azure Functions is here.

Once you have the default “Hello World” Azure Function created, replace your Run function with:

azFn

Publishing the function in Azure should generate a record in your chosen storage:AzureFninPortal

Testing

  1. Once published we can quickly spin a test for the new Azure Function in a web browser window:

web_browser_test

2. Removing the “W” in the previous test, triggers the Azure Function to return above json.

web_browser_invalid_postcode

3. Let’s test now the validation in Business Central:

ezgif-3-34f9ae149c11

Therefore, to replace a set of dotnet calls we need a worker placed somewhere else other than in AL or C/AL and a caller of that worker services placed in the extension. In my example use a codeunit (caller) in the extension range with a subscriber event defined that calls an Azure function(worker).

What other methods are you employing to achieve similar results ?

If you liked this article bookmark my blog or follow me for more stuff about NAV and Business Central.