Querying WebAPI - webapi Cannot deserialize the current JSON array - c#

I have built a Web API using MVC and it works as expected.
I am now trying to query the API from a console application and I am hitting an issue. I understand why I am getting the issue but I dont understand how to fix it.
My Code from the console application:
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
RunAsync().GetAwaiter().GetResult();
}
static async Task RunAsync()
{
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
List<TagDetail> tagDetail = new List<TagDetail>();
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=myTag&startdate=010120190000&enddate=020120190000");
Console.WriteLine(tagDetail.value);
}
static async Task<TagDetail> GetTagDetailAsync(string path)
{
List<TagDetail> tagdetail = new List<TagDetail>();
HttpResponseMessage response = await client.GetAsync(path);
var test = response.StatusCode;
var test2 = response.Headers;
if (response.IsSuccessStatusCode)
{
tagdetail = await response.Content.ReadAsAsync<List<TagDetail>>(
new List<MediaTypeFormatter>
{
new XmlMediaTypeFormatter(),
new JsonMediaTypeFormatter()
});
}
return tagdetail;
}
The error I am getting is on the lines:
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=99TOTMW&startdate=010120190000&enddate=020120190000");
And
return tagdetail;
The Web API returns the data in JSON format which looks like:
{
"tagname":"myTag",
"value":"99.99",
"description":"myDescription",
"units":"£",
"quality":"Good",
"timestamp":"2019-08-01T17:32:30"
},
{
"tagname":"myTag",
"value":"22.22",
"description":"myDescription",
"units":"£",
"quality":"Good",
"timestamp":"2019-08-01T17:33:30"
}
The TagDetail class is just declaration of each of the fields you see above.
The webapi provide the means of selecting a date range so I would get numerous TagDetails back as a List but it can also return just one (I can get this working by changing my code a bit). I need it to work for either one result or multiple.

As the comment has explained that you need to return List<TagDetail> for your GetTagDetailAsync.Then you could use foreach to loop the result.This will work for one or multiple TagDetail
static async Task RunAsync()
{
//other logic
List<TagDetail> tagDetail = new List<TagDetail>();
tagDetail = await GetTagDetailAsync("api/tagdetail/?tagname=myTag&startdate=010120190000&enddate=020120190000");
foreach(var item in tagDetail)
{
Console.WriteLine(item.value);
}
}
static async Task<List<TagDetail>> GetTagDetailAsync(string path)
{
List<TagDetail> tagdetail = new List<TagDetail>();
HttpResponseMessage response = await client.GetAsync(path);
var test = response.StatusCode;
var test2 = response.Headers;
if (response.IsSuccessStatusCode)
{
tagdetail = await response.Content.ReadAsAsync<List<TagDetail>>(
new List<MediaTypeFormatter>
{
new XmlMediaTypeFormatter(),
new JsonMediaTypeFormatter()
});
}
return tagdetail;
}

Related

httpClient Does not give Response

i am trying to call the Zoom Api for Access token. It work Perfectly fine when i am trying to post with postman .
but from code it does not respond
ZoomApiLink_StepNo2
below are the following details
public static async Task<String> PostTogetToken<T>(string requestUrl, string client_Secretkey) {
ZoomToken hello = new ZoomToken();
var EncodedURl = ApiService.Base64Encode(client_Secretkey);
using (var _httpClient = new HttpClient()) {
if (!string.IsNullOrEmpty(requestUrl))
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Basic " + EncodedURl);
var httpContent = new StringContent("", System.Text.Encoding.UTF8, "application/x-www-form-urlencoded");
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(requestUrl)) {
Version = HttpVersion.Version11,
Content = httpContent
};
using (var response = await _httpClient.SendAsync(httpRequestMessage)) {
if (response.IsSuccessStatusCode) {
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
} else {
return await response.Content.ReadAsStringAsync();
}
}
}
}
You're seeing a common deadlock problem because code further up the stack is using Result, blocking on asynchronous code.
To fix, change the Result to await and use async all the way.

Null message on hTTPResponse.Content.ReadAsStringAsync().Result after 1st foreach loop

