Dynamics NAV Export and Zip XMLPort Files
A couple of years ago I was asked about Dynamics NAV file handling for zipping a file after exporting the data, at the time I looked at creating a batch file and scripting the zipping up of the exported file, however I prefer to do as much as possible from within Dynamics NAV, I have revisited this topic to see how I could do it in Dynamics NAV 2013 R2 using Dot Net assemblies, this example uses one of the Zip Files Classes that come with the Windows Dot.Net installed dll classes, the report used in this example can be downloaded at the end of this post.
A search on Google and I found the IO Compression ZipFile Create from Directory Class on looking at the details I could not find a simple ‘’Zip a Single File” call, what this class does is to archive the contents of a directory to a zipped file, the points I had to consider were:
- I might export one or more files to include in the zip archive.
- The export directory folder must only contain the file or files I want to add to the zip.
- The source and target directories must not be the same.
- I might want to use the same file name and append a Date Time string for sequential files.
I decided to use a report for my example to enable beginners and intermediate level Dynamics NAV Developers to load the example report and be able to see and play with the code, the code and functions in the report could be transferred to a codeunit used in a production environment, the examples have only been tested on a local install of Dynamics NAV 2013 R2.
Export and Zip XMLPort Files
The steps in the example report to process a number of files into a zipped file were:
- Create a report as processing only to test the file export.
- Get a folder path if the user wants to copy the exported files to an archive folder.
- Get the Zip folder path used to store the zipped exported files.
- Get the Zip file name from the user.
- Give the user an option to append a Date Time string to the files.
- Test that the folder paths exist and the file name is valid.
- Create a new unique folder using a GUID for the folder name.
- Use one or more Dynamics NAV ‘Cronus’ XML Ports to export some test data.
- Copy the exported files to the archive directory path.
- Zip up all the files in the new temporary folder to the zip path and file name.
- Delete the new temporary directory and files.
To start I created a blank report set as processing only and added the C/AL Global in the screenshot.
I added two text constants for message or error dialog.
To Get the Folder paths I added four variables to the report options form, the AppendDT check-box is to add a date time stamp to the files.
Request Page Control Code
It is important to test that the Folder paths exist, therefore I added code to the Options controls, I just used standard functions from the File Management codeunit 419.
OnValidate:
//Return the Folder Path from the Input ArchiveFolder := FileMgmt.GetDirectoryName(ArchiveFolder);
On Lookup:
//Open a dialog window to browse for the folder ArchiveFolder := FileMgmt.BrowseForFolderDialog('Archive File Folder','',TRUE);
To test that the file Name is valid, I added code to the Zip File Options controls, I just used standard functions from the File Management codeunit 419.
On Validate:
IF ZipName <> '' THEN BEGIN //Make sure no invalid characters are in the file name ZipName := FileMgmt.StripNotsupportChrInFileName(ZipName); //If the user as added a file extention then remove it i := STRPOS(ZipName,'.'); IF i > 0 THEN ZipName := COPYSTR(ZipName,1,i-1); //Make sure the file name is valid, giving the user a chance to re-enter IF NOT FileMgmt.IsValidFileName(ZipName) THEN BEGIN MESSAGE(Text001); ZipName := ''; END; END;
On Pre-Report Trigger Code
For my example and to make sure no other files could exist in the target folder, I created a new unique folder using a GUID for the folder name and in this code I also use the Dot.Net Folder Helper variable copied from the codeunit 410 Global Variables.
There are two options when exporting data, firstly to replace the same file where the file name should be the same, secondly incremental file names by adding a unique sequential value to the file name, to do this I just used a Time Stamp value.
If in the process I have exported any files then the next process is to zip up all the files in the new folder to the zip path and filename entered on the options form.
The last step is to delete the new directory and files using a call to the Directory Helper delete with the recursive flag set to true as this will delete all the files as well as the temporary directory.
//Has the User entered a Zip Folder Name IF ZipFolder = '' THEN ERROR(Text002,'Zip File Folder '); //Has the User entered a Zip File Name IF ZipName = '' THEN ERROR(Text002,'Zip File Name '); //If the user requested a Date Time Stamp IF AppendDT THEN TimeStamp := '-' + FORMAT(CURRENTDATETIME,0,'<Year><Month,2><Day,2><Hours24><Minutes,2><Seconds,2>') ELSE TimeStamp := ''; //Set the Zip File Name ZipFileName := DELCHR(ZipFolder,'>','\') + '\' + ZipName + TimeStamp +'.zip'; //Set the Temp GUID to use as a unique folder name TempGUID := CREATEGUID; //If the Achive Folder Path has been entered then use this folder to create the temporary directory IF ArchiveFolder <> '' THEN XmlFolder := DELCHR(ArchiveFolder,'>','\') + '\' + FORMAT(TempGUID) + '\' ELSE XmlFolder := DELCHR(ZipFolder,'>','\') + '\' + FORMAT(TempGUID) + '\'; //Use the Directory Helper to Create the Directory, User must have write permissions IODirectory.CreateDirectory(XmlFolder); //If the user is overwriting the zip file delete it IF FILE.EXISTS(ZipFileName)THEN FILE.ERASE(ZipFileName); //Clear the file counter variable CLEAR(ExportCount); //Call the function to export the Contacts XMLPort and update the counter IF ExportXmlPort(XMLPORT::"Export Contact",'Contact','.txt')THEN ExportCount := ExportCount + 1; //Call the function to export the Segment Contacts XMLPort, update the counter IF ExportXmlPort(XMLPORT::"Export Segment Contact",'Segment-Contact','.txt')THEN ExportCount := ExportCount + 1; //Clear the Dot.Net Zip Variable CLEAR(IOCompress); //Zip all the files in the Temporary Folder to the Zip File if the counter is set IF ExportCount <> 0 THEN IOCompress.CreateFromDirectory(XmlFolder,ZipFileName); //Delete the Temporary Directory recursive to delete files IODirectory.Delete(XmlFolder,TRUE);
As part of the process I wanted the option to copy the exported files to the archive directory path so I have the original file, I coded to erase the file if it already existed.
Function ExportXmlPort Returns a Boolean value
ExportXmlPort(DataportNo: Integer; NewFileName: Text; Extention: Text) : Boolean;
Local Variables:
- XmlFile: File;
- XmlStream: OutStream;
- IsExported: Boolean;
//Set the File name to Export XmlFileName := DELCHR(XmlFolder,'>','\') + '\' + NewFileName + TimeStamp + Extention; //If replacing the file delete the old file IF FILE.EXISTS(XmlFileName)THEN FILE.ERASE(XmlFileName); //If archiving the files the set the Archive file name IF ArchiveFolder <> '' THEN BEGIN ArchiveFileName := DELCHR(ArchiveFolder,'>','\') + '\' + NewFileName + TimeStamp + Extention; //If replacing the Archived Version then delete the old file IF FILE.EXISTS(ArchiveFileName)THEN FILE.ERASE(ArchiveFileName); END ELSE ArchiveFileName := ''; //Clear the OutStream variable CLEAR(XmlStream); //Create the file and export the XMLPort into the Files OutStream XmlFile.CREATE(XmlFileName); XmlFile.CREATEOUTSTREAM(XmlStream); IsExported := XMLPORT.EXPORT(DataportNo, XmlStream); XmlFile.CLOSE; //If the Export Ran ok then Archive the file if required IF IsExported AND (ArchiveFileName <> '')THEN FILE.COPY(XmlFileName,ArchiveFileName); //Return the IsExported Boolean EXIT(IsExported);
Running the Report
I created a folder in my local Temp Folder, in production I would use a network folder that I knew the user had read and write permission for.
While the report is running a new folder has been created using a GUID as a unique folder name, to show this example screenshot I just remarked the code that deletes the folder.
After removing the remarks in the code and deleting the previous files, now when running the report I can now see a new zip file has been created.
Two sequential files have been written to the Archive folder, these could be used for audit or backup.
I hope the code in the download will be helpful, please feel free to comment on this post and discuss other Zip options.