Friday, 8 November 2019

Windows service using C# - append header to csv file


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Configuration;
using System.Timers;

namespace FileHeaderService
{
    public partial class FileHeaderService: ServiceBase
    {
        Timer timer = new Timer();

        public FileHeaderService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            string delayTimer = ConfigurationManager.AppSettings["timerDelay"];
            this.CreateLog("ServiceStart");
            this.AddHeader();

            timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
            timer.Interval = Convert.ToDouble(delayTimer); //number in milisecinds
            timer.Enabled = true;
        }

        protected override void OnStop()
        {
            this.CreateLog("ServiceStopped");
        }

        private void OnElapsedTime(object source, ElapsedEventArgs e)
        {
            AddMetrixHeader();
        }

        protected void AddHeader()
        {
            string sourceFile = ConfigurationManager.AppSettings["sourceFilePath"];
            string targetFile = ConfigurationManager.AppSettings["targetFilePath"];
            string headerFile = ConfigurationManager.AppSettings["headerFilePath"];
            string archiveFile = ConfigurationManager.AppSettings["archiveFilePath"];

            string csvContent = string.Empty;
            string reason = string.Empty;
            string reason2 = "OneFile";
            string[] sourceFileInfo;
            int cnt = Directory.GetFiles(sourceFile).Length;

            if (cnt == 0)
            {
                reason = "NoFile";
                this.CreateLog(reason);
            }

            else
            {             
                sourceFileInfo = Directory.GetFiles(sourceFile, "*.csv", SearchOption.TopDirectoryOnly);
                Boolean firstFile = true;
                foreach (string txtName in Directory.GetFiles(sourceFile, "*.csv"))
                {
                    if (firstFile == true) //process 1 file at a time
                    {
                        string sourceFileName = Path.GetFileName(txtName);
                        sourceFile = sourceFile + sourceFileName;
                        targetFile = targetFile + Path.GetFileNameWithoutExtension(txtName) + System.DateTime.UtcNow.ToString("-MMddyy-hhmmss") + ".csv";

                        StreamReader rd = new StreamReader(headerFile, true);
                        csvContent = rd.ReadToEnd();
                        rd.Close();

                        rd = new StreamReader(sourceFile, true);
                        csvContent = csvContent.ToString() + "\n" + rd.ReadToEnd();
                        rd.Close();

                        StreamWriter wr = new StreamWriter(targetFile, false);
                        StringBuilder sb = new StringBuilder(csvContent);
                        wr.Write(sb.ToString());
                        sb.Clear();
                        wr.Close();

                        //first, delete target archive file if exists, as File.Move() does not support overwrite
                        archiveFile = archiveFile + sourceFileName;
                        if (File.Exists(archiveFile))
                        {
                            File.Delete(archiveFile);
                        }
                        File.Move(sourceFile, archiveFile);

                        firstFile = false;
                        reason = "Success";
                        this.CreateLog(reason, sourceFileName, targetFile);
                    }
                    else
                    {
                        this.CreateLog(reason2, Path.GetFileName(txtName), "");
                        reason = "MultiFile";
                        reason2 = reason;
                    }                 
                }
            }
        }

        public void CreateLog(string _reason, string _sourceFileName = "", string _targetFile = "")
        {
            string          logFile = ConfigurationManager.AppSettings["logFilePath"];
            StreamWriter    log;
            FileStream      fileStream = null;
            DirectoryInfo   logDirInfo = null;
            FileInfo        logFileInfo;

            logFile     = logFile + "Log-" + System.DateTime.Today.ToString("MM-dd-yyyy") + "." + "txt";
            logFileInfo = new FileInfo(logFile);
            logDirInfo  = new DirectoryInfo(logFileInfo.DirectoryName);

            if (!logDirInfo.Exists) logDirInfo.Create();

            if (!logFileInfo.Exists)
            {
                fileStream = logFileInfo.Create();
            }
            else
            {
                fileStream = new FileStream(logFile, FileMode.Append);
            }
            log = new StreamWriter(fileStream);

            if (_reason == "NoFile")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "no file exist");
            }
            else if (_reason == "Success")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "Header appended and file moved to Archive. " + "incoming file - " + _sourceFileName + " target file - " + Path.GetFileName(_targetFile));
            }
            else if (_reason == "ServiceStart")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "Header File Service Stared");
            }
            else if (_reason == "ServiceStopped")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "Header File Service Stopped");
            }
            else if (_reason == "OneFile")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "Remaining Files in the folder to be processed" + "- " + _sourceFileName);
            }
            else if (_reason == "MultiFile")
            {
                log.WriteLine(_sourceFileName);
            }
            log.Close();
        }     
    }
}

