Working! Rudimentary Salesforce API Client in a BPM

Ok, I know I’m a terrible coder, and that’s partly why I’m posting this. I know I’ll get things working but I don’t know standards / best practices. Some things I know, like use a transaction scope or make the company explicit, but there are things like naming conventions or commenting conventions that I’ve just never learned, being all self-taught, so please feel free to give any level of feedback.

The challenge is to update Salesforce when one of many things occurs. It took a lot of scouring of this forum and the net, with many thanks to @timshuwy, @Aaron_Moreng, @Mark_Wonsil, @ckrusen, @jgiese.wci , @josecgomez, @hkeric.wci and many others from the usual cast of suspects. I started out not knowing which end of an API you blow on and thinking Json was a weird way to spell my kid’s name.

I have this now, activated by a UBAQ just as a repeatable trigger.

  //SF Salesforce Thingy


  //Login & get Bearer Token

  string loginMsg = "";

string appUrl = "https://test.salesforce.com/services/oauth2/token" ;

      var crmClient = new RestClient(appUrl);

      var request = new RestRequest (Method.POST) { RequestFormat = DataFormat.Json };

     

      request.AddParameter("grant_type", "password");

      request.AddParameter("client_id", "3MVG9ayzKZt5EleF5ZgnAfpIhI9xobfuscatedbunch_6leLRZ8oftextnotthisi1gzRRipKiUzFSojROIURb8vz");

      request.AddParameter("client_secret", "F53A22ACB8Anotsecret2deletedstufftooF2D56ifididn'tchangethisA1FB90D3FD39E5A9171E6F5blahblahblahE330B");

request.AddParameter("username", "myusername");

      request.AddParameter("password", "mypassword");

     

      request.AddHeader("Content-Type", "application/x-www-form-urlencoded");

     

      var response = crmClient.Execute(request);

      string msg = response.Content.ToString();

  Ice.Diagnostics.Log.WriteEntry(msg);

      
      string jmsg = @"{'login1':[" + msg + "]}";

      DataSet crmData = Newtonsoft.Json.JsonConvert.DeserializeObject<DataSet>(jmsg);

     
      string access_token = crmData.Tables[0].Rows[0].ItemArray[0].ToString();

      string instance_url = crmData.Tables[0].Rows[0].ItemArray[1].ToString();

      string id = crmData.Tables[0].Rows[0].ItemArray[2].ToString();

      string token_type = crmData.Tables[0].Rows[0].ItemArray[3].ToString();

 


  // get account info

var AcctClient = new RestClient("https://companyname--erp.my.salesforce.com/services/data/v49.0/sobjects/Account/[salesforce account id]" );

      var AcctReq = new RestRequest(Method.GET) { RequestFormat = DataFormat.Json };

     
      AcctReq.AddHeader("Authorization", "Bearer " + access_token);

      AcctReq.AddHeader("Content-Type", "application/json");

      response = AcctClient.Execute(AcctReq);

      msg = response.Content.ToString();

      Ice.Diagnostics.Log.WriteEntry(msg);

 JObject acct = JObject.Parse(msg);

//remove the attributes node from the response or it can't be deserialized easily 
 acct.Remove("attributes");

     
msg = acct.ToString();
jmsg = @"{'login1':[" + msg + "]}";

   

DataSet acctData = Newtonsoft.Json.JsonConvert.DeserializeObject<DataSet>(jmsg);

string outMsg = "";

     

for (int q = 0; q < acctData.Tables[0].Columns.Count; q++)

        {

          outMsg += acctData.Tables[0].Columns[q].Caption.ToString() + " - " + acctData.Tables[0].Rows[0].ItemArray[q].ToString() + Environment.NewLine;

        }

   

//send the account details list to the server logs  

Ice.Diagnostics.Log.WriteEntry(outMsg);

 

I had to include Newtonsoft which I thought was missing but seems to just require waiting a while for it to load. Also couldn’t find the JObject classes until adding Newtonsoft.Json.Linq.

Notice that in a Postman collection some things show as headers but didn’t work until I changed AddHeader for AddParameter. I have no idea why, I got a hint of it online somewhere and it worked.

Obviously still a lot of work to do - there are 10 calls in all and this only reads - but thought this might help someone and would like feedback. I have got Postman to create new accounts in Salesforce so it can’t be too hard. As is, this logs in and gets the account details from Salesforce, and makes them available to have your way with in Epicor.

1 Like

This is above my paygrade, but there are some things I’m curious about. Like the part:

Is the .Execute() method synchronous, and waits for the response? This isn’t the norm (or at least I wouldn’t think so). In jQuery, there would be a handler that would be invoked after the reply from the target URL is received.

1 Like

