Implement a custom destination for generated documents
The application programming interface (API) of the Electronic reporting (ER) framework lets you extend the list of ER destinations that you can use to store documents that ER formats generate. This article includes an overview of the main tasks that you must complete to implement a custom ER destination.
Prerequisites
You must deploy a topology that supports continuous build. For more information, see Deploy topologies that support continuous build and test automation. You must have access to this topology for one of the following roles:
- Electronic reporting developer
- Electronic reporting functional consultant
- System administrator
You must also have access to the development environment for this topology.
Create or import an ER format configuration
In the current topology, create a new ER format to generate the documents that you plan to store by using a custom ER destination. Alternatively, you can import an existing ER format into this topology.
Important
The ER format that you create or import must contain at least one of the following format elements:
- Common\File
- Common\File attachment
- Common\Folder
- Excel\File
- PDF\Merger
- PDF\File
Extend the source code
Append your Microsoft Visual Studio project with a new class (
ERFileDestinationFolder
in this example), and write code that uses theERIFileDestination
public interface to implement your custom destination. In the following example, the class is designed to store generated documents as files in a folder of the local file system.using Microsoft.Dynamics365.LocalizationFramework; public class ERFileDestinationFolder implements ERIFileDestination { private str folderPath; private boolean overwrite; public str parmFolderPath(str _value = folderPath) { folderPath = _value; return folderPath; } public boolean parmOverwrite(boolean _value = overwrite) { overwrite = _value; return overwrite; } [Hookable(false)] public System.IO.Stream saveFile(System.IO.Stream _stream, str _filePath) { this.sendFileToFolder(_stream, System.IO.Path::GetFileName(_filePath)); return _stream; } [Hookable(false)] public System.IO.Stream newFileStream(str _filePath) { return new System.IO.MemoryStream(); } private str getFileName(str _fileName) { if (overwrite) { return _fileName; } System.DateTime now = DateTimeUtil::utcNow(); return strFmt('%1_%2%3', System.IO.Path::GetFileNameWithoutExtension(_fileName), now.ToString('yyyy-M-d_HH_mm_ss'), System.IO.Path::GetExtension(_fileName)); } private void sendFileToFolder(System.IO.Stream _stream, str _fileName) { var resultPath = System.IO.Path::Combine(this.folderPath, this.getFileName(_fileName)); using(var fileStream = System.IO.File::Create(resultPath)) { if( _stream.CanSeek) { _stream.Seek(0, System.IO.SeekOrigin::Begin); } _stream.CopyTo(fileStream); } } /// <summary> /// Finalizes files processing. /// </summary> [Hookable(false)] public void finalize() { } }
Add a new class to your Visual Studio project (
ERFormatDestinationSaveInFolderSettings
in this example) to use theERIFormatFileDestinationSettings
public interface to write code that specifies how a custom destination is created, how its parameters are packed for storage, and how its parameters are unpacked for presentation in the user interface (UI).using Microsoft.Dynamics365.LocalizationFramework; public class ERFormatDestinationSaveInFolderSettings implements ERIFormatFileDestinationSettings { internal const str SettingsKey = classStr(ERFormatDestinationSaveInFolderSettings); public const int CurrentVersion = 1; private boolean isEnabled; private boolean overwrite; private str folderPath; [Hookable(false)] public str parmFolderPath(str _value = folderPath) { folderPath = _value; return folderPath; } [Hookable(false)] public boolean parmIsEnabled(boolean _value = isEnabled) { isEnabled = _value; return isEnabled; } [Hookable(false)] public boolean parmOverwrite(boolean _value = overwrite) { overwrite = _value; return overwrite; } [Hookable(false)] public void setEnabled(boolean _value) { isEnabled = _value; } [Hookable(false)] public boolean isEnabled() { return isEnabled; } [Hookable(false)] public str getName() { return 'Save in folder'; } [Hookable(false)] public void validate() { } [Hookable(false)] public ERIFileDestination createDestination(ERDestinationExecutionContext _destinationContext, boolean _isGrouped) { ERFileDestinationFolder ret = new ERFileDestinationFolder(); ret.parmFolderPath(this.parmFolderPath()); ret.parmOverwrite(this.parmOverwrite()); return ret; } [Hookable(false)] public str getKey() { return SettingsKey; } [Hookable(false)] public container pack() { return [CurrentVersion, isEnabled, folderPath]; } [Hookable(false)] public boolean unpack(container packedClass) { int version = RunBase::getVersion(packedClass); switch (version) { case CurrentVersion: [version, isEnabled, folderPath] = packedClass; return true; default: return false; } } /// <summary> /// Creates a new instance of an objected from a packed class container. /// </summary> /// <param name = "_packedClass">A packed class container.</param> /// <returns>The new instance of an object.</returns> public static ERFormatDestinationSaveInFolderSettings create(container _packedClass) { var ret = new ERFormatDestinationSaveInFolderSettings(); ret.unpack(_packedClass); return ret; } }
Note
In the preceding code, the
ERFormatDestinationSaveInFolderSettings
class is introduced as thestatic
class. The implementation object should have astatic create(container)
method that creates an object and unpacks the container that the object requires for a correct pack-unpack process. For more information, see Pack-Unpack Design Pattern.In your Visual Studio project, add a new extension for the
ERFormatDestinationSettings
form, and write code that implements a custom UI for your custom destination. The following illustration shows what this UI looks like in the Visual Studio designer.Add another new class (
ERFormatDestinationSettingsEventHandlers
in this example) to your Visual Studio project, and write the event handler code for an extended destination form. This step requires that the publicERIFormatFileDestinationSettingsStorage
interface be implemented.public class ERFormatDestinationSettingsEventHandlers { [PostHandlerFor(formStr(ERFormatDestinationSettings), formMethodStr(ERFormatDestinationSettings, init))] public static void ERFormatDestinationSettings_Post_init(XppPrePostArgs args) { FormRun formRun = args.getThis(); ERIFormatFileDestinationSettingsStorage settingsStorage = args.getThis(); var settings = ERFormatDestinationSettingsEventHandlers::getSaveInFolderSettings(settingsStorage); FormStringControl folderPathControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolder_FolderPath)); folderPathControl.text(settings.parmFolderPath()); FormCheckBoxControl enabledControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolderEnable)); enabledControl.checked(settings.isEnabled()); FormCheckBoxControl overwriteControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolder_Overwrite)); overwriteControl.checked(settings.parmOverwrite()); } [PreHandlerFor(formStr(ERFormatDestinationSettings), formMethodStr(ERFormatDestinationSettings, closeOk))] public static void ERFormatDestinationSettings_Pre_closeOk(XppPrePostArgs args) { FormRun formRun = args.getThis(); ERIFormatFileDestinationSettingsStorage settingsStorage = args.getThis(); var settings = ERFormatDestinationSettingsEventHandlers::getSaveInFolderSettings(settingsStorage); FormStringControl folderPathControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolder_FolderPath)); settings.parmFolderPath(folderPathControl.text()); FormCheckBoxControl enabledControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolderEnable)); settings.setEnabled(enabledControl.checked()); FormCheckBoxControl overwriteControl = formRun.design().controlName(formControlStr(ERFormatDestinationSettings, SaveInFolder_Overwrite)); settings.parmOverwrite(overwriteControl.checked()); } private static ERFormatDestinationSaveInFolderSettings getSaveInFolderSettings(ERIFormatFileDestinationSettingsStorage _settingsStorage) { ERFormatDestinationSaveInFolderSettings ret = _settingsStorage.getSettingsByKey(ERFormatDestinationSaveInFolderSettings::SettingsKey); if (ret == null) { ret = new ERFormatDestinationSaveInFolderSettings(); _settingsStorage.addSettings(ret); } return ret; } }
Rebuild your Visual Studio project.
Configure ER destinations for the ER format that you created or imported
Configure the Archive destination for one of the previously mentioned components (file, folder, merger, or attachment) of the ER format that you created or imported. For more information, see ER Configure destinations.
For the same component of the selected ER format, enable and configure the custom Save in folder destination.
Note
Make sure that the specified custom destination folder (c:\0 in this example) is present in the local file system of the server that runs the AOS service. Otherwise, a DirectoryNotFoundException exception will be thrown at runtime.
Run the ER format that you created or imported
- Run the ER format that you created or imported.
- Go to Organization administration > Electronic reporting > Electronic reporting jobs, and find the record that was created for this execution job and that has the generated file attached to it.
- Browse to the local C:\0 folder to find the generated file.
Additional resources
Electronic reporting (ER) destinations
Electronic reporting framework API changes for Application update 10.0.19