Thursday, November 20, 2025

How to upload a file in SFTP server through x++ in D365 F&O ?

 

When working with Dynamics 365 Finance and Operations (D365 F&O), integrations often require us to send or receive files from external systems. One of the most common ways to do this securely is through SFTP (Secure File Transfer Protocol).

Unfortunately, D365 F&O doesn’t provide a native SFTP client out of the box. Instead, we have a few options to achieve this:

  • Use Azure Blob Storage + Logic Apps / Azure Functions for file movement (Microsoft recommended approach).

  • Use third-party libraries like Renci.SshNet with .NET interop inside X++.

In this post, we’ll focus on the direct SFTP approach using X++ and .NET interop.


Prerequisites: -

Before jumping into code, make sure you have the following in place:

  1. Access to the SFTP server (host, port, username, password, and destination path).

  2. The Renci.SshNet.Async library deployed to your D365 F&O environment (uploaded via Visual Studio project reference).

  3. Proper permissions in your D365 F&O environment to run external .NET assemblies.


Now let us see step by step how to achieve this SFTP File upload functionality : - 

Step 1 - Create a C# Project for Class Library targeting to .Net Framework 4.7.2 (Very Important as D365 F&O also targets the same framework

Step 2 - Right click on the project and select Manage Nuget Packages. Go to Browse and install Renci.SshNet.Async package library



Step 3 - Create a class in C# Project and give it any name such as this case we have named it SFTPTransfer

Step 4 - Use the below code to create the functionality to upload your desired file to SFTP server : - 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Renci.SshNet;

namespace SFTPFileTransfer
{
    public class SFTPTransfer
    {
        public void TransferSFTPFile(string _host, string _username, string _password, System.IO.Stream _sourceFile,
        string _destinationPath, int _port, string _fileName)
        {
            List <AuthenticationMethod> methods;

            methods = new List<AuthenticationMethod>
            {
                new PasswordAuthenticationMethod(_username, _password)
            };

            try
            {
                var connectionInfo = new ConnectionInfo(_host, _port, _username, methods.ToArray());

                using (SftpClient sftpclient = new SftpClient(connectionInfo))
                {
                    sftpclient.Connect();

                    sftpclient.ChangeDirectory(_destinationPath.Trim());

                    _sourceFile.Position = 0;

                    sftpclient.BufferSize = 8 * 1024;

                    sftpclient.UploadFile(_sourceFile, _fileName);
                }
            }
            catch (WebException ex)
            {
            }
        }
    }
}


Step 5 - Build the class library project which should generate the DLL file. In our case DLL file name is SFTPFileTransfer.dll

Step 6 - Copy and paste the dll into the path - \AosService\PackagesLocalDirectory\yourmodelname\bin
 
Step 7 - Create a new Finance Operations project. In our case we have named it FinalSFTPExportTest and created two classes - SFTPUploadTest and VendAgingReportController_Extension (as we are testing SFTP file transfer for Vendor Aging Report in excel format) 

Step 8 - Use the below mentioned code in SFTPUploadTest class : - 

 
using SFTPFileTransfer;

public class SFTPUploadTest
{
    public void executeuploadtoSFTP(SrsReportRunController _controller)
    {
        str sftpServer   = "abc.sftp.com";
        int sftpPort     = 22;
        str sftpUser     = "xyz";
        str sftpPassword = "abcd12344";
        str sftpPath     = "/AB/Report/"; // remote directory

        str fileName = 'VendorAgingReport.xlsx';
        // Be careful with the fileName as few SFTP servers reject the file due to invalid characters in the name

        // --- Generate Report as Excel file ---
    
        SRSPrintDestinationSettings printSettings;
        SRSReportRunService         srsReportRunService;
        Map                         reportParametersMap;
        Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[] parameterValueArray;
        SRSProxy                    srsProxy;
        System.Byte[]               reportBytes;

        // Setup report
        _controller.parmReportName(ssrsReportStr(VendAgingReport, DesignWithNoDetailAndNoTransactionCur)); // Vendor Aging Report
        _controller.parmShowDialog(false);
        _controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);

        // Print settings
        printSettings = _controller.parmReportContract().parmPrintSettings();
        printSettings.printMediumType(SRSPrintMediumType::File);
        printSettings.fileFormat(SRSReportFileFormat::Excel);

