Skip to main content

Handling Temporary Files and Browser Downloads in D365FO

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:

  1. Stream Generation: Data is written to a .NET MemoryStream.
  2. Azure Upload: The system uploads this stream to a temporary Azure Blob Storage container via the File::SendFileToTempStore method.
  3. SAS Token: A secure URL (with a Shared Access Signature) is generated.
  4. Navigation: The browser is directed to this URL to initiate the download.
Pro Tip: Unlike legacy AX, the file never touches the local disk of the server. It moves directly from Memory (RAM) to the Cloud Storage.

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 FileUploadTemporaryStorageStrategy automatically 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.

Comments

Popular posts from this blog

Developing SSRS Reports Using Report Data Provider in D365F&O

Developing SSRS Reports Using Report Data Provider in D365F&O In this article, we will cover the Report Data Provider (RDP) class and the Report Contract class. We will also learn how to implement these classes for reporting needs in Dynamics 365 Finance & Operations and create a report using the RDP class via a step-by-step walkthrough. Report Data Provider (RDP) Framework A Report Data Provider (RDP) class is an X++ class used to access and process data for a report. An RDP class is an appropriate data source type when the following conditions are met: You cannot query directly for the data you want to render on a report. The data to be processed and displayed is complex and requires specific business logic. Report Data Provider Class This is an X++ class used to access and process data for an SSRS report. The RDP class processes business logic based on specified parameters and/or queries and return...

Fix SSRS Parameter Panel Layout Error

Fix SSRS Parameter Panel Layout Error [Fix] SSRS Error: "The parameter panel layout for this report contains more parameters than total cells available" Tags: #SSRS #Dynamics365 #SQLServer #ReportingServices #Troubleshooting If you have been working with SSRS (SQL Server Reporting Services) lately—especially within the Dynamics 365 environment—you might have encountered a frustrating error when trying to deploy a report after adding a new parameter. It usually happens when you add a Hidden Parameter or modify the existing list, and suddenly the deployment fails with this severity code: Error: The parameter panel layout for this report contains more parameters than total cells available. Source: Microsoft.ReportingServices.Library.ReportingService2005Impl.CreateReport The Scenario You have an existing report. You need to add a new par...

Developing SSRS Reports Using a Query in D365F&O

Developing SSRS Reports Using a Query in D365F&O In this tutorial, we will learn how to develop a report based on a predefined Query in the Application Object Tree (AOT) within Visual Studio. This method is often faster than using a Report Data Provider (RDP) class when complex business logic is not required. Scenario: We will create a report that prints a simple list of customer transactions directly from the CustTrans table without writing X++ business logic. Step 1: Create the Project and Query Unlike previous versions of Dynamics, all development for D365F&O occurs inside Visual Studio. Open Visual Studio as an administrator. Create a new Finance and Operations project. In the Solution Explorer , right-click the project node. Select Add > New Item . Select Query from the list and name it CustomerTransactionQuery . Click Add . Configuring the Data Source...