Business Central 2024 wave 2 (BC25.2): Package resources (files) in extensions and access from AL

Dynamics 365 Business Central

Hi, Readers.
Dynamics 365 Business Central 2024 wave 2 (BC25) is generally available. More details: General availability: Dynamics 365 Business Central 2024 release wave 2 (BC25).
And Business Central 2024 wave 2 CU03 (BC25.3) has been released last week. More details: Cumulative Update Summary for Microsoft Dynamics 365 Business Central (January, 2025)

I will continue to test and share some new features that I hope will be helpful.

Package resources in extensions and access from AL: APPLIES TO Business Central 2024 release wave 2, update 25.2 and later

Business value:
Features often require some data—for example, to initialize and set up. Until now, it has only been possible to add such data and consume from AL through the use of labels or code. In this release, we’re adding the ability to include resources in extensions and access these from AL.

https://learn.microsoft.com/en-us/dynamics365/release-plan/2024wave2/smb/dynamics365-business-central/package-resources-extensions-access-al?wt.mc_id=DX-MVP-5004336

This is also updated in the AL Changelog: AL Language extension -> Changelog -> Version 14.2

Let’s see more details.
To package resources in an extension, you need to declare which folders within your project contain resources to be packaged in the extension’s manifest file (app.json). To do this, add the "resourceFolders" property to the manifest file.

"resourceFolders": ["Resources"]

You can specify multiple folders:

If the folder name is incorrect or does not exist, the following prompt will appear.

The folder ‘Resources2’ does not exist. AL AL0863

Resource folders can contain subfolders as well. Files within these subfolders are also included as resources in the extension.

Resources can be accessed from AL code at runtime. There are several methods that can be used to interact with resources:

MethodDescription
NavApp.ListResources((Optional) Filter: Text)Used to list available resources in an extension
NavApp.GetResource(ResourceName: Text; var ResourceStream: Instream; (Optional) Encoding: TextEncoding)Used to read the content of resource files at runtime
NavApp.GetResourceAsText(ResourceName: Text; (Optional) Encoding: TextEncoding): TextUsed to read the content of resource files directly into a Text object
NavApp.GetResourceAsJson(ResourceName: Text; (Optional) Encoding: TextEncoding): JsonObjectUsed to read the content of resource files directly into a JsonObject

Let’s look at a few simple examples, this is the structure and files of my Resource folder

NavApp.ListResources((Optional) Filter: Text): This method can read all the files in the folder. If you want to read multiple files at the same time, you can use this method first.

You can add filters, similar to how you would in Windows.

/**
* For the following resource structure:
* Resources/
*     Images/
*         SampleImage1.png
*         SampleImage2.png
*         SampleImage3.jpg
*         TemplateImage.jpg
*     Snippet/
*         SampleSnippet.txt
*         TemplateSnippet.txt
*/

// This will return: ["Images/SampleImage1.png","Images/SampleImage2.png","Images/SampleImage3.jpg", "SampleSnippet.txt"]
NavApp.ListResources("Sample");

// This will return: ["Images/SampleImage1.png","Images/SampleImage2.png"]
NavApp.ListResources("Images/*.png")

NavApp.GetResource(ResourceName: Text; var ResourceStream: Instream; (Optional) Encoding: TextEncoding):

This can read the file into InStream Data type, which can be imported into BC as a file, or the contents can be read directly.

NavApp.GetResourceAsText(ResourceName: Text; (Optional) Encoding: TextEncoding): Text: The text can also be read using this method.

NavApp.GetResourceAsJson(ResourceName: Text; (Optional) Encoding: TextEncoding): JsonObject: Similar to the above method, you can save the file directly to the JsonObject data type.

For example,

Note:
Resources can only be accessed within the scope of the app that contains them. This means that there is no way to access the resources of another app.

Resources are saved in the app file.

And Resources are treated like code with regards to the Resource exposure policy setting for the application. If an application has allowDownloadingSource set to true, then any resources included with the extension will be packaged into the .app file that is downloaded.

The following limits are enforced on the resources you can include in an extension:

LimitValue
Maximum size of any single resource file16 MB
Maximum size of the total of all resource files256 MB
Maximum number of resource files in an extension256 files

Finally, let’s look at a specific test, first import items from a.txt, and then import the pictures of these items from ItemPictures folder.

a.txt: Dynamics 365 Business Central: How to import data from CSV (comma-separated values) file

ItemPictures Folder:

Test video:

Source Code: GitHub (Please note that the source code is for reference only, you can improve it according to your own needs)