        // Run report service
        srsReportRunService = new SRSReportRunService();
        srsReportRunService.getReportDataContract(_controller.parmReportContract().parmReportName());
        srsReportRunService.preRunReport(_controller.parmReportContract());

        reportParametersMap = srsReportRunService.createParamMapFromContract(_controller.parmReportContract());
        parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);

        _controller.parmReportContract().parmReportExecutionInfo(new SRSReportExecutionInfo());
        _controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());

        srsProxy = SRSProxy::constructWithConfiguration(_controller.parmReportContract().parmReportServerConfig());

        // Render report to byte array (Excel)
        reportBytes = srsProxy.renderReportToByteArray(_controller.parmReportContract().parmReportPath(),
                                                    parameterValueArray,
                                                    printSettings.fileFormat(),
                                                    printSettings.deviceinfo());

        if (!reportBytes || reportBytes.get_Length() == 0)
        {
            throw error("Vendor aging report rendering failed.");
        }
        System.IO.Stream objstream = new System.IO.MemoryStream(reportBytes);

        // --- Upload to SFTP ---
        SFTPTransfer transfer = new SFTPTransfer();

        transfer.TransferSFTPFile(sftpServer,sftpUser,sftpPassword,objstream,sftpPath,sftpPort,fileName);

    }

}

Step 9 - Use the below mentioned code to call  executeuploadtoSFTP method inside VendAgingReportController_Extension class so that every time the report runs the report output will be exported in excel format and will be sent to SFTP server : - 

[ExtensionOf(classstr(VendAgingReportController))]
final class VendAgingReportController_Extension
{
    protected void preRunModifyContract()
    {
        next preRunModifyContract();

        SFTPUploadTest objsftpupload = new SFTPUploadTest();

        objsftpupload.executeuploadtoSFTP(this);

    }

}


That's all for now. Please let us know your questions or feedback in comments section !!!!

Saturday, July 26, 2025

Demystifying the SysOperation Framework in D365 F&O: Building Scalable and Maintainable Batch Jobs

 If you've been developing in Dynamics 365 Finance and Operations for a while, chances are you've either worked with or heard about the SysOperation framework. It’s one of those powerful—but often underutilized—features that can drastically improve the way you handle batch processing, background operations, and complex business logic.

In today’s post, I want to walk through how to properly build a batch job using the SysOperation framework, including UI parameter dialogs and execution logic. This is not just another “hello world” example — this is about designing scalable, maintainable, and professional-grade batch processes.


Why SysOperation, Not RunBase?

For those coming from older AX versions, RunBaseBatch might feel more familiar. But in D365 F&O, SysOperation is the standard — and for good reason:


Benefit      Why It Matters
Separation of concerns      Cleanly splits data, UI, and business logic
Better testing      Each part is independently testable
Supports service-oriented architecture      Aligns with modern enterprise design
Declarative metadata      Leverages attributes for easier setup



Real-World Use Case

Let’s say we want to build a batch job that automatically deactivates vendors who haven’t had any transactions in the past 18 months.

This isn’t just a one-time script — it should run on a schedule, allow user input, and log its execution.



Step-by-Step Implementation

1) Define the Data Contract

The contract defines parameters that the user can set in the UI:

[DataContractAttribute]
class DeactivateVendorsContract
{
    TransDate cutoffDate;

    [DataMemberAttribute("Cutoff Date"), SysOperationLabel(literalStr("Deactivate vendors who haven’t transacted since"))]
    public TransDate parmCutoffDate(TransDate _cutoffDate = cutoffDate)
    {
        cutoffDate = _cutoffDate;
        return cutoffDate;
    }
  }

2) Write the Business Logic (Service Class)

Here’s where the actual deactivation logic lives:

class DeactivateVendorsService
{
    public void processVendors(DeactivateVendorsContract contract)
    {
        VendTable vendTable;

        while select forUpdate vendTable
            where vendTable.Active == NoYes::Yes
        {
            if (!VendorHasRecentTransactions::hasActivitySince(vendTable.AccountNum, contract.parmCutoffDate()))
            {
                vendTable.Active = NoYes::No;
                vendTable.update();
            }
        }
    }
}

