Testing REST API endpoints - c#

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");
}
}
}

Related

PostAsync API giving Bad Gateway

I am invoking a third party POST API from my own API (again POST METHOD). The third party API is having a security key, and it is working fine on the POSTMAN tool. However, when I tries to invoke through code, I am getting error, 'Bad Gateway'. Following is the code which I tried.
public static async Task<string> GetDetailsfromThirdParty(string kszstrng)
{
string contentstring = string.Empty;
using (var client = new HttpClient())
{
string baseURL = "https://abcde.kz.in/b2/vhsearch-all";
string prms = kszstrng;// input parameters to API, in JSON Format- this is JSON String.
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("key", "value");
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(prms);
var content = new ByteArrayContent(messageBytes);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(baseURL, content).ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
contentstring = result;
}
}
catch (Exception ex)
{
string msg = ex.Message.ToString();
}
return contentstring;
}
}
I am getting error on this line:
var response = await httpClient.PostAsync(baseURL, content).ConfigureAwait(false);
While trying to execute I am getting the below error:
Not able to find out what's the issue? There is no network / Fireawall blockage. I have cross-verified with Systems Team as well.
Please suggest any issue with the code.
First of all, i recommend you to not declare the HttpClient in a using statement since this can cause a socket exhaustion (because the connections will stay open).
(see the docs for details)
Go for a static HttpClient (or use the IHttpClientFactory if you're project is .net Core).
I can't test your code since I'm not able to access this api.
But give it a try using a cleaner approach:
// static HttpClient
private static readonly HttpClient _HttpClient = new HttpClient();
// Can be used to set the baseUrl of the HttpClient from outside
public static void SetBaseUrl(Uri baseUrl)
{
_HttpClient.BaseAddress = baseUrl;
}
public static async Task<string> GetDetailsfromThirdParty(string kszstrng)
{
string contentstring = string.Empty;
string baseURL = "https://abcde.kz.in/b2/vhsearch-all";
string prms = kszstrng; // input parameters to API, in JSON Format- this is JSON String.
try
{
// Be aware of which headers you wanna clean if using the static HttpClient
_HttpClient.DefaultRequestHeaders.Accept.Clear();
_HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_HttpClient.DefaultRequestHeaders.Add("key", "value");
_HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
byte[] messageBytes = Encoding.UTF8.GetBytes(prms);
var content = new ByteArrayContent(messageBytes);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _HttpClient.PostAsync(baseURL, content).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
contentstring = result;
}
}
catch (Exception ex)
{
// your exception handling
}
return contentstring;
}
Issue resolved. While forming the object to JSON String, there was an opening and closing angle brackets ([,]). Even though this is coming automatically while converting to JSON string, this was not accepted string at the vendor end. So I removed it and works perfectly. Thanks every one for the support.

Querying WebAPI - webapi Cannot deserialize the current JSON array

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;
}

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);
}

Consuming a Web API in Xamarin

I've created a Web API in ASP.NET that is hosted on a web server. This Web API accesses a table in SQL Server where I have a table called Products with Id, ProductName, Description and Price, I did the tests via Postman and it is working correctly, but when I try to consume the method to bring a specific product via Xamarin application, I get the following error message in break mode:
System.Net.Http.HttpRequestException: Timeout exceeded getting exception details
public class DataService
{
public async Task<List<Product>> GetProductAsync(string ProductName)
{
using (var client = new HttpClient())
{
string url = "http://ProductsAPI.hostname.com/api";
try
{
var uri = url + "/" + ProductName.ToString();
HttpResponseMessage response = await client.GetAsync(uri);
var ProductJsonString = awaitresponse.Content.ReadAsStringAsync();
var Product = JsonConvert.DeserializeObject<List<Product>>(ProductJsonString);
return Product;
}
catch (Exception ex)
{
throw ex;
}
}
}
}
Here's what I've used in the past:
public string GetAPIJsonAsync(string URL)
{
using (WebClient wc = new WebClient())
{
return wc.DownloadString(URL);
}
}
This would return the raw JSON to whoever called it, and I would then convert it to the desirable object.
If you increase the timeout of the HttpClient, does it return more information?
Also, try Refit It does all the work for you, including deserializing into json.
This Works Perfectly for me
public static async Task<List<BranchMasterModel>> GetBranchList(int city)
{
var client = new HttpClient(new NativeMessageHandler());
client.BaseAddress = new Uri(UrlAdd);//("http://192.168.101.119:8475/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "AuthToken"));
var result = await client.GetAsync("api/Master/V2/Branch/"+city);
string branch = await result.Content.ReadAsStringAsync();
var branches = JsonConvert.DeserializeObject<List<BranchMasterModel>>(branch);
return branches;
}

PutAsync doesn't send request to web api, but fiddler works fine

I have been trying to figure out what is going wrong for a few hours now and i just can't find what is going wrong.
Via the Mvc application the put method doesn't get hit, the request doesn't happen. But when i test it in fiddler the PutMethod in the api works.
Hopefully someone can clear things up for me.
Also pointers for a better structure or some good documentation are welcome .
public void UpdateWerknemerCompetentieDetail(int wnID, int WNC, CompetentieWerknemerDetail detail)
{
using (HttpClient client = new HttpClient())
{
string token = (string)HttpContext.Current.Session["token"];
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var wn = GetWerknemerById(wnID);
//var wnc = wn.CompetentiesWerknemer.Select(c => c).Where(c => c.ID == WNC).FirstOrDefault();
detail.CompetentieWerknemerID = WNC;
//wnc.CompetentieWerknemerDetail = detail;
var url = String.Format(URL + "PutDetails?id=" + WNC);
var json = JsonConvert.SerializeObject(detail, new JsonSerializerSettings()
{
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});
var response = client.PutAsync(url, new StringContent(json, Encoding.UTF8, "application/json"));
}
}
The above code is my service that should make the request to the api.
Here is the web api IHttpActionResult method (the put method).
[Route("PutDetails")]
[HttpPut]
public IHttpActionResult PutWerknemerCompetentieDetails(int id, [FromBody]CompetentieWerknemerDetail cwn)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != cwn.CompetentieWerknemerID)
{
return BadRequest();
}
//_db.Entry(cwn).State = EntityState.Modified;
try
{
_db.CompetentieWerknemerDetail.Add(cwn);
_db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!WerknemerExist(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
HttpClient.PutAsync is an asynchronous API, it returns a Task<HttpResponseMessage> which represents an operation which will complete in the future, which you need to await. You're wrapping your HttpClient inside a using statement, which means that right after you trigger the asynchronous PUT, you're disposing the client which causes a race condition with the request and the disposal of your object, and is probably the reason you're not seeing the request fire.
You have two choices. Either make the method async Task and await inside it:
public async Task UpdateWerknemerCompetentieDetailAsync(
int wnID, int WNC, CompetentieWerknemerDetail detail)
{
using (HttpClient client = new HttpClient())
{
string token = (string)HttpContext.Current.Session["token"];
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var wn = GetWerknemerById(wnID);
//var wnc = wn.CompetentiesWerknemer.Select(c => c)
// .Where(c => c.ID == WNC)
// .FirstOrDefault();
detail.CompetentieWerknemerID = WNC;
//wnc.CompetentieWerknemerDetail = detail;
var url = String.Format(URL + "PutDetails?id=" + WNC);
var json = JsonConvert.SerializeObject(detail, new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
var response = await client.PutAsync(
url, new StringContent(json, Encoding.UTF8, "application/json"));
}
}
Or use a synchronous API, such as exposed by WebClient.

Categories