Calling an Epicor Function via C# in an external program

Kevin, hopefully one last issue… I installed RestSharp 110.2.0 in my project and used the example from the link above to code a POST call to my Epicor function. Below is the code I created and a Swagger page showing the function call I’m trying to make. The function call works fine in Swagger, however it’s failing without any error when calling from the RestSharp code. Do I need to deserialize the argument data in the request to call the function, or is there something else I have screwed up?

        private static void CallPostFunction()
        {
            try
            {
                var options = new RestClientOptions(Properties.Settings.Default.AppServerURL);
                options.Authenticator = new HttpBasicAuthenticator(Properties.Settings.Default.BatchEntryPerson, Properties.Settings.Default.BatchEntryPass);
                var client = new RestClient(options);
                var request = new RestRequest();
                request.AddHeader("Accept", "application/json");
                request.AddHeader("X-API-Key", Properties.Settings.Default.APIKey);
                request.Method = Method.Post;
                string RequestContent = "\"GroupID\":" + $@"{BatchGroupID}";
                request.AddParameter("application/json", RequestContent, ParameterType.RequestBody);
                var response = client.Execute(request);
            }
            catch (Exception e)
            {
                throw new Exception("REST Call", e);
            }
        }

That translates to:

"GroupID": somegroupID

You need something more like:

{
    "GroupID": "somegroupID"
}

So either build the json with a new object and serialize it, or fix your json string manually:

Serialize Object:

var requestContentObj = new
{
    GroupID = $"{BatchGroupID}"
};

string requestContent = JsonConvert.SerializeObject(requestContentObj);

JsonConvert is part of Newtonsoft.Json.

Manually build via string:

string RequestContent = $"{\"GroupID\": \"{BatchGroupID}\"}";

Kevin, I built the JSON object and serialized it like you said, then ran my debug. However, when I try to execute the POST call to the server I’m getting the following error:
The SSL connection could not be established. See inner exception for details.

	Name	Value	Type
▶	InnerException	{"The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot"}	System.Exception {System.Security.Authentication.AuthenticationException}

   at System.Net.Http.ConnectHelper.<EstablishSslConnectionAsync>d__2.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.HttpConnectionPool.<ConnectAsync>d__100.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.HttpConnectionPool.<CreateHttp11ConnectionAsync>d__102.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.HttpConnectionPool.<AddHttp11ConnectionAsync>d__76.MoveNext()   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.<WaitWithCancellationAsync>d__1.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.HttpConnectionPool.HttpConnectionWaiter`1.<WaitForConnectionAsync>d__5.MoveNext()   at System.Net.Http.HttpConnectionPool.<SendWithVersionDetectionAndRetryAsync>d__86.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.RedirectHandler.<SendAsync>d__4.MoveNext()   at System.Threading.Tasks.ValueTask`1.get_Result()   at System.Net.Http.DecompressionHandler.<SendAsync>d__16.MoveNext()   at System.Net.Http.HttpClient.<<SendAsync>g__Core|83_0>d.MoveNext()   at RestSharp.RestClient.<ExecuteRequestAsync>d__3.MoveNext()

Couple of questions:

  1. Can I use a self-signed certificate?
  2. Where on the server does it need to be located?
  3. Where in the project do I need to indicate where and what certificate is to be used?

Yes, I assume you have one on there since it’s working and you can get to Kinetic & Swagger :slight_smile:

I’m not sure. If you have a need to replace it, someone else will need to answer that one.

If you are going to use a self signed, you have three options.

    1. Ignore it in code.
    1. Verify the signature yourself in code.
    1. Install the signature on your dev computer in the trusted root. (Exercise for you :dumpster_fire: )

I would not recommend option 1.
I can show you option 1 & 2. I think I have an example either on here or in my notes.

Obligatory:


@Mark_Wonsil

Option 2: (Recommended)

//Verify hash yourself

string Your_Certificate_SHA256_Hash = "longass_sha256_hashstring";

var client = new RestClient(serverUrl);

client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
    bool success = false;
  
    string certHash = "";
    certHash = certificate.GetCertHashString(System.Security.Cryptography.HashAlgorithmName.SHA256); 

    if(certHash == Your_Certificate_SHA256_Hash)
    {
        success = true;
    }
  
    return success;  
};