I have issue with null result messages when calling a HttpClient getAsync within a foreach loop.
I need to iterate with a list of objects for values to call an API via HttpClient. After the first loop, the HttpResponseMessage's result() comes up with null message.
I tried CacheControl.NoCache = true. It's not working.
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
/*This is where the issue is .begin.*/
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
/*This is where the issue is .end.*/
if (hTTPResponse.IsSuccessStatusCode)
{
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}
On: 'string result = hTTPResponse.Content.ReadAsStringAsync().Result;'
I would expect the result message would have values as it loops through ListOfObjects.
Update: Per comment
First off, I generally try to avoid using .Result with tasks. I also don't think adding the cache headers is the issue since you are making a server to server call.
One thing I tend to always do is evaluate the response before I try to read it. I also prefer to use the HttpRequestMessage so I can manage the disposal of it.
Here is a quick example demonstrating how call out:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Testing
{
class Program
{
static void Main(string[] args)
{
TestAsync().Wait();
}
static async Task TestAsync()
{
var urls = new string[]
{
"https://stackoverflow.com/questions/57084989/null-message-on-httpresponse-content-readasstringasync-result-after-1st-foreac",
"https://stackoverflow.com/users/2025711/rogala"
};
using (var httpClient = new HttpClient())
{
foreach (var url in urls)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
Console.WriteLine($"Request: {url}".PadLeft(5,'*').PadRight(5, '*'));
var response = await httpClient.SendAsync(request)
.ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
Console.WriteLine($"{body.Length}{Environment.NewLine}");
}
else
{
Console.WriteLine($"*Bad request: {response.StatusCode}");
}
}
}
}
Console.ReadKey();
}
}
}
One additional thing to keep in mind is socket exhaustion. If this code is running on a server, then you will run into socket exhaustion once your service gets some load. I would highly recommend using an HttpClientFactory to handle the HttpClients.
With respect to why the content string is empty, it could be because the server didn't return content which could be a server issue. The server may have also throttled you, which resulted in no content. I would recommend looking into Polly to handle certain response codes.
Solved it... I moved the .DefaultRequestHeaders values inside the foreach() loop.
I suspected that they were pilling up at every loop iteration. I had to clear them and set them up before the GetAsync()
Updated code here:
public async Task<List<Common.ProgressResponse>> RunAsync()
{
List<Response> ListOfResponses = new List<Responses>();
try
{
_client.BaseAddress = new Uri([URL]);
ListOfResponses = await SomeFunction();
}
catch (Exception ex)
{
//Exceptions here...
}
return ListOfResponses;
}
private async Task<List<ListOfResponses>> SomeFunction()
{
List<Response> responses = new List<Response>();
string aPISuffix = string.Format("{0}/{1}", [APISUFFIX1], [APISUFFIX2]);
foreach (Object obj in ListOfObject)
{
/*Moved Code .begin.*/
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true };
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add([KEY], [VALUE]);
/*Moved Code .end.*/
_client.DefaultRequestHeaders.Add("Param1", obj.Param1);
if (!string.IsNullOrEmpty(obj.Param2))
_client.DefaultRequestHeaders.Add("Param2", obj.Param2);
Response response = new Response();
HttpResponseMessage hTTPResponse = await _client.GetAsync(aPISuffix).ConfigureAwait(false);
if (hTTPResponse.IsSuccessStatusCode)
{
string result = hTTPResponse.Content.ReadAsStringAsync().Result;
response = [Code here...]
//Codes here...
}
responses.Add(response);
}
return responses;
}

HttpClient post returns bad request in c#, works in postman