3) Create the Controller Class

The controller connects the dots and supports scheduling:

class DeactivateVendorsController extends SysOperationServiceController
{
    public static void main(Args _args)
    {
        DeactivateVendorsController controller = new DeactivateVendorsController();
        controller.parmClassName(classStr(DeactivateVendorsService));
        controller.parmMethodName(methodStr(DeactivateVendorsService, processVendors));
        controller.parmDialogCaption("Vendor Deactivation Job");
        controller.startOperation();
    }
}


Scheduling the Batch Job

Once everything is in place:

  1. Navigate to System administration > Inquiries > Batch jobs.

  2. Schedule the job via the controller’s main() method.

  3. Set recurrence, alerts, and logging preferences.

Now you have a robust, enterprise-ready job that runs cleanly in the background — with full visibility and traceability.


Good practices to follow :

  • Always add meaningful labels in the DataContract so users aren’t guessing what each field means.

  • Separate query logic (e.g., checking for transactions) into a reusable class like VendorHasRecentTransactions.

  • Use logging via SysOperationProgress or custom logging tables to capture success/failure counts.

  • Consider adding batch group and task dependencies for large-scale production use.


Conclusion

The SysOperation framework isn’t just another way to run a batch job — it’s a foundation for writing clean, scalable operations that fit neatly into D365’s architecture. Once you get comfortable with it, you’ll never look back at RunBase.

Whether you’re processing millions of rows or just sending a daily summary email, using SysOperation the right way will help you deliver professional-grade solutions that your team — and your future self — will thank you for.


That's all for now. Please let us know your questions or feedback in comments section !!!!

Tuesday, July 22, 2025

Mastering Data Entities in D365 F&O: Creating a Custom Data Entity from Scratch

 One of the most powerful features in Dynamics 365 Finance and Operations is the Data Management Framework (DMF). Whether you're building integrations, facilitating data migrations, or enabling Excel-based edits — data entities are the cornerstone.

While standard entities cover a wide range of scenarios, there are times when you need a custom entity tailored to your exact business requirement.

Today’s post will walk you through the entire process of building a custom data entity from scratch, using best practices every technical consultant or architect should follow.


When and Why to Build a Custom Data Entity?

Before jumping in, it’s important to ask:

  • Does a standard entity already cover the requirement?

  • Is the customization aligned with business processes?

  • Will the entity be used for inbound, outbound, or dual-directional integrations?

If the answer points to a clear gap, it's time to create your own.


Use Case

Let’s say your client wants to integrate a custom loyalty program with D365 F&O. They’re storing loyalty card data in an external system and want to push it into a custom table named LoyaltyMemberTable.


Step-by-Step: Building a Custom Data Entity : -


