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;
}
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.
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?
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.
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 ---
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?
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 ---
I see the following
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?
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.
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()]);
}
}
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.
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?
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 ---