.NET Development
Switching from HttpClient to HttpWebRequest
Gravatar is a globally recognized avatar based on your email address. Switching from HttpClient to HttpWebRequest
  Naomi
  All
  Nov 28, 2016 @ 02:51am

Hi everybody,

I wrote a .NET C# dll that sends and receives json using HttpClient class. I tested it and it was working fine. All of the sudden it stopped working and instead of sending json using application/json format started to send it in the text/plain. All my attempts to remedy the problem failed so far.

Also, I was advised by Rick to use HttpWebRequest instead of HttpClient because I would need to call that dll from VFP code (as well as from another C# dll). So, I am attempting to re-write my code now using HttpWebRequest class.

My problem at the moment - I am not sure how to use the needed API urls and one request variable.

Here is my whole code so far:

 private HttpWebRequest request;

        /// <summary>
        /// CityPass CheckPass function
        /// <param name="tcHost">The URL for the “Host:” string
will be user configurable</param>
        /// <param name="tcUserName">The DLL will encode it along with tcPassword into the “Authorization:” string</param>
        /// <param name="tcPassword"></param>
        /// <param name="tcBarcode">Barcode - The DLL will package up the tcBarcode and tcUsageDate into simple JSON</param>
        /// <param name="tcUsageDate">Datetime of the scan converted into the necessary string format</param>
        /// <param name="tnTimeout">The number of seconds to wait for the HTTP POST call to complete and return a response</param>
        /// <param name="tnRetries">If no respone in the timeout period, the number of times to attempt contact. 
        ///                         If it was a DNS lookup failure or other glaring connection issue, then retries can be bypassed..</param>
        /// <param name="tnReturnValue">Passed by reference, it is the “ReturnValue” string in the JSON response</param>
        /// <param name="tcReturnValueText">Passed by reference, it is the “ReturnValueText” string in the JSON response </param>
        /// <param name="tcMessage">“Message” string in the JSON response, if present</param>
        /// <param name="tcProductCode">“ExternalProductCode” string in the JSON response, if present</param>
        /// <returns>int Status</returns> 
        /// </summary>
        public int CheckPass(String tcHost, String tcUserName, String tcPassword, String tcBarcode,
                      String tcUsageDate, Int32 tnTimeout, Int32 tnRetries,
                      ref Int32 tnReturnValue, ref String tcReturnValueText, ref String tcMessage, ref String tcProductCode,
                      String url = "/integrationapi//Attraction/Tickets/Validate")

        {
            Int32 statusCode = 1;
            Int32 nTries = 0;
            try
            {
                SetRequestHeaders(tcHost, tcUserName, tcPassword, tnTimeout);

                var json = new CityPassJSON();

                json.TicketUsageRequests = new List<TicketUsageRequest>();
                json.TicketUsageRequests.Add(new TicketUsageRequest(tcBarcode, tcUsageDate, tcUserName));

                String jsonString = JsonConvert.SerializeObject(json);

                TicketUsageResponse responseObject = null;

                while (nTries <= tnRetries)
                {

                    String result = GetResponse(jsonString, url);

                    try
                    {
                        responseObject = JsonConvert.DeserializeObject<TicketUsageResponse>(result); ;
                    }

                    catch (AggregateException exception)
                    {
                        if (exception.InnerExceptions.OfType<TaskCanceledException>().A??ny())
                        {
                            nTries++; // increment number of tries
                            if (nTries > tnRetries)
                            {
                                throw exception;
                            }
                        }
                        else
                        {
                            throw exception;
                        }
                    }
                    catch (TaskCanceledException te)
                    {
                        nTries++; // increment number of tries
                        if (nTries > tnRetries)
                        {
                            throw te;
                        }
                    }
                    catch (WebException we)
                    {
                        if (we.Status == WebExceptionStatus.Timeout && nTries < tnRetries)
                        {
                            nTries++; // increment number of tries
                        }
                        else
                        {
                            throw we; // re-throw exception
                        }
                    }

                    if (responseObject != null)
                    {
                        tcMessage = responseObject.ReturnData[0].Message;
                        tcReturnValueText = responseObject.ReturnValueText;
                        tnReturnValue = responseObject.ReturnValue;
                        tcProductCode = responseObject.ReturnData[0].ExternalProductCode;
                        break; // from the while loop
                    }
                }
            }

            catch (Exception e)
            {
                var extra = "";
                if (e.InnerException != null)
                {
                    extra = e.InnerException.Message;
                }
                tcReturnValueText = e.Message;
                if (extra != "")
                {
                    tcReturnValueText = tcReturnValueText + Environment.NewLine + extra;
                }
                statusCode = 0;
            }

            return statusCode;
        }


        private string GetResponse(string json, String url)
        {
            request = WebRequest.Create(url) as HttpWebRequest;
            request.Method = "Post";
            request.ContentType = "application/json;charset=UTF-8";
            Stream stream = request.GetRequestStream();
            byte[] buffer = Encoding.UTF8.GetBytes(json);
            stream.Write(buffer, 0, buffer.Length);

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            if (response.StatusCode == HttpStatusCode.OK)
            {
                Stream responseStream = response.GetResponseStream();
                using (StreamReader sr = new StreamReader(responseStream))
                {
                    return sr.ReadToEnd();
                }
            }
            else
            {
                throw new Exception(response.StatusDescription);
            }
        }

        private void SetRequestHeaders(string tcHost, string tcUserName, string tcPassword, int tnTimeout)
        {
            request = WebRequest.Create(tcHost) as HttpWebRequest;
            request.Host = tcHost;
            request.Headers.Remove("Accept");
            request.Headers.Add("Accept", "application/json");
            string authorizationKey = Convert.ToBase64String(
             System.Text.ASCIIEncoding.ASCII.GetBytes(
                 string.Format("{0}:{1}", tcUserName, tcPassword)));
            request.Headers.Add("Authorization", "Basic " + authorizationKey);
            request.Timeout = tnTimeout * 1000;
        }
Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Naomi
  Nov 28, 2016 @ 02:58am

So, my problem is that I need to use the API url in the GetResponse. Assuming I created my request variable properly in the SetRequestHeaders method (although this method uses Host URL), what changes do I need to implement in the GetResponse method? I do not see any methods that using the Host url will go to specific url. Do I need to re-create the request again? Can (and should) I re-use the request?

This is what is not clear to me. When I was using HttpClient I had static client member (I saw suggestions of using static variable). Here right now I declared private member of my class called request. I am not sure if it's correct or not.

Thanks a lot in advance.

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Naomi
  Nov 28, 2016 @ 06:10am

I sent email to the developers of the site I am supposed to be using. I don't know why it was working before. Now all my attempts (using HttpClient and HttpWebRequest) result in the bad request error.

I am supposed to send

POST /integrationapi//Attraction/Tickets/Validate HTTP/1.1 Host: hosthere Content-Type: application/json Authorization: Basic string here Cache-Control: no-cache Postman-Token: string here

{ "TicketUsageRequests": [ { "TicketBarcode": "something here", "TicketUsageDate": "2016-10-28T15:09:06.7021957-06:00", "UserID": "user here" } ] }

and this is what I am sending

POST https://hosthere//integrationapi//Attraction/Tickets/Validate HTTP/1.1 Authorization: Basic string here Content-Type: application/json; Host: hosthere Cache-Control: no-store,no-cache Pragma: no-cache Content-Length: 193 Expect: 100-continue Connection: Keep-Alive

{ "TicketUsageRequests": [ { "TicketBarcode": "barcode here", "TicketUsageDate": "2016-10-28T15:09:06.7021957-06:00", "UserID": "name here" } ] }

So, the difference I see is that in my case the POST uses full URL as opposed to partial URL and also I'm using there (they are somehow added to HttpWebRequest):

Pragma: no-cache Content-Length: 193 Expect: 100-continue Connection: Keep-Alive


The rest seems to be the same as what I am supposed to send. Also, as I said, in my initial attempts everything was working and I was getting status 200 before.

Perhaps the developers changed API?

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Naomi
  Nov 28, 2016 @ 12:58pm

It was indeed a problem on their end. I now have both versions of the code (using HttpClient and HttpWebRequest) working. I guess I need to keep the second version as the right one.

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Rick Strahl
  Naomi
  Nov 29, 2016 @ 12:16am

The easiest way to use HttpWebRequest actually is to use WebClient() which is wrapper around it. Most operations are a single line although if you need to add authentication or headers it's a little more work.

WebClient client = new WebClient();
client.Headers.Set("Accept", "application/json");
client.Headers.Set("Content-Type", "application/json");

// this should handle basic auth
client.Credentials = new NetworkCredential("username", "password");

var resultJson = client.UploadString(url,json);

Another even easier way is to use Westwind.Utilities and you can do something like this:

[TestMethod]
public void JsonRequestTest()
{
    var settings = new HttpRequestSettings()
    {
        Url = "http://codepaste.net/recent?format=json",
        Credentials = new NetworkCredential("username","Password")
    };
    var snippets = HttpUtils.JsonRequest<List<CodeSnippet>>(settings);

    Assert.IsNotNull(snippets);
    Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);
    Assert.IsTrue(snippets.Count > 0);
    Console.WriteLine(snippets.Count);
}