Option 1: Blind Trust - Not Recommended

//Ignore errors
//Blind Trust - Not Recommended

var client = new RestClient(serverUrl);

client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
    return true;  
};

I think you can also just tell RestSharp to ignore them, but I can’t remember how to do that,
or if this is what they said to do :rofl:

When you installed your appServer, you had the opportunity to choose a certificate or let Kinetic create one for you. You can see these certs in certmgr at the server.

image

To check binding, use IIS Manager and select bindings:

To automate all of this, you can follow @jgiese.wci and @EarlGrei’s “stupid simple” advice and use a service that generates free certs for you. Or if you want to do it yourself with a little help from Azure (yes, even for on-prem servers) you can read the following. Using real trusted certs just makes life so much more simple.

de923a09-02e5-4387-8af0-3bc2c8a4981d_text

Party on, party people, let me hear some noise :musical_note:
DC’s in the house, jump, jump, rejoice :musical_note:

Kevin, I found a certificate that IIS is using on the server to which I’m issueing the REST call that is not expired. To verify the certificate like you indicated, where would I find the hash value? I have a screenshot of the certificate details below, would it be the Thumbprint or Serial Number value?


Easiest way would be to run that snippet I gave you modified to print it out.

Kevin, the code snippet isn’t working, I think due to the version I’m using (110.2.0). VS is saying the RemoteCertificateValidationCallback is under the RestClientOptions class, so I set up the code like this:

                var options = new RestClientOptions(Properties.Settings.Default.AppServerURL)
                {
                    Authenticator = new HttpBasicAuthenticator(Properties.Settings.Default.BatchEntryPerson, Properties.Settings.Default.BatchEntryPass)
                    RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
                    {
                        string certHash = "";
                        certHash = certificate.GetCertHashString(System.Security.Cryptography.HashAlgorithmName.SHA512);
                        Console.WriteLine(certHash);
                    }
                };

What do I need to do to remedy this?

I’ve got that somewhere, let me think.

I had to redo it:

namespace RCExample;

using RestSharp;
class Program
{
    static void Main(string[] args)
    {

        var options = new RestClientOptions("https://icanhazip.com")
        {
            RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
            {
                string certHash = "";
                certHash = certificate.GetCertHashString(System.Security.Cryptography.HashAlgorithmName.SHA512);
                Console.WriteLine(certHash);
                return true;
            }
        };            

        var client = new RestClient(options);

        var request = new RestRequest();
        
        request.AddHeader("Accept", "*/*");
        request.Method = Method.Get;
        
        var response = client.Execute(request);

    }
}

Response:
8E7F0961D277F9BC0625FB4CA3F67420B97A257A2F25417BDDF15DC900D176F43FA3C5B8AE9F75FAA1E229DE20CED5F18A81B376DA9A91D2352F42C04FC73956

Kevin, I still can’t get the string to display in the console window. The REST still isn’t working either. What do I need to do to put the hash where it’s needed, or to bypass it if I’m checking it myself? I’m not getting any kind of exception in the call. I did find there is a self-signed certificate in the IIS bindings that doesn’t expire until 2031. What am I doing wrong if I can call the function from the REST V2 help page?

What url are you passing it? Try passing the main url you use to get to kinetic, that
way it won’t need to try to auth.

I’m passing the URL https://usaz1app007p.am.dir.grpleg.com/EpicorERPPilot/api/v2/efx/KEN/PostAPInvoice/PostAPGroup. Same one I’m using in the Swagger call. What would I pass in instead?

Kevin, would you have time sometime today to chat about this by phone or Teams?

Normally I would, but after I finish my coffee, I’m packing up my office and moving 200 yards
away lol.

Pass it this: Let’s see what happens

https://usaz1app007p.am.dir.grpleg.com/EpicorERPPilot/apps/erp/home/

If I use that URL, how will the system know to call the function?

I meant just to get the cert hash.

OK, got it…

Kevin, I just tried the code above in a new project, will not return a certificate hash from the lambda expression. What next? Also wanted to ask if I need to have the certificate from the server installed on my system as well for the call to work…