Oh right, I forgot to mention - that’s from RestSharp, which I had to include. Everything else I tried seemed to require member declaration which BPMs don’t allow. I’m hoping to find something else already built in to avoid relying on RestSharp, but there seem to be a lot of people using it with Salesforce.

I’m not at all clear on how it works, being a bit of a script kiddie :grimacing:

So this is pretty interesting, what do others think?

RestSharp seems to be a great tool for this, but I want to be able to subcontract development. I’m also not sure what to think about building dependencies in.

Does Epicor still ship with built-in Http client libraries? Some of the examples on this forum don’t seem to be there anymore, even with System.Net included, for example.

Have I ever mentioned that Epicor Functions are available via https? :thinking:

2 Likes

This post unlocks a great way for those who are familiar with BPMs and widgets invoking BO methods to unleash their knowledge and bring it to the REST realm.

2 Likes

:lying_face: no never

Are there pre-existing functions for stuff like API calls? I just went from 100 to 700 and haven’t had a chance to learn about them yet, but I don’t want to re-invent the wheel more than necessary.

An interesting error:

CS1069	The type name 'HttpClient' could not be found in the namespace 'System.Net.Http'. This type has been forwarded to assembly 'System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly.

Is it a .Net framework version issue? The microsoft docs here HttpClient Class (System.Net.Http) | Microsoft Learn make me think I’m on the right track, but the thing just doesn’t seem to be there.

That’s basically what I’m trying to do.

In theory it should be easy to:

  • post or patch a customer whenever that happens in Epicor
  • post or patch a part whenever that happens in Epicor

etc.

Steve, what I was trying to say about @Mark_Wonsil 's post was that calling an epicor function from rest allows you to utilize epicor functions. If you haven’t read about epicor funcitons I believe it is found in the customization guide. They have a walkthrough example in there too which is cool. Once you build your function you can call it from rest and then you don’t have to worry about making a million rest calls in your application.

I take it some developers don’t mind coding and using rest calls, reading responses, etc., but for me I have found it a little easier to use the invoke BO method widgets and inside a function when I have to do lengthier customizations (like updating multiple tables or creating many records based on other validations).

I think it’s whatever you’re most comfortable with. Jose has made some killer nugets for rest that make it wayyyyy easier to start working with. I got hung up on using dynamic datasets. I need to revisit that side of it. But I’ve been getting back to basics and learning about widgets and bo methods. @jdewitt6029 has made some pretty extensive use of rest calls.

1 Like

Epicor REST comes in two versions (v1 and v2) and each of these are available in two flavors: OData and Custom. The custom interface is just a happy wrapper around the business objects you know and love. The OData is a, well, OData compliant interface to those same BOs (YMMV).

As Utah explained, using Functions (which are like BPMs in many ways) encapsulates the Epicor logic away from the client making upgrades much easier. You get to define your own API.

The next big thing would be Webhooks. Epicor could publish events (on_new_part, on_new_order, on_changed_user, etc.) and subscribers would be notified of the event. Subscribing to other webhooks can be done with an ASP.NET application which then talks to Epicor. Maybe this can be done with CDC, but I have not tried it yet.

1 Like

ahh, ok - so, nerd chills registered, but actually I’m still talking about calling someone else’s API from Epicor. So I interpreted this as “build a function to call Salesforce and then call that from your data directive”.

Which still might be a good idea, but I gather that’s not what you’re referring to.

1 Like

Yes, you are correct. I thought you were moving to the next step and getting data over to SF.

So quick aside about integrations. From easiest to most difficult:

  • No integration
  • One way Link
  • Two Way Link
  • One Way Sync
  • Two Way Sync
  • Seamless Integration (right over there, next to the :unicorn: )

We can go over each in depth at another time but it makes sense that keeping two sets of data in sync (like your Palm Pilot or more recently, OneDrive) is a very difficult task. Pushing all changes is easier but handling deletes can be inefficient if you actually remove records from your source.

And then there’s linking. A key is established to the target and when the key is passed, the most current data is returned for that target. In the case of Salesforce for example, a key to the Customer record in SF is passed to the REST endpoint and customer data is returned.

One of the more popular current architectures that works well with linking is the component. Components are popular in many popular JavaScript frameworks and ASP.NET Core. Why? Reusability. We can write a Salesforce Customer component and embed it in various places. There is comparatively less code in the hosting application which makes it easy to upgrade and to embed in many places throughout the application. With K21 being a web-based product, it will be far easier to write your own SF component and embed it. This is way better than trying to sync all of the data between the two systems. One should be able to expose K21 data in Salesforce as well given the correct key back into Epicor. Need to add a field to a view in the component? Go ahead. Every place that component is displayed gets that change without touching any of that hosting code.

Been thinking about integrations lately…

2 Likes