Interfacing with API in C# console program

I have been tasked with interfacing with a third-party software to manage invoice submittal and payment. It’s all done via ftp and I have the server setup to handle the ftp requests outside of Epicor.

I created the query within the Epicor space with the thought to have the Epicor server perform the query and return the results to me via REST. This is working fantastically but I’m struggling to turn this into JSON. Each time the call returns a blob string which I’ve tried a few different avenues to ‘prettify’ it.

Code below:
Main

HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://centralusdtadtl15.epicorsaas.com/saas5102third/api/v2/odata/100000/BaqSvc/rMasterVendor/Data");
            StringContent content = new StringContent("", null, "text/plain");
            

            // Build API Request with Headers
            request.Headers.Add("x-api-key", "API Key here");
            request.Headers.Add("Authorization", "Basic <auth string>");
            request.Content = content;


            HttpResponseMessage response = await client.SendAsync(request);
            response.EnsureSuccessStatusCode();

            var BAQResults = await response.Content.ReadAsStringAsync();
            EpiJSON json = new EpiJSON();
            JsonConvert.PopulateObject(BAQResults, json);

            Console.WriteLine(BAQResults);

EpiJSON

[JsonObject]
    public class EpiJSON
    {
        public string? context { get; set; }
        public List<EpiVendor>? value { get; set; }
    }

EpiVendor

