One of the major limitations we face when upgrading to Business Central’s SaaS environment is the usage of our local or server’s file system. Quite often, various NAV or BC solutions use the file system to save files for archiving purposes or to be processed later by an automated job.
There are multiple methods of refactoring various file system usages ranging from file downloads and uploads to connecting to an online drive. In this guideline, we’re going to cover this by creating our own BlobStorage and replicating a file system by allowing the user to save and load files from the newly created BlobStorage.
What is a BlobStorage?
A BlobStorage is a method of storing a file as a BLOB within a field in a table instead of holding it in memory.
The most basic form of BLOBStorage is comprised of a table containing a key field (ID or a name) and a BLOB type field. It is also possible to consider a BlobStorage record as a file in itself, with all of its metadata contained in its own field and the contents of the file stored in the BLOB field itself. So, all in all, a listing page would act as a list of files in a folder.
Creating a BlobStorage Table
The first step to creating a BlobStorage is to create a table in which the file itself can be stored along with any additional data we’d like to keep. Since each record is considered a file in itself, we can create the following fields:
- Type – Enum
- ID – Integer, AutoIncrement is set to true.
- Parent ID – Integer
- Name – Text
- BLOB – BLOB
Type field – this field will be used to define whether this is a file or a folder that can contain more folders or files, a separate enum has to be created for it containing two values: Folder, File.
ID field – this field is used as a unique identifier, which is used to find the file through code. The ID field can as well be a code field instead of an integer, but that would require you to use number series to make sure each file record is unique, so autoincrement is used now for the time being.
Parent ID field – this field is used to define a BlobStorage’s folder type record, in which the file is considered to be stored.
Name field – a filename field containing the file extension, the BlobStorage table can contain the file extension in a separate field as well.
BLOB field – the field that stores the actual contents of the file.
The end result should be similar to this:
Creating a navigation page
The point of creating navigation pages for the BlobStorages is to simulate a file browser and to replicate the two major functions that are often used in older NAV or BC versions – OpenFileDialog and SaveFileDialog. The two functions prompt the user to select a file to get its directory for later usage or to select a folder to get its directory for the purposes of saving a file. Other page customizations can be created to allow the user to manually browse through the files, rename or delete them as well.
Creating a list of files is simple in itself, all it takes is creating a listing page with the BlobStorage as the source table, but simulating an actual file browser needs to have additional functions coded into the page to constantly change the view for the user.
The initial version on this navigation page should include folder browsing capabilities, therefore a few procedures have been created to keep track of the current folder and update the page based on the current folder.
Name field’s DrillDown trigger has also been given a function to see if we’re clicking on a folder or a file, and if we click on a folder, we’re going to load another page that will display the contents of the folder.
Expanding BlobStorage functionalities
Now that we have a basic file navigation page, we can expand it with various file management functionalities:
- CreateNewFile – A function that creates an empty file within the BlobStorage. Will be used within the navigation page.
- CreateNewFolder – A function that creates a folder within the BlobStorage. Will be used within the navigation page.
- DownloadFile – A function that will allow the user to download a file from a BlobStorage to their own environment. This function can be called from within the DrillDown trigger whenever a file is clicked on.
- ImportFile – A function that will allow the user to upload a file into the BlobStorage. This function can be called from an action.
- OpenFileDialog – A function that will let the user select a file from a BlobStorage and get the ID of the file. This function can be called
- SaveFileDialog – A function that will let the user select a folder within the BlobStorage to save a file to.
To store these functions, a custom codeunit “BLOBFileManagement” has been created.
As mentioned previously, this function will be used within the navigation page to allow the user to create their own file.
The function requires an ID of a folder (0 if in the root directory) and the desired file name.
A BlobStorage is created and is assigned the required values – parent folder, name of the file, type. The record is then inserted and the ID of the record is returned.
Similarly, as before, this function will be used within the navigation page to allow the user to create more directories.
The function requires an ID of a folder (0 if in the root directory) and the desired file name.
A BlobStorage is created and is assigned the required values – parent folder, name of the file, type. The record is then inserted and the ID of the folder record is returned.
This function is one of the functions that link the client with the server using streams and it requires only 1 parameter – the ID of the file we wish to download.
The function first checks if a file with a specified ID exists, and if it does, it calculates the field to get the contents of the file within the BLOB field and is read through an InStream. A tempblob variable is initialized by creating an outstream and copying the InStream to the OutStream. Once copied, file management’s BLOBExport function is used to prompt the user to download the file from the BlobStorage.
Opposite to DownloadFile, ImportFile prompts the user to select a file from their local file system, which then writes the contents of the file into a stream. The function requires only 1 parameter – folder directory (0 if we wish to upload a file to a root directory) Once uploaded, a BlobStorage record is created and is given the same file name and format as the original file, a stream is then created for the BLOB field within the BlobStorage and is written into by copying from an InStream, which contains the uploaded file contents.
Expanding the navigation page
Now that we have created some functions for BlobStorage’s file management codeunit, we can utilize it to expand the navigation page to allow the user to edit directories and upload or download files of their own. Two actions have been created that let the user create folders and empty files of their own.
The DeleteAllowed property has been commented out to allow the user to delete any unneeded files. To make sure the deletion is properly handled, additional code has been added to the source table‘s OnDelete trigger to make sure to delete any files contained within the folder the user is deleting.
The name field has also been modified with an addition to the DrillDown trigger code. Whenever we drilldown a file, the DownloadFile function will be called so that the user may open the file within their own system if they wished.
Lastly, an action has been created that will let the user upload a file from their local file system into the BlobStorage:
With the addition of file and folder creating actions as well as deletion handling code, we are now able to create a basic file browsing system, in which we may also upload or download files:
Now that we have a basic replica of a file system in a form of a BlobStorage, we can use it to recreate some of the obsoleted functions that existed within the previous NAV solutions.
OpenFileDialog and SaveFileDialog
To create OpenFileDialog and SaveFileDialog functions, the existing navigation page has to be modified to support such functions or a copy of the page needs to be created for the sole purpose of looking up and opening a file or saving a file.
A new lookup page has been created that will be used for both OpenFileDialog and SaveFileDialog. This page not only contains a repeater that lists all of the available files but also has a field that will allow the user to choose a new file name when saving a file with SaveFileDialog.
Unlike the navigation page, this page changes filters whenever an item is selected, this is to allows the person to select a file without going through multiple pages so that when closing, the functions are able to get a selected file to save or open.
This page was given an action “Back”. Since this page works by changing filters whenever we open a folder, we have to set the filters back whenever we’d like to close the folder and go back.
The method of going back can be developed in many ways, but one suitable method would be to find the folder by its ID and then get the folder’s parent ID. Once we have the parent ID, we may set the parent as a current folder and refresh the page.
Lastly, a function GetFileToSave and GetSelectdFile have been created to get either the selected file for OpenFileDialog or get a directory for SaveFileDialog to have a file created:
Now that we have a lookup page, we can create OpenFileDialog and SaveFileDialog functions. OpenFileDialog simply prompts the lookup page for the user to select a file, once the file is selected, the page closes and the ID of the selected file is returned:
SaveFileDialog is a different case whatsoever, it all boils down to whether or not we create a file as soon as we select a directory. To try to replicate the older file management as much as possible, SaveFileDialog in this instance will return the folder ID and the desired filename. SaveFileDialog opens the same lookup page but instead uses the GetFileToSave function to get the desired filename and folder ID. If a file is detected with an identical name or an already existing file has been selected, a confirmation dialog is used to confirm if the desired file should be replaced. In general, SaveFileDialog should return a file name and a folder ID if the selection has been successful and empty values if the selection has been canceled:
Utilizing the BlobStorage
A codeunit was created to test some of the BlobStorages capabilities and to see if it can save a report within a BlobStorage. The codeunit first prompts the user to select a folder, to which the report will be saved as a pdf. Once the user selects a folder, a BlobFile record is initialized but not inserted before the report has been successfully saved into an OutStream, which has been liked to the BlobStorage record. Once the report has been written to the BlobStorage record through an OutStream, the record can then be inserted.
The end result is a newly created record:
When downloaded, the pdf file contains all of the data that was generated during the reporting process:
This guideline was written to showcase the basics of creating a BlobStorage application that may assist the developer in overcoming some file management limitations when upgrading from NAV to Business Central SaaS. The code showcased in this guideline is not the only method of achieving the desired results with the use of BlobStorage and should be used as an example to set up a foundation for a personalized and custom BlobStorage application.
The example code, showcased in this guideline, has been uploaded to Github.