It handles the HTTP Call and JSON conversion for you in one shot.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Rick Strahl
  Nov 29, 2016 @ 02:17am

WestWind.Utilities look great. Do you have a link to the documentation? UPDATE. Found http://west-wind.com/westwindtoolkit/docs/_4ij04e9hw.htm

In particular, how would I translate my SetHeaderRequests method if using utilities (e.g. setting no-cache and timeout)?

Also, how would I handle a few tries in case of server's timeout?

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Rick Strahl
  Naomi
  Nov 29, 2016 @ 03:26am

There's a Headers property on the settings object.

http://west-wind.com/westwindtoolkit/docs/_4ij04e9q9.htm

public void JsonRequestTest()
{
    var settings = new HttpRequestSettings()
    {
        Url = "http://codepaste.net/recent?format=json",
        Credentials = new NetworkCredential("username","Password"),
        Timeout = 5000
    };
    settings.Headers.Add("Cache","no-cache");
    
    var snippets = HttpUtils.JsonRequest<List<CodeSnippet>>(settings);

    Assert.IsNotNull(snippets);
    Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);
    Assert.IsTrue(snippets.Count > 0);
    Console.WriteLine(snippets.Count);
}

Intellisense is your friend.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Rick Strahl
  Nov 29, 2016 @ 11:05am

