1

My company uses a C# .net application to programmatically retrieve data from a vendor website in the form of a CSV file. However, the vendor changed their API recently to be a JSON API. I'm a bit of a novice programmer, and don't have any experience with JSON API calls. When the vendor registered my new API userID, they provided an example of how to download the necessary csv data file using curl. Here is the sample:

echo 'Going to log in to sso'
data=$(curl -c cookies.txt -X POST https://sso.theice.com/api/authenticateTfa -H "Content-Type: application/json" \
--data '{"userId":"yourApiUserId","password":"yourpassword","appKey":"yourAppKey"}')

echo 'Going to download icecleared_oil_2020_06_26.dat file from Settlement_Reports_CSV/Oil'
curl -b cookies.txt -O ' https://downloads2.theice.com/Settlement_Reports_CSV/Oil/icecleared_oil_2020_06_26.dat

Since I have no experience with Curl, I used https://curl.olsh.me/ to convert the sample above to C#. Doing so generated the following code:

//Authenticate userId (and get cookie?)
using (var httpClient = new HttpClient())
{
    using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://sso.theice.com/api/authenticateTfa"))
    {
        request.Content = new StringContent("{\"userId\":\"yourApiUserId\",\"password\":\"yourpassword\",\"appKey\":\"yourAppKey\"}");
        request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); 

        var response = await httpClient.SendAsync(request);
    }
}

//Using authentication cookie, from previous code block, retrieve dat file
var handler = new HttpClientHandler();
handler.UseCookies = false;

using (var httpClient = new HttpClient(handler))
{
    using (var request = new HttpRequestMessage(new HttpMethod("GET"), "https://downloads2.theice.com/Settlement_Reports_CSV/Oil/icecleared_oil_2020_06_26.dat"))
    {
        request.Headers.TryAddWithoutValidation("Cookie", "cookies.txt");

        var response = await httpClient.SendAsync(request);
    }
}

I integrated this code into my project and was able to get it to authenticate my userID (the POST request), but cannot for the life of me figure out how to retrieve the DAT file from the second code block (the GET request). I have observed that the response from the GET request is the same no matter whether I successfully login or not with the POST request. At this point I am assuming the validation cookie from the POST request is not being picked up by the GET request, but even if it was I'd have no idea how to extract the file from the response object in the GET request.

Can anyone advise on what I need to do? Below is the code that's been integrated into my project:

public async Task DownloadAsync(List<string> iceReportFileNamesFullUrl)
{
    await AuthenticateUserCredentialsOnICE();
    await RetrieveIceRportFiles(iceReportFileNamesFullUrl);

}

private async Task AuthenticateUserCredentialsOnICE()
{
    using (HttpClient httpClient = new HttpClient())
    {
        using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("POST"), "https://sso.theice.com/api/authenticateTfa"))
        {               
            request.Content = new StringContent("{\"userId\":\"xxxxxxxxx\",\"password\":\"xxxxxxxxxx\",\"appKey\":\"xxxxxxxxxxx\"}");
            request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");

            HttpResponseMessage response = await httpClient.SendAsync(request);
            var value = response.Content.ReadAsStringAsync();
        }
    }
}
private static async Task RetrieveIceRportFiles(List<string> iceReportFileNamesFullUrl)
{
    foreach (string iceReportUrl in iceReportFileNamesFullUrl)
    {
        HttpClientHandler handler = new HttpClientHandler();
        handler.UseCookies = false;

        using (HttpClient httpClient = new HttpClient(handler))
        {
            using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("GET"), iceReportUrl))
            {
                request.Headers.TryAddWithoutValidation("Cookie", "cookies.txt");

                HttpResponseMessage response = await httpClient.SendAsync(request);
                var value = response.Content.ReadAsStringAsync();

            }
        }
    }
}

-UPDATE: 25 AUG 2020-