pageextension 50105 ItemListExt extends "Item List"
{
    actions
    {
        addafter(CopyItem)
        {
            action(ImportItems)
            {
                Caption = 'Import Items';
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                Image = Import;
                ToolTip = 'Import Items';
                trigger OnAction()
                var
                    InS: InStream;
                    Item: Record Item;
                    LineNo: Integer;
                begin
                    CSVBuffer.Reset();
                    CSVBuffer.DeleteAll();
                    NavApp.GetResource('a.txt', InS, TextEncoding::UTF8);
                    CSVBuffer.LoadDataFromStream(InS, ',');
                    for LineNo := 2 to CSVBuffer.GetNumberOfLines() do begin
                        Item.Init();
                        Item.Validate("No.", GetValueAtCell(LineNo, 1));
                        Item.Insert(true);
                        Item.Validate(Description, GetValueAtCell(LineNo, 2));
                        case GetValueAtCell(LineNo, 3) of
                            'Inventory':
                                Item.Validate(Type, Item.Type::"Inventory");
                            'Service':
                                Item.Validate(Type, Item.Type::"Service");
                            'Non-Inventory':
                                Item.Validate(Type, Item.Type::"Non-Inventory");
                        end;
                        Item.Validate(GTIN, GetValueAtCell(LineNo, 4));
                        Evaluate(Item."Unit Price", GetValueAtCell(LineNo, 5));
                        Item.Validate("Base Unit of Measure", GetValueAtCell(LineNo, 6));
                        Item.Modify(true);
                    end;
                end;
            }
            action(ImportMultipleItemPictures)
            {
                Caption = 'Import Multiple Item Pictures';
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                Image = Import;
                ToolTip = 'Import Multiple Item Pictures';
                trigger OnAction()
                var
                    File: Text;
                    InS: InStream;
                    Item: Record Item;
                    ItemNo: Code[20];
                    RootPathList: List of [Text];
                    FileMgt: Codeunit "File Management";
                begin
                    foreach File in NavApp.ListResources('ItemPictures/*.jpg') do begin
                        NavApp.GetResource(File, InS, TextEncoding::UTF8);
                        RootPathList := File.Split('/');
                        ItemNo := FileMgt.GetFileNameWithoutExtension(RootPathList.Get(RootPathList.Count));
                        if Item.Get(ItemNo) then begin
                            Clear(Item.Picture);
                            Item.Picture.ImportStream(InS, 'Demo picture for item ' + Format(Item."No."));
                            Item.Modify(true);
                        end;
                    end;
                end;
            }
        }
    }

    var
        CSVBuffer: Record "CSV Buffer" temporary;

    local procedure GetValueAtCell(RowNo: Integer; ColNo: Integer): Text
    begin
        if CSVBuffer.Get(RowNo, ColNo) then
            exit(CSVBuffer.Value)
        else
            exit('');
    end;
}

This also works in the Install and Upgrade codeunits.
For exmaple,

Source code:

codeunit 50121 MyCodeunit
{
    Subtype = Install;

    trigger OnInstallAppPerCompany()
    var
        InS: InStream;
        Item: Record Item;
        LineNo: Integer;
    begin
        CSVBuffer.Reset();
        CSVBuffer.DeleteAll();
        NavApp.GetResource('a.txt', InS, TextEncoding::UTF8);
        CSVBuffer.LoadDataFromStream(InS, ',');
        for LineNo := 2 to CSVBuffer.GetNumberOfLines() do begin
            Item.Init();
            Item.Validate("No.", GetValueAtCell(LineNo, 1));
            Item.Insert(true);
            Item.Validate(Description, GetValueAtCell(LineNo, 2));
            case GetValueAtCell(LineNo, 3) of
                'Inventory':
                    Item.Validate(Type, Item.Type::"Inventory");
                'Service':
                    Item.Validate(Type, Item.Type::"Service");
                'Non-Inventory':
                    Item.Validate(Type, Item.Type::"Non-Inventory");
            end;
            Item.Validate(GTIN, GetValueAtCell(LineNo, 4));
            Evaluate(Item."Unit Price", GetValueAtCell(LineNo, 5));
            Item."Base Unit of Measure" := GetValueAtCell(LineNo, 6);
            Item.Modify(true);
        end;
    end;

    var
        CSVBuffer: Record "CSV Buffer" temporary;

    local procedure GetValueAtCell(RowNo: Integer; ColNo: Integer): Text
    begin
        if CSVBuffer.Get(RowNo, ColNo) then
            exit(CSVBuffer.Value)
        else
            exit('');
    end;
}

Great, give it a try!!!😁

PS:
1. If you encounter the following error in BC25.2, please upgrade the AL Language extension and try again.

A resource matching ‘CustomerPictures.zip’ could not be found in app ‘BC25 by YUN ZHU 1.0.0.2’.

2. Adding and accessing resources in Business Central extensions

3. You can find more information in Adding and accessing resources in Business Central extensions and What’s new in AL – Embedding resources in applications

What's new in AL – Embedding resources in applications

END

Hope this will help.

Thanks for reading.

ZHU

コメント

Copied title and URL