I'm trying to access a rest endpoint, https://api.planet.com/auth/v1/experimental/public/users/authenticate. It is expecting json in the request body.
I can get the request to work in Postman but not using c#. Using postman I get the expected invalid email or password message but with my code I get "Bad Request" no matter I try.
Here is the code that makes the request
private void Login()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://api.planet.com/");
client.DefaultRequestHeaders.Accept.Clear();
//ClientDefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
Data.User user = new Data.User
{
email = "myemail#company.com",
password = "sdosadf"
};
var requestMessage = JsonConvert.SerializeObject(user);
var content = new StringContent(requestMessage, Encoding.UTF8, "application/json");
var response = client.PostAsync("auth/v1/experimental/public/users/authenticate", content).Result;
Console.WriteLine(response.ToString());
}
catch (WebException wex )
{
MessageBox.Show(wex.Message) ;
}
}
class User
{
public string email;
public string password;
}
Here are screen grabs form Postman that are working
The way to get this to work was to alter the content header "content-type". By default HTTPClient was creating content-type: application/json;characterset= UTF8. I dropped and recreated the content header without the characterset section and it worked.
content.Headers.Remove("Content-Type");
content.Headers.Add("Content-Type", "application/json");
The issue is you are trying to call an async method without waiting for the response using await method or var task = method; task.Wait() Therefore, when you end up doing response.ToString() it returns the text you are seeing.
One way to handle this within a non-async method would be to do the following:
var task = client.PostAsync("auth/v1/experimental/public/users/authenticate", content);
task.Wait();
var responseTask = task.Content.ReadAsStringAsync();
responseTask.Wait();
Console.WriteLine(responseTask.Result);
Another way is to make the current method async by doing private async void Login() and then do:
var postResp = await client.PostAsync("auth/v1/experimental/public/users/authenticate", content);
var response = await postResp.Content.ReadAsStringAsync();
Console.WriteLine(response);
Create a Method Like this...
static async Task<string> PostURI(Uri u, HttpContent c)
{
var response = string.Empty;
var msg = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.PostAsync(u, c);
msg = await result.Content.ReadAsStringAsync();
if (result.IsSuccessStatusCode)
{
response = result.StatusCode.ToString();
}
}
return response;
}
call In your Method
public void Login()
{
string postData ="{\"email\":\"your_email\",\"password\":\"your_password\"}";
Uri u = new Uri("yoururl");
var payload = postData;
HttpContent c = new StringContent(payload, Encoding.UTF8,"application/json");
var t = Task.Run(() => PostURI(u, c));
t.Wait();
Response.Write(t.Result);
}

Testing REST API endpoints

