Header Ads Widget

📝 Blog Title: Export Data to Excel using X++ in Dynamics 365 FO | Enhanced Custom Report

 Category: X++ Development, D365FO Reporting


Exporting data to Excel is a frequent requirement in many D365FO implementations, especially for business users who want to slice and dice the data offline. This article walks you through how to export data to Excel directly from X++ without saving it on the local disk — just stream and go!


✅ Scenario

We’ll create an Excel report from a custom or standard D365FO table (like PurchParmLine) and allow the user to download it through the UI using the DocuFileSave utility.


💡 Final Output

  • Excel file generation using OfficeOpenXml

  • In-memory streaming

  • Download via browser — no physical file save


✅ X++ Code: Modern Version

x++
class MBP_ExportExcelSample { public static void main(Args _args) { PurchParmLine parmLineBuffer = _args.record(); PurchParmLine exportLine; DocuFileSaveResult fileDialogResult = DocuFileSave::promptForSaveLocation("Exporting purchase lines", "xlsx", '', "PurchaseDataExport"); if (fileDialogResult && fileDialogResult.parmAction() != DocuFileSaveAction::Cancel) { fileDialogResult.parmOpenParameters('web=1'); fileDialogResult.parmOpenInNewWindow(false); System.IO.MemoryStream excelStream = new System.IO.MemoryStream(); int rowIdx = 1; using (var excelPkg = new OfficeOpenXml.ExcelPackage(excelStream)) { var workbook = excelPkg.get_Workbook().get_Worksheets(); var sheet = workbook.Add("PO Line Details"); var cells = sheet.get_Cells(); var cell = cells.get_Item(rowIdx, 1); str headerLabels[] = ["S.No", "PO Number", "Line #", "Item ID", "Category", "Description", "Site", "Warehouse", "CW Qty", "Qty", "Unit Price", "Batch Date", "Net Amount", "Expiry", "Quality Status"]; for (int i = 0; i < headerLabels.length(); i++) { cell = cells.get_Item(rowIdx, i + 1); cell.set_Value(headerLabels[i]); } while select exportLine where exportLine.OrigPurchId == parmLineBuffer.OrigPurchId && exportLine.SMJ_SNo != "" { rowIdx++; InventDim invDim = InventDim::find(exportLine.InventDimId); int col = 1; cells.get_Item(rowIdx, col++).set_Value(exportLine.SMJ_SNo); cells.get_Item(rowIdx, col++).set_Value(exportLine.OrigPurchId); cells.get_Item(rowIdx, col++).set_Value(exportLine.PurchaseLineLineNumber); cells.get_Item(rowIdx, col++).set_Value(exportLine.ItemId); cells.get_Item(rowIdx, col++).set_Value(exportLine.ProcurementCategory); cells.get_Item(rowIdx, col++).set_Value(exportLine.name()); cells.get_Item(rowIdx, col++).set_Value(invDim.InventSiteId); cells.get_Item(rowIdx, col++).set_Value(invDim.InventLocationId); cells.get_Item(rowIdx, col++).set_Value(exportLine.PdsCWReceiveNow); cells.get_Item(rowIdx, col++).set_Value(exportLine.ReceiveNow); cells.get_Item(rowIdx, col++).set_Value(exportLine.PurchPrice); cells.get_Item(rowIdx, col++).set_Value(exportLine.PdsVendBatchDate); cells.get_Item(rowIdx, col++).set_Value(exportLine.LineAmount); cells.get_Item(rowIdx, col++).set_Value(exportLine.PdsVendExpiryDate); cells.get_Item(rowIdx, col++).set_Value(exportLine.qualityOrderStatusDisplay()); } excelPkg.Save(); } excelStream.Seek(0, System.IO.SeekOrigin::Begin); DocuFileSave::processSaveResult(excelStream, fileDialogResult); } } }

🔍 Notes:

  • This version uses renamed buffers like parmLineBuffer and exportLine instead of reusing the same ones.

  • DocuFileSave::promptForSaveLocation prompts the user for download — so no local server path issues!

  • System.IO.MemoryStream handles file creation in memory without file system dependency.


📌 Conclusion

This approach keeps your export logic clean, avoids unnecessary local file dependencies, and keeps users happy with a simple, browser-based download prompt.

Post a Comment

0 Comments