1.   Create the Table (If It Doesn't Exist) with the below fields : - 
           
       string CardNumber ,  CustAccount CustomerAccount , TransDate JoinDate , string                                   MembershipLevel
       

2.  Create a New Data Entity

  • In Visual Studio, right-click on your project → Add → New Item → Data Entity.

  • Name it LoyaltyMemberEntity.
  • Set the Primary datasource to LoyaltyMemberTable
  • In the properties:

    • Public = Yes (if it should be available for integrations)

    • DataEntityView = Auto

    • IsPublic = Yes

    • IsReadOnly = No

    

3.   Add Fields to the Entity

Select only the necessary fields — avoid exposing internal or irrelevant fields to keep the integration secure and clean.

You can also rename labels or make fields mandatory here.

   

4.   Build and Synchronize

       Once the entity is created:

  • Build your solution.

  • Sync the database to register the entity in the DMF.

        You’ll now find your new entity under Data Management Workspace > Data Entities.

  

5.    Test It

         Use the Data Management Workspace:

  • Try exporting data using your new entity.

  • Then test an import with a sample Excel file.


Best Practices to Remember :-

  • Use surrogate keys wherever possible to simplify mapping and support system-generated RecIds.

  • Limit the number of exposed fields — keep entities lean and focused.

  • Add business logic in postLoad/postWrite methods only if necessary.

  • For larger datasets, enable incremental export to improve performance.

      

Pro Tip: Extend an Existing Entity When Possible

Before creating a new one, check if an extension of a standard entity can serve your purpose. It keeps things clean and ensures you’re building only what’s needed.


Conclusion

Custom data entities are more than just a way to move data — they’re critical building blocks in a well-architected D365 ecosystem. Whether you're integrating with external systems, enabling Excel-based edits, or facilitating controlled data migrations, knowing how to build a reliable entity is essential.

As a D365 F&O technical consultant or architect, being comfortable with data entities isn’t just a technical skill — it’s a must-have competency for delivering real-world business value.


That's all for now. Please let us know your questions or feedback in comments section !!!!

Friday, July 18, 2025

A Practical Guide to Using Event Handlers in D365 F&O (The Smart Way to Customize)

Customizing standard processes in Dynamics 365 Finance and Operations has evolved significantly. Gone are the days when overlayering was the norm. Today, we work with event handlers - a cleaner, upgrade-safe way to extend standard logic without touching Microsoft’s base code.

In this post, I’ll break down how event handlers work, when to use them, and walk you through a real-world example that illustrates how powerful (and easy) they are to implement.



Why Event Handlers Deserve Your Attention ?

As developers and technical consultants, we’re often tasked with extending standard business logic. Event handlers let us do that without modifying the original codebase. This ensures:

  • No conflicts during platform upgrades
  • Better code separation for long-term maintenance
  • Compliance with Microsoft's One Version policy


Mostly used types of event handlers

Depending on your use case, there are different handler types available:

Event Type         Purpose
Pre-event         Executes before the standard method – ideal for validations
Post-event         Executes after the method – great for additional processing
OnModified, OnValidated         Used for form field-level triggers
DataEventHandlers         Fire during insert/update/delete events on tables

Use Case: Auto-Generating Vendor Codes

Let’s say your client needs vendor records to be assigned a unique code automatically when a new vendor is created. The standard VendTable insert method doesn’t do this out-of-the-box.

Instead of overlayering the base method (which is a no-go in today’s world), we’ll attach a post-event handler.

Let's look at the below example of how to create an event handler :-



class VendTableEventHandler
{
    [PostHandlerFor(tableStr(VendTable), tableMethodStr(VendTable, insert))]
    public static void VendTable_PostInsert(XppPrePostArgs args)
    {
        VendTable vendTable = args.getThis() as VendTable;

        if (!vendTable)
            return;

        vendTable.selectForUpdate(true);
        vendTable.VendCode = VendTableEventHandler::generateVendorCode(vendTable.RecId);
        vendTable.doUpdate();
    }

    private static str generateVendorCode(RecId recId)
    {
        return strFmt("VEND-%1", recId);
    }
}

Now, every time a vendor is created, this handler fires after the insert and assigns a custom vendor code like VEND-123456.

Tips to Keep in Mind

  • Keep event logic focused - Don’t clutter handlers with business rules. Move complex logic into helper or service classes.
  • Naming matters - Use consistent names like VendTableEventHandler so future developers can easily trace logic.
  • Avoid assumptions about execution order - If multiple handlers exist, the order isn’t guaranteed — so design your logic to be independent.


When to Use Event Handlers vs. Chain of Command

Both are powerful, but they serve different purposes:

Use case      What to use ?
Working with public table/form methods      Event Handler
Modifying protected business logic (services, controllers)      Chain of Command
Handling table-level inserts/updates      DataEventHandler
UI interaction (field validations)      Form event methods

Conclusion

Understanding how and when to use event handlers is essential for any D365 F&O developer or architect. Not only does it keep your codebase cleaner and upgrade-ready, but it also aligns with how Microsoft expects us to extend their platform moving forward. If you’re still relying on overlayering, it’s time to re-think your approach. With just a few lines of code, event handlers give you the flexibility you need — without the headaches that come with platform updates.


That's all for now. Please let us know your questions or feedback in comments section !!!!

Monday, May 22, 2023

How to reverse Free Text Invoice Voucher entries without Dialog in D365 F&O through x++ ?

 Hey Folks , 


This blog post is in continuation of the previous post for Reversing Free Text Invoice Voucher entries with Dialog. Only difference is this time we are doing it without dialog or you can say by suppressing

For doing it we have extended TransactionReversal_Cust class. 

We have a staging table FTIVouchers which contains the Voucher Number to be reversed.

We can use the below code to achieve this : - 



class TransactionReversal extends TransactionReversal_Cust
{
    public static TransactionReversal construct()
    {
        return new TransactionReversal();
    }

    public boolean showDialog()
    {
        return false;
    }

    public static void main(Args _args)
    {
        CustTrans 				custTrans;
        FTIVouchers                             vouchers;
        TransactionReversal 	                transactionReversal;
        ReasonTable 			        reasonTable;
        ReasonCode 				reasonCode;
        ReasonRefRecID 			        reasonRefRecID;
        InvoiceId 				invoiceId;
        Args 					args;
        

        while select vouchers where vouchers.Voucher!=''
        {
            args = new Args();
            custTrans = CustTrans::findByVoucher(vouchers.Voucher);
            args.record(custTrans);  
            reasonCode = 'XYZ';
            reasonTable = ReasonTable::find(reasonCode);
            reasonRefRecID = ReasonTableRef::createReasonTableRef(
            reasonTable.Reason, vouchers.Voucher+'REV');
            transactionReversal = transactionReversal::construct();
            transactionReversal.parmReversalDate(custTrans.TransDate);
            transactionReversal.parmReasonRefRecId(reasonRefRecID);
            transactionReversal.reversal(args);
            info(strFmt("%1 %2 %3 %4 reversed.",
                custTrans.Voucher,
                custTrans.TransDate,
                custTrans.Invoice,
                custTrans.Txt));
        }
        
    }

}

}