Create windows service from C# executable


How to register the service?

C:\Windows\Microsoft.NET\Framework\v4.0.30319>installUtil.exe "C:\xyz\FileHeaderService.exe"

How to uninstall the service?

C:\Windows\Microsoft.NET\Framework\v4.0.30319>installUtil.exe -u "C:\xyz\FileHeaderService.exe"

Thursday, 24 October 2019

append header to csv file using c#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Configuration;


namespace Header
{
    class AddHeader
    {
        static void Main(string[] args)
        {            
            string      sourceFile      = ConfigurationManager.AppSettings["sourceFilePath"];
            string      targetFile      = ConfigurationManager.AppSettings["targetFilePath"];
            string      headerFile      = ConfigurationManager.AppSettings["headerFilePath"];
            string      archiveFile     = ConfigurationManager.AppSettings["archiveFilePath"];

            string      csvContent      = string.Empty;
            string      reason          = string.Empty;
            string[]    sourceFileInfo;
            Int64       cnt             = Directory.GetFiles(sourceFile).Length;
            
            AddHeader mtx = new AddHeader();

            if (cnt > 1)
            {
                reason = "MultipleFile";
                mtx.CreateLog(reason);
            }

            else if (cnt == 0)
            {
                reason = "NoFile";
                mtx.CreateLog(reason);
            }

            else
            {
                sourceFileInfo = Directory.GetFiles(sourceFile, "*.csv", SearchOption.TopDirectoryOnly);

                foreach (string txtName in Directory.GetFiles(sourceFile, "*.csv"))
                {
                    string sourceFileName = Path.GetFileName(txtName);
                    sourceFile = sourceFile + sourceFileName;
                    targetFile = targetFile + Path.GetFileNameWithoutExtension(txtName) + System.DateTime.UtcNow.ToString("-MMddyy-hhss") + ".csv";


                    StreamReader rd = new StreamReader(headerFile, true);
                    csvContent = rd.ReadToEnd();
                    rd.Close();

                    rd = new StreamReader(sourceFile, true);
                    csvContent = csvContent.ToString() + "\n" + rd.ReadToEnd();
                    rd.Close();

                    StreamWriter wr = new StreamWriter(targetFile, false);
                    StringBuilder sb = new StringBuilder(csvContent);
                    wr.Write(sb.ToString());
                    sb.Clear();
                    wr.Close();

                    //first, delete target archive file if exists, as File.Move() does not support overwrite
                    archiveFile = archiveFile + sourceFileName;
                    if (File.Exists(archiveFile))
                    {
                        File.Delete(archiveFile);
                    }
                    File.Move(sourceFile, archiveFile);

                    reason = "Success";
                    mtx.CreateLog(reason, sourceFileName, targetFile);
                }
            }                
        }

