I am using HttpClient to make a GET call and i want to parse the Json response and assert the key value pairs
This is my code
public partial class EntityClass
{
[JsonProperty("StatusCode")]
public string StatusCode { get; set; }
// more code
}
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.Credentials = new NetworkCredential(#"Domain\user", "password");
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://baseURL");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.GetAsync("api/apiendpoint")
response.EnsureSuccessStatusCode();
string responsebody = response.Content.ReadAsStringAsync().Result;
var data = JsonConvert.DeserializeObject<EntityClass>(responsebody);
}
}
I can assert the response like this
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
However I want to assert each Key Value pair in the Json response. I cant get it to work with the above code
How can I access the key value pair and assert?
For example if the Json Response is:
{
StatusCode:200,
Key1: Value1,
Key2: Value2
}
I want to be able to assert like
Assert.AreEqual(200,data.StatusCode);
Assert.AreEqual(Value1, data.Key1);
Assert.AreEqual(Value2, data.Key2);
However in my code response.StatusCode returns OK and not 200
As the commenters noted, the status code is not part of the body, but you appear to be handling that part correctly.
To get to the key/value pairs as you call them, you can parse the string into an intermediate representation, and then iterate over the properties of that representation. This avoids having to have the native .NET type that would normally be populated, which may be preferable to avoid having the automation test depend on a specific type.
So, it depends on which serializer you can or have to use. Since I see JsonConvert in your code you're using Newtonsoft's code. In that case use JObject.Parse():
using Newtonsoft.Json.Linq;
...
var expectedValues = new Dictionary<string, object>();
var responsebody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var root = JObject.Parse(responsebody);
foreach (var property in root.Properties)
{
// not tested, but you get the idea...
var expectedValue = expectedValues[property.Name];
if (expectedValue == null)
// null has to be checked via token type
Assert.IsTrue(property.Value.Type == JTokenType.Null);
else
// check they are same type AND value
Assert.AreEqual(property.Value.ToObject(expectedValue.GetType()), expectedValue);
}
The trick here is that you need to create a dictionary that maps expected keys to expected values, and then check these values against what's in the intermediate representation.
This can be used to prove that no unexpected key is in the output (because there would be no dictionary entry) and to prove that for the keys returned, their values are expected.
To also prove there are no missing keys, you can check the keys of the dictionary against the names of the returned properties.
Related
Returning data from my API and deserializing it in the client controller, the content seems to empty. I've verified that the API is returning valid data, and it the "count" in the response is accurate. However, the data when serialized is seems not be initialized.
Not quite sure where to look for what's causing the issue. My best theory is that the data is being deserialized before it's materialized due to the async, but that doesn't explain why the count is correct (2 records).
public async Task<IActionResult> Index()
{
var httpClient = _httpClientFactory.CreateClient("APIClient");
var request = new HttpRequestMessage(HttpMethod.Get, "/api/AppUsers");
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
var users = await JsonSerializer.DeserializeAsync<IList<AppUser>>(responseStream);
return View(new IndexViewModel(users));
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
return RedirectToAction("AccessDenied", "Authorization");
}
throw new Exception("Problem accessing the API");
}
API Method which is called
[HttpGet]
public IActionResult GetAppUsers()
{
var users = _appUserService.ListAppUsers();
return Ok(users);
}
Calling the API Method directly shows the 2 rows returned as expected
After some debugging, I noticed that the json structure returned from the API is having first letter in property name as lowercase, but the AppUser class have first letter UpperCase. Not sure why this happens, but that's the reason why the deserialization fails.
Try to deserialize to List instead to IList:
var users = await JsonSerializer.DeserializeAsync<List<AppUser>>(responseStream);
I think that you should remove ConfigureAwait(false) so you can remain in the same synchronizaton context.
For more info check this link: https://devblogs.microsoft.com/dotnet/configureawait-faq/
After noticing the properties changing from upper case to lower case on the JSON object, the solution will be to either force JSON to PascalCase as the default now is camelCase
https://codeopinion.com/asp-net-core-mvc-json-output-camelcase-pascalcase/
or I need to consider the camelCase on deserialization.
This issue stole several hours from my life :/
Hope this post can save some hours for somebody else.
One solution would be to disable the defaulting
builder.Services.AddControllers(options =>
{
options.ReturnHttpNotAcceptable = true;
})
.AddXmlSerializerFormatters()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null; //to avoid defaulting to camelCase
});
Due to the fact that I need to convert this C# dll into a tlp file to be called from Visual Basic 6, I need avoid using external dependencies. I have used RestSharp to consume a WebAPI by doing the following (working):
using RestSharp;
using Newtonsoft.Json;
..
public string GetToken (string Key, string Password) {
var client = new RestClient (BaseUrl + "auth/GetToken");
var request = new RestRequest (Method.POST);
request.AddHeader ("cache-control", "no-cache");
request.AddHeader ("Content-Type", "application/json");
Dictionary<string, string> data = new Dictionary<string, string> {
{ "APIKey", Key },
{ "APIPassword", Password }
};
var dataJSON = JsonConvert.SerializeObject (data);
request.AddParameter ("undefined", dataJSON, ParameterType.RequestBody);
IRestResponse response = client.Execute (request);
GetTokenResults g = JsonConvert.DeserializeObject<GetTokenResults> (response.Content);
return g.Token;
}
where GetTokenResults was a struct that contained a declaration for the string Token. I want to achieve this same functionality without using RestSharp. Here is my unsuccessful attempt:
using System.Net.Http;
using System.Net.Http.Headers;
..
public async void GetToken (string Key, string Password) {
var client = new HttpClient ( );
client.DefaultRequestHeaders.Accept.Clear ( );
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue ("application/json"));
client.BaseAddress = new Uri (BaseUrl + "auth/GetToken");
Dictionary<string, string> data = new Dictionary<string, string> {
{ "APIKey", Key },
{ "APIPassword", Password }
};
var dataJSON = JsonConvert.SerializeObject (data);
var content = new StringContent (dataJSON, Encoding.UTF8, "application/json");
var response = await client.PostAsync ("", content);
}
I am unclear on how to achieve the same results (send API key and password, return token as string) using HttpClient as I did using RestSharp earlier. Anything that can point me in the right direction is greatly appreciated!
I think you got stung by this issue. In short, the URI in client.BaseAddress needs a slash at the end of it.
However, I wouldn't simply add it, I'd consider doing it a little different. Presumably your BaseUrl already has a trailing slash, given you're appending "auth/GetToken" to it. I'd do it this way:
client.BaseAddress = new Uri(BaseUrl);
...
var response = await client.PostAsync("auth/GetToken", content);
As you can see, HttpClient fits very cleanly with how your code is already set up, i.e. you have a "base" address with a trailing slash and you want to append to it for a specific call.
That should get you un-stuck to this point. The next thing you'll need to tackle is deserializing the JSON response so you can get the token out of it. It's similar to how you did it in RestSharp, except that response.Content is not a string in the HttpClient world, so you need one more step to get that:
var json = await response.Content.ReadAsStringAsync();
GetTokenResults g = JsonConvert.DeserializeObject<GetTokenResults>(json);
return g.Token;
Last thing you'll need to do to get this to compile is change the method signature to:
public async Task<string> GetTokenAsync
One final note: you are now in the async world, and that's a good thing, but you need to know how to use it correctly or you could end up with deadlocks and other mysterious bugs. In short, don't block on async code by calling .Result or .Wait() anywhere up the call stack. That's by far most common mistake people make. Use async/await all the way down.
I think you are missing first parameter in the method PostAsync i.e. requestUri=Client.BaseAddress (see my implementation below).
Try with this first, if did not work, read below. I have a little different implementation where I passed client.BaseAddress as first parameter and I am passing my content as ByteArrayContent. In my case I have to pass my content as "application/x-www-form-urlencoded" excerpt of my code:
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
//as I can't send JSON, probably, you can skip as it's already JSON
byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
//requestUri=client.BaseAddress
await client.PostAsync(requestUri, byteContent).ConfigureAwait(false);
We have somewhat different need but I think you are pretty close. If it does not help, write me I will share my code. After reading the comment, I would like to share how I have made my HttpClient. The code is as it is:
using (var client = CreateMailClientForPOST($"{BaseUrl}/"))
{
//removed code, you can call above code as method like
var response= await client.DoThingAsAsync($"{client.BaseAddress}, content").ConfigureAwait(false);
}
protected HttpClient CreateMailClientForPOST(string resource)
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
}
var client = new HttpClient(handler)
{
BaseAddress = new Uri($"https://api.address.com/rest/{resource}")
};
return client;
}
I have this simple method which suppose to get weather data, when I call it this error occur:
System.Runtime.Serialization.SerializationException was unhandled by user code
HResult=-2146233076
Message=There was an error deserializing the object of type UWpWeather.RootObject. Encountered unexpected character '<'.
public async static Task <RootObject> GetWeather(double lat, double lng) {
var http = new HttpClient();
var response = await http.GetAsync("http://api.openweathermap.org/data/2.5/forecast/daily?q=leeds&type=accurate&mode=xml&units=metric&cnt=3&appid= MY AIP-KEY");
string result = await response.Content.ReadAsStringAsync();
var serializer = new DataContractJsonSerializer(typeof (RootObject));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
var data = (RootObject) serializer.ReadObject(ms);
return data;
}
The API does not honour any of the HTTP content or Accept headers you pass through on the request, but rather it sets the content-type of the response based on the query string parameter.
Your initial URL:
http://api.openweathermap.org/data/2.5/forecast/daily?q=leeds&type=accurate&mode=xml&units=metric&cnt=3&appid=
MY AIP-KEY"
What it should be:
http://api.openweathermap.org/data/2.5/forecast/daily?q=leeds&type=accurate&mode=json&units=metric&cnt=3&appid=
MY AIP-KEY"
That should allow you to deserialize it into your RootObject correctly.
Caveat: I don't have your root object implementation, so I could only verify up until getting a JSON-formatted response back.
I found the answer, my first mistake was using Xml instead of Json when calling my data. second, when I used this website (json2csharp) to convert Json to series of classes that represent my Json it created it fine except one which created as a list public List<List> list { get; set; }
I simply removed that one and my code now is working just fine. thanks all for your support.
I have a code that access a Api link and retrieves that Json values and stores them in a object list. However when there is nothing to retrieve (Link to the Api doesnt exist) it returns nothing.
However even though the Api link is wrong it still returns Json values. These are their default values that will always be there even though there is nothing that matches the ID requested.
Correct Api Json value
Below link will show values that will always return no matter what
INCorrect Api Json value
here is where i call to the Api
var spidyApi_idByName_result = api_Handler.objFromApi_nameToId(spidyApi_searchIdByName);
Here the Api is accessed and the Json is deserialized.
public RootObject objFromApi_nameToId(string url){
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
try{
WebResponse response = request.GetResponse();
using (Stream responseStream = response.GetResponseStream()){
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
var jsonReader = new JsonTextReader(reader);
var serializer = new JsonSerializer();
return serializer.Deserialize<RootObject>(jsonReader);
}
}
catch (WebException){
throw;
// used throw as otherwise i can not run code, function requires every path to return
// Catch never gets called
}
}
RootObject class
public RootObject GetApi(string url)
{
// ...
return serializer.Deserialize<RootObject>(jsonReader);
}
How do i go around and ignore their "default" json values? just do nothing.
You need to manage the defaults in your code. E.g. Your invalid API example will always return the count as 0. You can now use that condition to decide your program flow.
RootObject rootObject = serializer.Deserialize<RootObject>(jsonReader);
if (rootObject.count > 0)
return rootObject;
else
return null; //or any other program flow you need to execute.
I'm having some issues with calling an API.
I need to send post data containing 2 things: an ID and an array of strings.
I have tried a lot of things, all resulting in errors or simply not sending data in the right way.
All answers I found, do not handle the fact that I want to send 2 different data types.
My current C# code is like this:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(SERVER_URI);
var content = ; //This is where I need help
HttpResponseMessage response = client.PostAsync("API URL", content).Result;
The API function is set up like this:
public ActionResult Function(int Id, string[] array)
{
// Contents are not relevant
}
The problem here is that I need to be able to set the names for the values.
I have tried serializing the required data to Json with the following code:
StringContent content = new System.Net.Http.StringContent(TypeSerializer.SerializeToString(new { Id = Id, array = array }));
Of course, Id and Array in this example are filled variables.
This results in a successful call to the server, but the server does not receive the data correctly (both variables stay null)
I've also tried doing it with MultiPartContent, but once again I don't see any way to actually give the right names to the values (Every attempt once again results in the API receiving null values)
Edit:
I got it to send the Id using MultipartFormDataContent instead.
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StringContent(Id), "Id");
I still can't seem to get it to send an array to the server though.
Wrap things into an object like this
public class MyPostObject
{
public int Id{get;set;}
public IEnumerable<string> Array{get;set;}
}
then send it as json
using (var client = new HttpClient())
{
var myObject = new MyPostObject(){Id =XXXX, Array = YYYYY};
using (var response = await client.PostAsJsonAsync("api/CONTROLLER/METHOD", myObject))
using (var content = response.Content)
{
var result = content.ReadAsAsync<LoginModelResponse>().Result;
}
}
and receive it as json like this in the server
[HttpPost]
public dynamic METHOD([FromBody] MyPostObject mydata)
{
//Do whatever
}