That's all for now. Please let us know your questions or feedback in comments section !!!!

Thursday, May 11, 2023

How to reverse Free Text Invoice voucher entry through x++ with Dialog

 Hey Folks , 


In order to do quick reversal of wrongly created Customer Invoices it will take lot of time and effort to do this manually. Here x++ pitches in to help us achieve this quickly.

For doing it through a dialog for a single transaction we have TransactionReversal_Cust class. 

We have a staging table FTIVouchers which contains the Voucher Number to be reversed.

We can use the below code to achieve this : - 



internal final class RunnableClass1
{
    /// 
    /// Class entry point. The system will call this method when a designated menu 
    /// is selected or when execution starts and this class is set as the startup class.
    /// 
    /// The specified arguments.
    public static void main(Args _args)
    {
        FTIVouchers                vouchers;
        TransactionReversal_Cust   transactionReversal = TransactionReversal_Cust::construct();

        Args        args = new Args();

        while select vouchers  where vouchers.Voucher!=''
        {
            CustTrans   custTrans = CustTrans::findByVoucher(vouchers.Voucher);

            args.record(custTrans);

            transactionReversal.parmReversalDate(systemdateget());

            transactionReversal.reversal(args);
        }
    }

}


That's all for now. Please let us know your questions or feedback in comments section !!!!

Monday, January 9, 2023

How to get data from InventDim for InventSum table

 

When we get a requirement to fetch the data for InventDim from tables like InventTable , SalesTable , SalesLine , PurchTable , PurchLine its pretty straight forward. All we need to do is check the relations which is based on the field InventDimId

For e.g InventTable.InventDimId = InventDim.InventDimId

But when we have a requirement to get the data from InventSum table which only has transactional records from InventTrans and moreover the data replicates and has no unique index. Its gets difficult most of the time to get the data. 

To save the day InventSum has the method joinChild() 

Given below is a sample code to illustrate the use of joinChild() method : - 



    [SysClientCacheDataMethodAttribute]
    public display Str1000 getProfVal(InventSum _inventSum)
    {
        InventDim               inventDimJoin;

        inventDimJoin.data(_inventSum.joinChild());

        WHSLocationProfile whsLocationProfile = WHSLocationProfile::findByWarehouseAndLocation(inventDimJoin.InventLocationId, inventDimJoin.wMSLocationId);

        return whsLocationProfile.LocProfileId;

    }


That's all for now. Please let us know your questions or feedback in comments section !!!!

How to upload a file in SFTP server through x++ in D365 F&O ?

  When working with Dynamics 365 Finance and Operations (D365 F&O), integrations often require us to send or receive files from external...