Can I use Epicor DLLs reliably in .NET Core 6 (generating reports)

Hello all,

I am writing a method to save a pdf invoice when given an invoice id. The end result will be our accounting department having an interface to email specific invoices to their contacts. The reason for making a custom interface instead of using a bulk email or bulk invoice print, is that we need each invoice to be its own pdf. This is also a pilot project of sorts as we determine how to structure our integration with Epicor objects.

The backend is using an api that is referencing a new “ERP” class library (currently just Epicor). Both of these are .NET Core 6. I’m attempting to reuse code that works in our .NET 4.8 app (net.tcp and WCF, ARInvFormImpl and ARInvFormParam, submit to the SystemTaskAgent, write to directory.)

Despite adding what I think are the required DLLs, it continues to throw exceptions. Right now, it gets stuck in
c# var objSOForm = new Erp.Proxy.Rpt.ARInvFormImpl(netTcpBinding, svcSOForm);
throwing
System.MissingMethodException: ‘Method not found: ‘System.ServiceModel.Security.X509CertificateValidationMode System.ServiceModel.Security.X509ServiceCertificateAuthentication.get_CertificateValidationMode()’.’
Source: “Epicor.Ice.Shared.Wcf.Private”

  1. Any idea what I need to add here?
  2. Is planning to use Epicor DLLs in .NET Core 6 doomed from the start to always run into this kind of issue?
  3. Are there any compatibility libraries or it possible to use .NET standard to avoid future issues?

Bonus: it doesn’t appear that saving an invoice as a pdf in this way marks it as printed. Is there a param for that?

public async Task<string> PrintInvoice(int invoiceId)
        {

            var errorMessage = string.Empty;

            //TODO get customerName? or use date as dir name so we can auto delete on a different day
            var dirName = DateTime.Now.DayOfWeek.ToString();
            var directoryPath = GetReportDirectory() + $@"{dirName}\";
            Directory.CreateDirectory(directoryPath); // create directory (if not exists)

            // TODO change this to be automatic
            var epicorInstance = "EpicorTest";

            string filePath = GetReportFilePath(invoiceId, dirName);

            try
            {
                CustomBinding netTcpBinding = NetTcp.UsernameWindowsChannel();

                Uri svcSOForm = new Uri("net.tcp://epicor_app/" + epicorInstance + "/Erp/Rpt/ARInvForm.svc");
                Uri svcReportMonitor = new Uri("net.tcp://epicor_app/" + epicorInstance + "/Ice/BO/ReportMonitor.svc");

                var creds = new CredentialHelper();

                // code breaks here due to missing method exception. System.ServiceModel.Security.X509ServiceCertificateAuthentication.get_CertificateValidationMode()
                var objSOForm = new Erp.Proxy.Rpt.ARInvFormImpl(netTcpBinding, svcSOForm);
                objSOForm.ClientCredentials.UserName.UserName = creds.EpicorUsername;
                objSOForm.ClientCredentials.UserName.Password = creds.EpicorPassword;

                var datasetSOForm = new Erp.Rpt.ARInvFormDataSet();

                var guid = Guid.NewGuid();

                var workstationID = guid.ToString() + invoiceId.ToString();


                datasetSOForm = objSOForm.GetNewParameters();
                datasetSOForm.ARInvFormParam.Rows[0]["InvoiceNum"] = invoiceId;
                datasetSOForm.ARInvFormParam.Rows[0]["AutoAction"] = "SSRSGenerate";
                datasetSOForm.ARInvFormParam.Rows[0]["AgentID"] = "SystemTaskAgent";
                datasetSOForm.ARInvFormParam.Rows[0]["SSRSRenderFormat"] = "PDF";
                datasetSOForm.ARInvFormParam.Rows[0]["WorkstationID"] = workstationID;

                objSOForm.SubmitToAgent(datasetSOForm, "SystemTaskAgent", 0, 0, "Erp.UIRpt.ARInvForm");

                var objReportMonitor = new Ice.Proxy.BO.ReportMonitorImpl(netTcpBinding, svcReportMonitor);
                objReportMonitor.ClientCredentials.UserName.UserName = creds.EpicorUsername;
                objReportMonitor.ClientCredentials.UserName.Password = creds.EpicorPassword;

                var datasetReportMonitor = new Ice.BO.ReportMonitorDataSet();

                var startTime = DateTime.Now;

                while (true)
                {
                    var result = await _repository.ReportStatus.GetByWorkstationIdAsync(workstationID, false);

                    if (result != null)
                    {
                        if (result.TaskStatus == "COMPLETE")
                        {
                            break;
                        }
                        else if (result.TaskStatus == "ERROR")
                        {
                            objSOForm.Dispose();
                            objReportMonitor.Dispose();

                            errorMessage = "Report failed to generate.";

                            return string.Empty;
                        }
                    }

                    // if report takes too long to complete, thow an exception
                    if (DateTime.Now - startTime > TimeSpan.FromMinutes(3))
                    {
                        throw new Exception("SOA Creation timed out"); // leave while true loop
                    }

                    Thread.Sleep(500);
                }

                datasetReportMonitor = objReportMonitor.GetRowsKeepIdleTime($"WorkStationID = '{workstationID}'", 0, 0, out bool morePages);

                objReportMonitor.Update(datasetReportMonitor);

                byte[] reportBytes = objReportMonitor.GetReportBytes((Guid)datasetReportMonitor.SysRptLst.Rows[0]["SysRowID"]);
                objReportMonitor.Update(datasetReportMonitor);

                objSOForm.Dispose();
                objReportMonitor.Dispose();

                using (var reportFile = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                {
                    for (var i = 0; i < reportBytes.Length; i++)
                    {
                        reportFile.WriteByte(reportBytes[i]);
                    }
                }



            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;

                return string.Empty;
            }

            return filePath;
        }

1 Like

I think the intent from Epicor is to use the REST API for integrations. I’m not aware of anyone using the .NET Core .DLLs for integration like we used to do with the .NET Framework ones.

One nice thing about using the REST API is that you are disconnected from version changes, so forced updates are less frequent. Only necessary if the data schema or method parameters change.

Check this out if you haven’t already:

2 Likes

You got that in there twice :wink:

I think, there is a field in that dataset you set to mark as printed. Can’t remember, I’d run a trace.

I’d use REST for your integration if you can.

Doesn’t APR already do this? Generate a separate pdf for each invoice and email it individually?

Thank you all for your replies!

We are going to try to use the REST API instead. I was also not aware of APR and will investigate if it solves our use case.