        public void CreateLog(string _reason, string _sourceFileName = "", string _targetFile = "")
        {
            string          logFile = ConfigurationManager.AppSettings["logFilePath"];
            StreamWriter    log;
            FileStream      fileStream = null;
            DirectoryInfo   logDirInfo = null;
            FileInfo        logFileInfo;

            logFile     = logFile + "Log-Metrix-" + System.DateTime.Today.ToString("MM-dd-yyyy") + "." + "txt";
            logFileInfo = new FileInfo(logFile);
            logDirInfo  = new DirectoryInfo(logFileInfo.DirectoryName);

            if (!logDirInfo.Exists) logDirInfo.Create();

            if (!logFileInfo.Exists)
            {
                fileStream = logFileInfo.Create();
            }
            else
            {
                fileStream = new FileStream(logFile, FileMode.Append);
            }
            log = new StreamWriter(fileStream);

            if (_reason == "NoFile")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "no file exist in the incoming folder");
            }
            else if (_reason == "MultipleFile")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "more than 1 file present in incoming folder, place only 1 file at a time");
            }
            else if (_reason == "Success")
            {
                log.WriteLine(System.DateTime.UtcNow.ToString() + "- " + "Header appended and file moved to Archive. " + "incoming file - " + _sourceFileName + " target file - " + Path.GetFileName(_targetFile));
            }
            log.Close();
        }
    }
}

Thursday, 4 July 2019

Data entity methods execution sequence


The content of this post has been taken from different knowledge sources and reference to the original posts has been mentioned at the bottom.

Here is a sequence of method’s calls during export:

1. initValue
2. validateField
3. validateWrite
4. update
4.1. doUpdate
4.1.1. persistEntity
4.1.1.1. doPersistEntity
4.1.1.1.1. initializeDataSources
4.1.1.1.1.1. initializeEntityDataSource (Note: initializeDataSource is called once for each DataSource in Entity.)
4.1.1.1.2. mapEntityToDataSources (Note: initializeDataSource is called once for each DataSource in Entity.)
4.1.1.1.3. saveDataSources
4.1.1.1.3.1. updateEntityDataSource
4.1.1.1.4. mapEntityToDataSource (maybe for another record)
4.1.1.1.5. saveDataSources
4.1.1.1.5.1. updateEntityDataSource for update operation and (insertEntityDataSource for insert)
4.1.1.1.5.1.1. mapDataSourceToEntity
4.1.1.1.5.1.2. doSaveDataSource
4.1.1.1.5.1.2.1. updateDataSource
4.1.1.1.5.1.2.1.1. preupInsertDataSource
4.1.1.1.5.1.2.1.1.1. validateWrite of table

postLoad - This method is called during the export for setting the value to unmapped fields after entity was downloaded to datasource.


Some additional Notes - 




The postLoad method is called also by import! Since postLoad is the recommended place to set values for the virtual fields, this potentially slow-down the import process unnecessarily.
Be careful by using the postTargetProcess method! This method is called at the end of EACH thread/task-bundle if you are using the “Import threshold record count” option.
Previously, you could add postTargetProcess only to a newly created entity, but now you can extend any entity using CoC
Extension is simple.

[ExtensionOf(tableStr(DataEntity))]
final public class DataEntity_Extension
{

    public static void postTargetProcess(DMFDefinitionGroupExecution _dmfDefinitionGroupExecution)
    {
        // Do no call next
        //logic here
    }
}


Please note that this can be done only MDM scenarios but not via ODATA because ODATA updates, inserts records row by row and there is no post event\method to use. You might wish to use OData action to perform post actions on records.


Credits - 
https://community.dynamics.com/365/financeandoperations/f/dynamics-365-for-finance-and-operations-forum/301996/data-entity-methods-execution-sequence

https://axatoz.wordpress.com/2019/03/10/d365-data-entity-sequence/


Saturday, 19 January 2019

Extensions through Event Handlers



How to get Form Datasource
If you copy form event handler here is how you can get form datasource from XFormRun
[FormEventHandler(formStr(HcmPosition), FormEventType::Initialized)]
        public static void HcmPosition_OnInitialized(xFormRun sender, FormEventArgs e)
        {

            FormDataSource hcmosition_ds = sender.dataSource(formDataSourceStr(HcmPosition, HcmPosition));
 Or 
FormDataSource                              hcmosition_ds = sender.dataSource('HcmPosition');
        }