I have a number of REST api endpoints that I am calling via ajax from a web client, and I want to write some automated tests to insure that they work properly outside of a web browser.
I am writing them as unit tests Tests and here is what I have so far:
[TestClass]
public class ApiTests
{
string local_host_address = "http://localhost:1234//";
public async Task<string> Post(string path, IEnumerable<KeyValuePair<string, string>> parameters)
{
using (var client = new HttpClient())
{
client.Timeout = new TimeSpan(0,0,5);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response_message = await client.PostAsync(local_host_address + path, new FormUrlEncodedContent(parameters));
var response = await response_message.Content.ReadAsStringAsync();
if (response_message.IsSuccessStatusCode)
{
return response;
}
else
{
throw new Exception("Request failed");
}
}
}
[TestMethod]
[TestCategory("ApiTests")]
public void TestLogon()
{
var parameters = new Dictionary<string, string>();
parameters["email"] = "bob#aol.com";
parameters["password"] = "rosebud";
Task.Run( () =>
{
var output = Post("Default.aspx/Logon", parameters);
Console.WriteLine(output.Result);
}).Wait();
}
}
...pretty basic, it just tries to call a specific endpoint, and return the results. Problem is, this call returns the basic default.aspx web page body, not the results generated by default.aspx/logon. I am doing something wrong, but I have been over it with a debugger and I cannot see my error. The default.aspx/logon endpoint exists and it works perfectly when I access it via website. Am I missing or overlooking something?
-TTM
SOLUTION:
Bruno's alteration of my code snippet works quite nicely. Anyone else trying to solve the problem of testing a REST endpoint can just put that into a unit test and pass in a POCO and it will return the JSON response.
You are sending the body as FormUrlEncoded although you marked your request as application/json.
If your API is REST and takes JSON, instead of taking the Dictionary, you could deserialize an object (e.g. with Newtonsoft.Json):
public async Task<string> Post<T>(string path, T data)
{
using (var client = new HttpClient())
{
client.Timeout = new TimeSpan(0, 0, 5);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var json = JsonConvert.SerializeObject(data);
var response_message = await client.PostAsync(local_host_address + path, new StringContent(json, Encoding.UTF8, "application/json");
var response = await response_message.Content.ReadAsStringAsync();
if (response_message.IsSuccessStatusCode)
{
return response;
}
else
{
throw new Exception("Request failed");
}
}
}

Troubleshooting HTTPClient asynchronous POST and reading results

I have a problem post string to form and read. Problem is they get away but need to do so sophisticated and it was very fast. Absolutely perfect multithreaded or asynchronous. Thank you very for your help.
This is my code.
private static void AsyncDown()
{
const string url = "http://whois.sk-nic.sk/index.jsp";
const string req = "PREM-0001";
var client = new HttpClient();
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("text", "PREM-0001")
};
FormUrlEncodedContent content = new FormUrlEncodedContent(pairs);
HttpResponseMessage response = client.PostAsync("http://whois.sk-nic.sk/index.jsp", content).Result;
if (response.IsSuccessStatusCode)
{
HttpContent stream = response.Content;
Task<string> data = stream.ReadAsStringAsync();
}
}
Taking a rough stab in the dark at what your problem is, I'd guess that you're having trouble reading the response of your call.
When the content is POSTed to the server,
HttpResponseMessage response
= client.PostAsync("http://whois.sk-nic.sk/index.jsp", content).Result;
if (response.IsSuccessStatusCode)
{
HttpContent stream = response.Content;
Task<string> data = stream.ReadAsStringAsync();
}
It does so asynchronously, so the code will continue execution even though the result is not (most likely) available yet. Checking response.IsSuccessStatusCode will thus not give you the behavior you're expecting.
Change your calls to look like this by adding the await keyword:
HttpResponseMessage response
= await client.PostAsync("http://whois.sk-nic.sk/index.jsp", content);
Then, change the reading of the stream to use await as well:
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
}
EDIT: got some await objects mixed up and have corrected the code listing.
edit 2: here is the complete LINQPad script that I used to successfully download an HTML page from the given URL.
var client = new HttpClient();
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("text", "PREM-0001")
};
FormUrlEncodedContent content = new FormUrlEncodedContent(pairs);
HttpResponseMessage response = await client.PostAsync("http://whois.sk-nic.sk/index.jsp", content);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
//data.Dump(); //uncomment previous if using LINQPad
}
Maybe the site has changed since last post but now the request parameter name is whois not text. If this was the case a year ago too that's why it didn't work.
Today site responds to get too, i.e. http://whois.sk-nic.sk/index.jsp?whois=PREM-0001
Full code with get:
private async Task<string> Get(string code)
{
using (var client = new HttpClient())
{
var requestUri = String.Format("http://whois.sk-nic.sk/index.jsp?whois={0}", code);
var data = await client.GetStringAsync(requestUri);
return data;
}
}
Full code with post:
private async Task<string> Post()
{
using (var client = new HttpClient())
{
var postData = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("whois", "PREM-0001"),
};
var content = new FormUrlEncodedContent(postData);
var response = await client.PostAsync("http://whois.sk-nic.sk/index.jsp", content);
if (!response.IsSuccessStatusCode)
{
var message = String.Format("Server returned HTTP error {0}: {1}.", (int)response.StatusCode, response.ReasonPhrase);
throw new InvalidOperationException(message);
}
var data = await response.Content.ReadAsStringAsync();
return data;
}
}
Or a parser could be used because I guess extracting the returned values is the final goal:
private void HtmlAgilityPack(string code)
{
var requestUri = String.Format("http://whois.sk-nic.sk/index.jsp?whois={0}", code);
var request = new HtmlWeb();
var htmlDocument = request.Load(requestUri);
var name = htmlDocument.DocumentNode.SelectSingleNode("/html/body/table[1]/tr[5]/td/table/tr[2]/td[2]").InnerText.Trim();
var organizations = htmlDocument.DocumentNode.SelectSingleNode("/html/body/table[1]/tr[5]/td/table/tr[3]/td[2]").InnerText.Trim();
}

Categories