public class EpiVendor
    {
        #region Fields

        private string? Calculated_CompanyID { get; set; }
        private string? Vendor_VendorID { get; set; }
        private string? Calculated_VendorCode { get; set; }
        private string? Calculated_VendorLocationID { get; set; }
        private string? Calculated_VendorLocationCode { get; set; }
        private string? Vendor_Name { get; set; }
        private string? Vendor_Address1 { get; set; }
        private string? Vendor_Address2 { get; set; }
        private string? Vendor_Address3 { get; set; }
        private string? Vendor_City { get; set; }
        private string? Vendor_State { get; set; }
        private string? Vendor_Zip { get; set; }
        private string? Vendor_Country { get; set; }
        private int? PurTerms_NumberOfDays { get; set; }
        private string? EntityGLC_GLControlCode { get; set; }
        private string? Calculated_ERSFlag { get; set; }
        private string? Calculated_PaySiteFlag { get; set; }
        private string? Calculated_PurchasingSiteFlag { get; set; }
        private string? Vendor_CurrencyCode { get; set; }
        private string? Calculated_PaymentCurrency { get; set; }
        private string? PayMethod_Name { get; set; }
        private string? Calculated_EMailAddress { get; set; } // Vendor Portal Contact Email Address
        private string? Calculated_Field2 { get; set; }
        private string? Calculated_Field3 { get; set; }
        private string? Calculated_Field4 { get; set; }
        private string? Calculated_Field5 { get; set; }
        private bool Calulated_Status { get; set; }

        #endregion

It kept throwing errors about non-nullable properties if I don’t use the null-conditional operator. Could somebody explain that too possibly?

My thought is to call the API, iterate through the dataset and I have to create a pipe-delimited text file from a few different tables, once every 2 hours and upload to the ftp server. I have the ftp script written and uploading files correctly, it’s just getting this data into a format that I know what to do with.

Might take me a second to parse what you are doing exactly, but if you want to “prettify” the json you are getting back you can just use Newtonsoft.Json, and do this:

//ref Newtonsoft.Json
using Newtonsoft.Json;

string pretty = JToken.Parse(BAQResults).ToString();
1 Like

Thanks for your response!

I just attempted that and got it to prettify but it cuts out over half of my dataset for some reason. I’m pulling two companies worth of data. The results that I get when it IS formatted correctly cuts out all Vendors of one company and picks of in the letter ‘N’ in the second company. Its super weird because it’s not even the start of that Vendor, it’s half of the object too. It’s like a character is in the string that’s throwing it off somehow but there isn’t anything crazy in the raw data and Postman display the results with no problem.

image

Interesting. You can leave off the Formatting.Indented, JToken.Parse().ToString() default is indented.

Why do you need it pretty?

If you need to see what’s going on, just prettify one row.

I come from a JavaScript background and my mind thought about turning it into JSON and iterating through the dataset that way. Am I thinking about it wrong? Would it be better to keep it as a string? How do I iterate through that?

It IS Json.

I would recommed using a library if you don’t want to deal with all the parsing and stuff yourlself

This nuget returns an object which contains the response as both a JSON string and a Dynamic C# object

1 Like

Got it. That’s what was confusing me along with I believe I hit a buffer limit on the console. I put an incrementor on a loop while it printed and saw that even though it was half of an object, the count started at row 5,xxx. I created it as a file and saw it.

So you good now?

1 Like

I’ll figure the rest out hopefully this weekend

1 Like

I love it when people say the whole term: JavaScript Object Notation

I know @josecgomez recommended an easier way which i’ll totally take up if I just can’t get this but I’m kind of determine to understand this and finish it this way :sweat_smile:

I jumped back into this after getting home. I’m getting stuck at the @odata.context portion of the JSON string. How do I map this via deserialization? That’s not a valid variable name. Would creating a class of oData and adding a field for context that holds a string value then make the oData variable of the oData class?

1 Like

I gotta run, but I would just get rid of it.

1 Like

Just ignore it.

namespace Discard1;

using System.IO;
using Newtonsoft.Json;

public class EpiJSON
{
    public List<ABCCode> value;
}
public class ABCCode
{
    public string? ABCCode_Company;
    public string? ABCCode_ABCCode;
    public int? ABCCode_CountFreq;
    public bool? ABCCode_ExcludeFromCC;
    public string? ABCCode_StockValPcnt;
    public string? ABCCode_PcntTolerance;
    public bool? ABCCode_CalcPcnt;
    public bool? ABCCode_CalcQty;
    public bool? ABCCode_CalcValue;
    public string? ABCCode_QtyTolerance;
    public string? ABCCode_ValueTolerance;
    public int? ABCCode_ShipToCustNum;
    public Guid? ABCCode_SysRowID;
    public Guid? RowIdent;        
}


class Program
{
    static void Main(string[] args)
    {
        string json = File.ReadAllText("abc.json");

        var anObject = JsonConvert.DeserializeObject<EpiJSON>(json);

        foreach(var abc in anObject.value)
        {
           Console.WriteLine(abc.ABCCode_SysRowID.ToString());
        }
    }
}

Input:

{
  "odata.metadata": "https://aserverliveshere.nope/server/api/v1/BaqSvc/KEV_ABC/$metadata#Epicor.DynamicQuery.QueryResults",
    "value": [
      {
        "ABCCode_Company": "1234567",
        "ABCCode_ABCCode": "A",
        "ABCCode_CountFreq": 3,
        "ABCCode_ExcludeFromCC": false,
        "ABCCode_StockValPcnt": "0",
        "ABCCode_PcntTolerance": "0.00",
        "ABCCode_CalcPcnt": false,
        "ABCCode_CalcQty": false,
        "ABCCode_CalcValue": false,
        "ABCCode_QtyTolerance": "0",
        "ABCCode_ValueTolerance": "0",
        "ABCCode_ShipToCustNum": 0,
        "ABCCode_SysRowID": "8429abd7-2649-4201-ac11-e2df2a9cd71a",
        "RowIdent": "00000001-0000-0000-0000-000000000000"
      },
      {
        "ABCCode_Company": "1234567",
        "ABCCode_ABCCode": "B",
        "ABCCode_CountFreq": 7,
        "ABCCode_ExcludeFromCC": false,
        "ABCCode_StockValPcnt": "0",
        "ABCCode_PcntTolerance": "0.00",
        "ABCCode_CalcPcnt": false,
        "ABCCode_CalcQty": false,
        "ABCCode_CalcValue": false,
        "ABCCode_QtyTolerance": "0",
        "ABCCode_ValueTolerance": "0",
        "ABCCode_ShipToCustNum": 0,
        "ABCCode_SysRowID": "d1840ac1-789e-406a-af2f-9bbbc1e77f70",
        "RowIdent": "00000002-0000-0000-0000-000000000000"
      } ]
  }

Output:

8429abd7-2649-4201-ac11-e2df2a9cd71a
d1840ac1-789e-406a-af2f-9bbbc1e77f70

The full code for ignore is:

var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;

var anObject = JsonConvert.DeserializeObject<EpiJSON>(json, jsonSerializerSettings);

The default is Ignore, so it works without it. JsonSerializerSettings.MissingMemberHandling Property

Gets or sets how missing members (e.g. JSON contains a property that isn’t a member on the object) are handled during deserialization. The default value is Ignore.

You can also use Error, and catch it.

You don’t need to define contracts to use deserialized objects, just use dynamic late binding… This is how I run BAQs from C#. Don’t use the oData (just V2 custom methods) because sometimes I need to pass parameter to BAQs that would make the url too long.

var baseAddress = $"{Session.AppServerURL}api/v2/odata/{Session.CompanyID}";

try
{
    using (var client = new HttpClient())
    {
        // Get the UBAQ parameters dataset
        var queryIdParamJson = JsonConvert.SerializeObject(new { queryID = baqId });

        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri(baseAddress + "/Ice.BO.DynamicQuerySvc/GetQueryExecutionParametersByID"),
            Headers = 
            {
                { "Authorization", "Basic " + auth },
                { "Accept", "application/json" },
                { "X-API-Key", apiKey },
            },
            Content = new StringContent(queryIdParamJson, Encoding.UTF8, "application/json")
        };

        var result = client.SendAsync(request).Result;

        if (result.IsSuccessStatusCode)
        {
            dynamic jsonRsp = JsonConvert.DeserializeObject(result.Content.ReadAsStringAsync().Result);

            var ds = jsonRsp.returnObj;

            // Set UBAQ parameters
            foreach (var param in ds.ExecutionParameter)
            {
                if (param.ParameterID == "param1")
                {
                    param.ParameterValue = param1Value;
                    param.IsEmpty = false;
                }
                else if (param.ParameterID == "param2")
                {
                    param.ParameterValue = param2Value;
                    param.IsEmpty = false;
                }
            }

            var queryParamsJson = JsonConvert.SerializeObject(new { queryID = baqId, executionParams = ds });

            request = new HttpRequestMessage
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri(baseAddress + "/Ice.BO.DynamicQuerySvc/ExecuteByID"),
                Headers =
                {
                    { "Authorization", "Basic " + auth },
                    { "Accept", "application/json" },
                    { "X-API-Key", apiKey },
                },
                Content = new StringContent(queryParamsJson, Encoding.UTF8, "application/json")
            };

            // Call UBAQ
            result = client.SendAsync(request).Result;

            if (result.IsSuccessStatusCode)
            {
                jsonRsp = JsonConvert.DeserializeObject(result.Content.ReadAsStringAsync().Result);

                // Get the returned dataset as a dynamic
                ds = jsonRsp.returnObj;

                foreach (var row in ds.Results)
                {
                    // Do something
                    var a = (decimal)row.Calculated_SomeCalcField;
                }
            }
        }
    }
}
catch (Exception ex)
{
    // Handle exceptions
}

If you need to map the results to an internal type for your application, just do it in the final foreach.

1 Like