If you copy form datasource event you can get form run object from datasource  as below. Once you get form run you can call any form method available on the form.
[FormDataSourceEventHandler(formDataSourceStr(HcmPosition, HcmPosition), FormDataSourceEventType::Created)]
        public static void HcmPosition_OnCreated(FormDataSource sender, FormDataSourceEventArgs e)
        {

            FormRun formRun = sender.formRun() as FormRun;
            
        }

Similarly you can get formrun on a from control event handler using FormControl
[FormControlEventHandler(formControlStr(HcmPosition, HcmPosition_PositionId1), FormControlEventType::Modified)]
        public static void HcmPosition_PositionId1_OnModified(FormControl sender, FormControlEventArgs e)
        {
            FormRun formRun = sender.formRun() as FormRun;

        }
To get control on a form  and make it editable or non-editable
[FormEventHandler(formStr(HcmPosition), FormEventType::Initialized)]
        public static void HcmPosition_OnInitialized(xFormRun sender, FormEventArgs e)
        {
            sender.design().controlName(formControlStr(HcmPosition, HcmPositionNewPosition)).AllowEdit(false);
            // to get form open Mode
            OpenMode                                    openMode = sender.args().openMode();
        }

Getting  current record
Hcmposition is first datasource  on HcmPosition form that’s why gave 1 in datasource.
[FormControlEventHandler(formControlStr(HcmPosition, HcmPositionNewPosition), FormControlEventType::Clicked)]
        public static void HcmPositionNewPosition_OnClicked(FormControl sender, FormControlEventArgs e)
        {

            HcmPosition hcmposition = sender.formRun().dataSource(1).cursor();
        }

Using DataEventArgs to send Validation result
[DataEventHandler(tableStr(CategoryTable), DataEventType::ValidatingDelete)]
    public static void CategoryTable_onValidatingDelete(Common _sender, DataEventArgs _e)
    {
        CategoryTable categoryTable = _sender as CategoryTable;
        ValidateEventArgs validateEventArgs = _e as ValidateEventArgs;
        boolean ret = true;

        if (categoryTable.UseInProject)
        {
            ProjCategory projCategory = ProjCategory::find(categoryTable.CategoryId);
            ret = projCategory.validateDelete();
        }

        if (ret && categoryTable.UseInExpense)
        {
            TrvCostType trvCostType = TrvCostType::find(categoryTable.CategoryId);
            ret = trvCostType.validateDelete();
        }
        
        if (!ret)
        {
            validateEventArgs.parmValidateResult(false);
        }
    }

Similarly you can use on ValidateFieldValueEventArgs  to send validation result back to Validate Field method
[DataEventHandler(tableStr(LedgerParameters), DataEventType::ValidatingFieldValue)]
    public static void LedgerParameters_onValidatingFieldValue(Common sender, DataEventArgs e)
    {
        ValidateFieldValueEventArgs ve = e;
        boolean isValid = true;
        LedgerParameters ledgerParameters = sender as LedgerParameters;
        #isoCountryRegionCodes
        
        if (ve.parmFieldName() == fieldStr(LedgerParameters, ChineseVoucher_CN) 
            && SysCountryRegionCode::isLegalEntityInCountryRegion([#isoCN]))
        {
            if ((select firstonly RecId from LedgerJournalTrans
                    where LedgerJournalTrans.LedgerVoucherType_CN != 0
                        || LedgerJournalTrans.Voucher_CN != '').RecId != 0)
            {
                // The general journal needs to be empty in order to modify the setup for the Chinese voucher system.
                isValid = checkFailed("@GLS54497");
            }
            ve.parmValidateResult(isValid);
        }
        
    }

Source - 
https://community.dynamics.com/365/financeandoperations/b/365operationswithsukrut/archive/2018/01/15/customizing-d365-with-event-handlers