Calling an Epicor Function via C# in an external program

@klincecum, I’ve been able to get past the Function issue, but I’m still having a problem calling the function within C#. Below is thte code I’m using in the call:

        private static async void CallPostFunction()
        {
            try
            {
                string ApiKey = Properties.Settings.Default.APIKey;
                var RestClient = new RestClientBuilder()
                    .SetAppServerUrl(Properties.Settings.Default.AppServerURL)
                    .SetCompanyId(Properties.Settings.Default.CompanyID)
                    .SetDefaultApiKey(ApiKey)
                    .UseBasicAuthentication(Properties.Settings.Default.BatchEntryPerson, Properties.Settings.Default.BatchEntryPass)
                    .Build();

                var FunctionParameters = new RestContent(new { GroupID = BatchGroupID });

                var FunctionResponse = await RestClient.Function.PostAsync(Properties.Settings.Default.RESTLibrary, Properties.Settings.Default.RESTFunction, FunctionParameters, true);
                if (FunctionResponse.IsSuccessStatusCode != true)
                {
                    Console.WriteLine(FunctionResponse.StatusCode.ToString() + " " + FunctionResponse.Content);
                }
            }
            catch (Exception e)
            {
                throw new Exception("REST Call", e);
            }

When the call is executed, I get a System.IO.IOException from System.Private.CoreLib.dll, then a System.Exception from the project. I can eexcute the same call from Swagger with no error, I just don’t know what I’m doing wrong in the code. Would you have a minute or two to help me?

Maybe, but here, I think your problem is this is a version where the RestClient helper is broken.

You’ll have to use something else.

…OK, what would I use instead? I HAD this working before upgrading this project to .NET 7 and VS 2022. Is there any way to revert a project backward without completely rebuilding it?

This is an external project?

If so, just add Restsharp.

Kevin, do you have an example of using RestSharp to call the function using an API Key and a parameter? I’ve been looking for one but haven’t had any luck in finding one specific to Epicor.

Yes, somewhere. Gimme a bit.

https://www.epiusers.help/t/modbus-to-epicor-integration-for-the-curious/101408

Thank you! I apologize for being a pest, this is all just VERY new to me, and I have a project that is almost past deadline to get this working. BTW, any suggestion on reading to get up to speed on using REST calls? Or just the Epicor REST V2 documentation?

To be honest, I recommend doing a search here for “REST”, clicking “More…”, and
read whatever you see that looks interesting. (Or everything, if you’re like me :dumpster_fire: )

We’re all a bit pesty in the beginning, especially when

feel-that-warm-breeze

Plus, I’ll talk to a fencepost if it sits still long enough.

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?