Deep Dive: Handling Temporary Files and Browser Downloads in D365FO
In the transition from Dynamics AX 2012 to Dynamics 365 Finance & Operations, file handling has undergone a paradigm shift. Because the application is cloud-hosted, developers can no longer rely on local file system paths like C:\Temp.
Instead, professional X++ development requires understanding System.IO.Streams and Azure Blob Storage interaction. This article explores how to generate files efficiently, manage memory, and force specific browser behaviors.
1. The Architecture: Where does the file go?
When you generate a file in X++, it exists initially in the AOS server's memory. To get it to the user's browser, the system follows this sequence:
- Stream Generation: Data is written to a .NET
MemoryStream. - Azure Upload: The system uploads this stream to a temporary Azure Blob Storage container via the
File::SendFileToTempStoremethod. - SAS Token: A secure URL (with a Shared Access Signature) is generated.
- Navigation: The browser is directed to this URL to initiate the download.
2. The Professional Extension: Force "New Tab" Download
The standard File::SendStringAsFileToUser is useful for quick debugging, but for a polished user experience, we often want to open generated reports or logs in a new tab without interrupting the user's current session.
Below is a robust extension class. Notice the use of the using statement pattern. In .NET (and X++), this ensures that the memory stream is disposed of correctly, preventing memory leaks on the AOS.
[ExtensionOf(classStr(File))]
final class My_File_Extension
{
/// <summary>
/// Converts a string content to a stream and sends it to a new browser tab.
/// </summary>
public static void SendStringAsFileToUserNewTab(
str _content,
str _fileName,
System.Text.Encoding _encoding = System.Text.Encoding::get_UTF8(),
ClassName _strategy = classstr(FileUploadTemporaryStorageStrategy))
{
// Convert string to byte array
System.Byte[] byteArray = _encoding.GetBytes(_content);
// "using" pattern ensures the stream is closed/disposed after use
using (System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray))
{
File::SendFileToUserNewTab(stream, _fileName, _strategy);
}
}
/// <summary>
/// Uploads a stream to temp storage and navigates the browser to the URL in a new tab.
/// </summary>
public static void SendFileToUserNewTab(
System.IO.Stream _stream,
str _fileName,
ClassName _strategy = classstr(FileUploadTemporaryStorageStrategy))
{
Browser browser = new Browser();
str downloadUrl;
// Upload the stream to Azure Temp Storage and get the SAS URL.
// The 4th parameter 'true' requests a downloadable URL link.
downloadUrl = File::SendFileToTempStore(_stream, _fileName, _strategy, true);
if (downloadUrl != "")
{
// Navigate: (URL, OpenInNewTab = true, ShowLoading = false)
browser.navigate(downloadUrl, true, false);
}
else
{
warning("@ApplicationPlatform:DownloadFailed");
}
}
}
3. How to Consume This Code
You can now utilize this extension in any Runnable Class (Job) or Service Controller. This is ideal for generating XML logs, simple TXT exports, or CSV files on the fly.
class Job_TestFileDownload
{
public static void main(Args _args)
{
str logContent = "Log Entry: " + DateTimeUtil::toStr(DateTimeUtil::utcNow()) + "\nStatus: Success";
// This will trigger the browser to open a new tab and download 'LogFile.txt'
File::SendStringAsFileToUserNewTab(logContent, 'LogFile.txt');
info("Download initiated in background.");
}
}
4. Summary: Key Takeaways
- Do not use hardcoded paths:
System.IO.Path::GetTempPath()returns a server-side path, which is inaccessible to the client browser directly. - Use Streams: For any file larger than a few kilobytes, prefer Streams over Strings to manage AOS memory pressure.
- Temporary Storage Strategy: The
FileUploadTemporaryStorageStrategyautomatically handles the cleanup of these files in Azure, so you don't need to write code to delete the temp file after the user downloads it.
No comments:
Post a Comment