Word document from code

Sometimes you may want to generate a Word document from code in D365FO, which gives you much more control over the result than if you simply printed a report to Word.

Here is a very brief example of how you can do it.

Start with creating an X++ project. Then add a C# class library to the same solution (I called it WordLib). Right-click the C# project, use Manage NuGet Packages… and install DocumentFormat.OpenXml package.

Then add the following class:

using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
 
namespace WordLib
{
    public class WordDoc
    {
        public Stream Create()
        {
            MemoryStream ms = new MemoryStream();
 
            using (WordprocessingDocument wordDocument =
                WordprocessingDocument.Create(ms, WordprocessingDocumentType.Document, true))
            {
                MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
 
                string text = "Do androids dream of electric sheep?";
                Body body = new Body(new Paragraph(new Run(new Text(text))));
 
                mainPart.Document = new Document(body);
            }
 
            return ms;
        }
    }
}

This code will create a very simple Word document, containing only a single line of text, and returns it as a memory stream. You would likely need something more complicated, but that’s out of scope of this blog post. You can get more information from Open XML SDK documentation.

Build the C# class library and we’re done with it; now we need to call it from X++.

Go to the X++ project, right-click References, chose Add Reference… and add a project reference to the C# library.

Then add a runnable class with the following code, which merely calls the library and returns the stream as a file to user:

class WordDocGeneratorSample
{        
    public static void main(Args _args)
    {     
        using (System.IO.Stream wordStream = new WordLib.WordDoc().Create())
        {
            File::SendFileToUser(wordStream, 'file.docx');
        }
    }
}

Set the class as the startup object, run the project and your browser should offer you the Word document for download.

Report printed to e-mail

You can easily send a D365FO report in email just by configuring print destination to Email and setting a few properties.

You can do the same from code, where you can also set the body. You must create an instance of SrsReportEMailDataContract class, fill it values and pass it to SRSPrintDestinationSettings.parmEMailContract(). Here is an example:

SrsReportRunController controller = new SrsReportRunController();
 
controller.parmReportName(ssrsReportStr(SysUserRoleInfo, Report));
controller.parmShowDialog(false);
 
SRSPrintDestinationSettings settings = controller.parmReportContract().parmPrintSettings();
 
settings.printMediumType(SRSPrintMediumType::Email);
settings.fileFormat(SRSReportFileFormat::PDF);
settings.fileName('SysUserRoleInfo.xlsx');
 
// Here we configure the email
SrsReportEMailDataContract emailContract = new SrsReportEMailDataContract();
 
emailContract.parmTo("user@example.com");
emailContract.parmSubject("Security report");
emailContract.parmBody("Hi there! Here is your report.");
 
settings.parmEMailContract(emailContract);
 
controller.startOperation();

Printing reports from code in D365FO

A comment below my blog post Printing reports from code in AX2012 asked me to provide an example for D365FO. Here is it.

The code is virtually identical. Only writing a file to a shared folder doesn’t make a good sense in cloud, therefore I changed the code to return the file to user for download.

SrsReportRunController          controller = new SrsReportRunController();
SysUserLicenseCountRDPContract  rdpContract = new SysUserLicenseCountRDPContract();
SRSPrintDestinationSettings     settings;
 
// Define report and report design to use
controller.parmReportName(ssrsReportStr(SysUserLicenseCountReport, Report));
// Use execution mode appropriate to your situation
controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);
// Suppress report dialog
controller.parmShowDialog(false);
 
// Explicitly provide all required parameters
rdpContract.parmReportStateDate(systemDateGet());
controller.parmReportContract().parmRdpContract(rdpContract);
 
// Change print settings as needed
settings = controller.parmReportContract().parmPrintSettings();
settings.printMediumType(SRSPrintMediumType::File);
settings.fileFormat(SRSReportFileFormat::Excel);
settings.fileName('UserLicenseCount.xlsx');
 
// Execute the report
controller.startOperation();

Dynamics 365 Saturday South Africa

I’m safely back in Prague and I would like to thank everybody who attended Dynamics 365 Saturday in Johannesburg. There were so many of you!

And I want to give special thanks to people who did all the work to make the event happen and those who cared of me there. This was my first visit to South Africa and it was great you showed my lions, local food and everything.

I hope that my talks (about .NET in D365FO and about design for extensibility) gave people some useful information.

Dynamics 365 Saturday isn’t limited to South Africa, of course. There are quite a few already scheduled, therefore check what’s going in your area. It’s all free!

Weird behaviour after moving a form to another package

I recently had to move a form to another package, because we decided to extract some functionality to its own independent package. I moved files, built both packages and everything looked fine, so I started making changes for a new requirement.

But later I noticed something strange in the form. When I opened the form, it didn’t look right. Some new controls (added after the move) were missing and other parts behaved incorrectly (I won’t go into details here). Everything went back to normal when I refreshed the page, but the problem appeared again when I opened the form from menu. Using the refresh button inside the form (beside buttons Open in Microsoft Office and attachments) end up with an error and the application froze.

After making sure that everything is built correctly and restarting everything, I duplicated the form and tried the copy. It worked flawlessly, proving that the form itself was implemented correctly. Therefore, I suspected that the environment somehow used a cached design of the form as it was defined in the old package. Building the packages with and without Build Pre-Compiled Form didn’t make any difference, so I tried one more thing.

I searched the packages directory for the name of my form and I did find something suspicious. There was a file in WebContent subfolder of the original package:

[…]\PackagesLocalDirectory\MyOriginalPackage\WebContent\Forms\myform.json

Just deleting the file didn’t fix the problem, but restarting the server after that did. It would be likely sufficient to restart IIS; I turned the whole machine off because I went home and tried the result the next day.

I wanted to write this down because others might run into the same problem. And it looks like a bug in Microsoft tools, therefore they should look at it and fix it.