Thanks for your comments y'all. You made it clear that I wasn't capturing the cookies from the POST request. After some research (this, this, and this), I've updated the code so that it now successfully downloads the CSV/DAT file. I've posted what I have below, though it still requires error handling, checks for failed login, etc. I'm also sure there will be critical feedback.

Here it is:

private CookieContainer authenticationCookieContainer;
//main method for downloading file
public async Task DownloadAsync(string iceReportDownloadUrl)
{
    await AuthenticateUserCredentialsOnIceAsync();
    await SendDownloadReportRequestAsync(iceReportDownloadUrl);
}

Authenticate the credientials via JSON API and store authentication cookies in authenticationCookieContainter

private async Task AuthenticateUserCredentialsOnIceAsync()
{
    //This will contain the ICE authentication cookies
    //if the login is successful
    authenticationCookieContainer = new CookieContainer();

    //Create and assign handler for proxy config and cookie storage
    using (HttpClientHandler handler = new HttpClientHandler() { CookieContainer = authenticationCookieContainer })
    {
        using (HttpClient httpClient = new HttpClient(handler))
        {
            using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("POST"), "https://sso.theice.com/api/authenticateTfa"))
            {
                request.Content = new StringContent("{\"userId\":\"xxxxxxxxxxx\",\"password\":\"xxxxxxxxxxxxxx\",\"appKey\":\"xxxxxxxxxxxxx\"}");
                request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
                HttpResponseMessage response = await httpClient.SendAsync(request);
            }
        }
    }
}

If the login was successful, the authenticationCookieContainer will contain the login cookies and allow you to request the data at the specified url

private async Task SendDownloadReportRequestAsync(string iceReportDownloadUrl)
{
    if (authenticationCookieContainer != null)
    {
        //Create and assign handler for proxy config and cookie storage
        using (HttpClientHandler handler = new HttpClientHandler() { CookieContainer = authenticationCookieContainer })
        {
            //Set to true to use the cookies obtained during authentication call
            handler.UseCookies = true;

            using (HttpClient httpClient = new HttpClient(handler))
            {
                Uri iceReportUri = new Uri(iceReportDownloadUrl);
                //Request the ICE data file using the url passed through
                using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("GET"), iceReportUri.AbsoluteUri))
                {
                    HttpResponseMessage response = await httpClient.SendAsync(request);
                    using (Stream responseStream = await response.Content.ReadAsStreamAsync())
                    {
                        //Write the extracted file to the file path
                        byte[] content = StreamToByteArray(responseStream);
                        File.WriteAllBytes(@"C:\Users\my.name\Desktop\" + iceReportUri.Segments.Last(), content);
                    }
                }
            }
        } 
    }
}
private static byte[] StreamToByteArray(Stream stream)
{
    using (MemoryStream memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        return memoryStream.ToArray();
    }
}
jelhan
  • 6,149
  • 1
  • 19
  • 35
  • 1
    What do you think this line is going to do? `request.Headers.TryAddWithoutValidation("Cookie", "cookies.txt");` – Ian Kemp Aug 21 '20 at 19:13
  • @IanKemp I think it's meant to get the validation cookie from the POST request... – yellow_thermos Aug 21 '20 at 19:29
  • 2
    Why not try POSTMAN (https://www.postman.com/downloads/) and then you can separate out the actual API call and make sure it works? – raddevus Aug 21 '20 at 19:31
  • 2
    From the example they have provided, you arent requesting the CSV file from a JSON API. You're authenticating and obtaining a cookie via the JSON API and then using that to access static content. When you post the JSON to their API to authenticate, you need to use that response as your cookie in the following request to the file. – Kieran Devlin Aug 21 '20 at 19:37
  • @yellow_thermos Instead of assuming, why not read the documentation of [`TryAddWithoutValidation`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.headers.httpheaders.tryaddwithoutvalidation)? Then you will **know**. And if you know, you'll understand what the issue is. – Ian Kemp Aug 21 '20 at 21:36

0 Answers0