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:
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
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.
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:
Method | Description |
---|---|
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): Text | Used to read the content of resource files directly into a Text object |
NavApp.GetResourceAsJson(ResourceName: Text; (Optional) Encoding: TextEncoding): JsonObject | Used 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:
Limit | Value |
---|---|
Maximum size of any single resource file | 16 MB |
Maximum size of the total of all resource files | 256 MB |
Maximum number of resource files in an extension | 256 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
END
Hope this will help.
Thanks for reading.
ZHU
コメント