I see the following

http://stackoverflow.com/questions/22560971/what-is-the-overhead-of-creating-a-new-httpclient-per-call-in-a-webapi-client

Does it also apply to the HttpWebRequest?

E.g. I added a private static request member, but I'm re-creating it on each call. What exactly is happening when a new request created? The original request is properly disposed or it consuming resources?

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Naomi
  Nov 29, 2016 @ 01:03pm

BTW, I realized that I can not use Westwind.Utilities after I already started making changes in my class.

The reason is that I need to send one json object (using POST method) and receive back another (different object).

I checked documentation and don't see this scenario covered.

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Rick Strahl
  Naomi
  Nov 30, 2016 @ 04:30am

Sure that works. Just set settings.Content to your JSON object and it will be serialized. The result value will be deserialized into the generic type parameter you specify.

Look at the tests for examples at:
https://github.com/RickStrahl/WestwindToolkit/blob/master/Tests/Westwind.Utilities.Test/HttpUtilsTests.cs

Here's a test that posts an object and receives a list result:

[TestMethod]
public void JsonRequestPostTest()
{
    // object to post
    var postSnippet = new CodeSnippet()
    {
        UserId = "Bogus",
        Code = "string.Format('Hello World, I will own you!');",
        Comment = "World domination imminent"
    };

    // configure the request
    var settings = new HttpRequestSettings()
    {
        Url = "http://codepaste.net/recent?format=json",
        Content = postSnippet,
        HttpVerb = "POST"
    };

    var snippets = HttpUtils.JsonRequest<List<CodeSnippet>>(settings);

    Assert.IsNotNull(snippets);
    Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);
    Assert.IsTrue(snippets.Count > 0);

    Console.WriteLine(snippets.Count);
    Console.WriteLine(settings.CapturedRequestContent);
    Console.WriteLine();
    Console.WriteLine(settings.CapturedResponseContent);

    foreach (var snippet in snippets)
    {
        if (string.IsNullOrEmpty(snippet.Code))
            continue;
        Console.WriteLine(snippet.Code.Substring(0, Math.Min(snippet.Code.Length, 200)));
        Console.WriteLine("--");
    }
    
    Console.WriteLine("Status Code: " + settings.Response.StatusCode);

    foreach (var header in settings.Response.Headers)
    {
        Console.WriteLine(header + ": " + settings.Response.Headers[header.ToString()]);
    }
}
Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Rick Strahl
  Nov 30, 2016 @ 05:57am

I see, looks great indeed. Too bad I already switched to another task for a moment. May be I'll return back to it, though and implement using your library instead of HttpWebRequest I am using at the present.

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Naomi
  Naomi
  Nov 30, 2016 @ 06:32am

I decided to make a quick test and it worked quite nicely. Also there is much less code. But I would need to distribute more libraries, I believe.

With my current solution I need my dll and NewtonSoft.

With your utilities I need my dll, Westwind.Utilities, NewtonSoft and also System.Net.Http.Formatting

Am I right that all these dlls are required?

Gravatar is a globally recognized avatar based on your email address. re: Switching from HttpClient to HttpWebRequest
  Rick Strahl
  Naomi
  Nov 30, 2016 @ 02:32pm

You don't need System.Net.Http.Formatting, but you do need the others. Newtonsoft.Json is required either way you go - HttpClient's serialization depends on it as well.

  • wwipstuff.dll (or ClrLoader.dll)
  • wwdotnetbridge.dll
  • westwind.utilities.dll
  • newtonsoft.json.dll

+++ Rick ---

© 1996-2024