Calling API with HttpClient catch BadRequest - c#

I'd like to call a Web API in .NET 6 with httpclient. The code works fine when I return OK(result), I get the right result. The problem is when I return a BadRequest, I'd like to have access to the properties EN, FR, NL. In the HttpRequestException, I just receive the message "500 internal exception error", not the properties EN, FR, NL.
How can I do to get these values ?
[HttpPost(nameof(Testing), Name = "Testing")]
public async Task<ActionResult<MyResponseDto>> Testing(ParameterDto parameter)
{
var res = new MyResponseDto
{
//Few properties her
};
//return Ok(res);
return BadRequest(new { FR = "My Error FR", NL = "My Error NL", EN = "My Error EN" });
}
I call web api from console application doe testing purpose with this code :
in program.cs
var result = await Api.Testing();
public static class Api
{
public static async Task<string> Testing()
{
string response = await HttpRequests.Post(
"/api/Testing",
new { /* few parameter here */ });
var result = JsonConvert.DeserializeObject<MyResponseDto>(response);
}
}
public static class MyHttpRequests
{
private static readonly HttpClient client = new HttpClient();
private const string url = "myURL";
public static async Task<string> Post(string entryPoint, Object dto)
{
string url = $"{url}{entryPoint}";
string dto = JsonConvert.SerializeObject(dto);
HttpContent httpContent = new StringContent(dto, Encoding.UTF8, "application/json");
try
{
using(HttpResponseMessage response = await client.PostAsync(url, httpContent))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
}
catch(HttpRequestException e)
{
Console.WriteLine($"Message :{e.Message} ");
}
return await Task.FromResult(string.Empty);
}
}

This line is throwing the exception:
response.EnsureSuccessStatusCode();
Without it, you would continue to return the response body which will contain a json representation of your error variables.
So instead of throwing an exception like this, you can manually check the response status code and parse the json. EG:
public static async Task<string> Post(string entryPoint, Object dto)
{
string url = $"{url}{entryPoint}";
string dto = JsonConvert.SerializeObject(dto);
HttpContent httpContent = new StringContent(dto, Encoding.UTF8, "application/json");
using(HttpResponseMessage response = await client.PostAsync(url, httpContent))
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
return responseBody;
Console.WriteLine($"Error: {responseBody}");
}
return await Task.FromResult(string.Empty);
}

Related

Unhandled Exception in AppDelegate when trying to get token for login (iOS)

Basically I get
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object occurred
when I try to get the token from the restservice.
when it gets to
public async Task<T> PostResponseLogin<T>(string weburl, FormUrlEncodedContent content) where T : class
{
var response = await client.PostAsync(weburl, content);
var jsonResult = response.Content.ReadAsStringAsync().Result;
var responseObject = JsonConvert.DeserializeObject<T>(jsonResult);
return responseObject;
}
on the return respondObject; it goes to the appDelegate and throws the exception.
-I'm currently in the learning process of C#/Xamarin, so if I have made a simple mistake that you notice, that will be why. Thanks for your help.
I've added MainPage = new NavigationPage(new LoginPage()); so I can navigate between pages, that is working.
edit: It was suggested that this might be a possible duplicate of another ticket with an unhandled exception. While it did help me to understand that error better, it's still more specific than that.
public class RestService
{
HttpClient client;
string grant_type = "password";
public RestService()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded' "));
}
public async Task<Token> Login(User user)
{
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("grant_type", grant_type));
postData.Add(new KeyValuePair<string, string>("Email", user.Email));
postData.Add(new KeyValuePair<string, string>("Password", user.Password));
var content = new FormUrlEncodedContent(postData);
var weburl = "https://blahblah.com/auth/login";
var response = await PostResponseLogin<Token>(weburl, content);
DateTime dt = new DateTime();
dt = DateTime.Today;
response.expireDate = dt.AddSeconds(response.expireIn);
return response;
}
public async Task<T> PostResponseLogin<T>(string weburl, FormUrlEncodedContent content) where T : class
{
var response = await client.PostAsync(weburl, content);
var jsonResult = response.Content.ReadAsStringAsync().Result;
var responseObject = JsonConvert.DeserializeObject<T>(jsonResult);
return responseObject;
}
public async Task<T> PostResponse<T>(string weburl, string jsonstring) where T : class
{
var Token = App.TokenDatabase.GetToken();
string ContentType = "application/json";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token.accessToken);
try
{
var Result = await client.PostAsync(weburl, new StringContent(jsonstring, Encoding.UTF8, ContentType));
if (Result.StatusCode == System.Net.HttpStatusCode.OK)
{
var JsonResult = Result.Content.ReadAsStringAsync().Result;
try
{
var ContentResp = JsonConvert.DeserializeObject<T>(JsonResult);
return ContentResp;
}
catch { return null; }
}
}
catch { return null; }
return null;
}
public async Task<T> GetResponse<T>(string weburl) where T : class
{
var Token = App.TokenDatabase.GetToken();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token.accessToken);
try
{
var response = await client.GetAsync(weburl);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var JsonResult = response.Content.ReadAsStringAsync().Result;
try
{
var ContentResp = JsonConvert.DeserializeObject<T>(JsonResult);
return ContentResp;
}
catch
{
return null;
}
}
}
catch
{
return null;
}
return null;
}
}

How do I use HttpClient PostAsync parameters properly?

So I am working on writing an extension class for my project using HttpClient since I am moving over from HttpWebRequest.
When doing the POST request, how do I send a normal string as a parameter? No json or anything just a simple string.
And this is what it looks like so far.
static class HttpClientExtension
{
static HttpClient client = new HttpClient();
public static string GetHttpResponse(string URL)
{
string fail = "Fail";
client.BaseAddress = new Uri(URL);
HttpResponseMessage Response = client.GetAsync(URL).GetAwaiter().GetResult();
if (Response.IsSuccessStatusCode)
return Response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
else
return fail;
}
public static string PostRequest(string URI, string PostParams)
{
client.PostAsync(URI, new StringContent(PostParams));
HttpResponseMessage response = client.GetAsync(URI).GetAwaiter().GetResult();
string content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return content;
}
}
If you look at this like
client.PostAsync(URI, new StringContent(PostParams));
You can see that I just tried creating new StringContent and passing a string into it and the response returned 404 page not found.
How do I properly use Post.Async(); do I send a string or byte array? Because with HttpWebRequest you would do it like this
public static void SetPost(this HttpWebRequest request, string postdata)
{
request.Method = "POST";
byte[] bytes = Encoding.UTF8.GetBytes(postdata);
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
}
In the PostRequest the following is done..
client.PostAsync(URI, new StringContent(PostParams));
HttpResponseMessage response = client.GetAsync(URI).GetAwaiter().GetResult();
Which does not capture the response of the POST.
Refactor to
public static string PostRequest(string URI, string PostParams) {
var response = client.PostAsync(URI, new StringContent(PostParams)).GetAwaiter().GetResult();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return content;
}
HttpClient is primarily meant to be used async so consider refactoring to
public static async Task<string> PostRequestAsync(string URI, string PostParams) {
var response = await client.PostAsync(URI, new StringContent(PostParams));
var content = await response.Content.ReadAsStringAsync();
return content;
}
You need prepare object and then you will serialize the object using Newtonsoft.Json. After that you will prepare byte content from the buffer. We are using api url api/auth/login and it is not full api url as we used dependency injection and configure base address in startup, see the second code.
public async void Login(string username, string password)
{
LoginDTO login = new LoginDTO();
login.Email = username;
login.Password = password;
var myContent = JsonConvert.SerializeObject(login);
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync("api/auth/login", byteContent);
var contents = await response.Content.ReadAsStringAsync();
}
services.AddHttpClient<IAuthService, AuthService>(client =>
{
client.BaseAddress = new Uri("https://localhost:44354/");
});
.NET 5 Solution
In .NET 5, There is new class JsonContent and you can implement this easily
LoginDTO login = new LoginDTO();
login.Email = username;
login.Password = password;
JsonContent content = JsonContent.Create(login);
var url = "http://...";
HttpResponseMessage response = await httpClient.PostAsync(url, content);
I have worked the following (using the package Ngonzalez.ImageProcessorCore).
Query (ASP.NET Core 2 Controller):
async Task<byte[]> CreateImage(IFormFile file)
{
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
var image = new Image(memoryStream);
var height = image.Height < 150 ? image.Height : 150;
image.Resize((int)(image.Width * height / image.Height), height).Save(memoryStream);
return memoryStream.ToArray();
}
}
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> ImageAdd(ImageAddVm vm)
{
byte[] image = null;
if (vm.File != null && vm.File.Length > 0)
image = await CreateImage(vm.File);
if (image != null)
{
var json = JsonConvert.SerializeObject(new { vm.ObjectId, image });
var content = new StringContent(json, Encoding.UTF8, "application/json");
var client= new HttpClient();
await client.PostAsync($"{ApiUrl}/SaveImage", content);
}
return RedirectToAction("ReturnAction");
}
Api (ASP.NET Core 2 Controller):
public class ObjectImage
{
public int ObjectId { get; set; }
public byte[] Image { get; set; }
}
[HttpPost("SaveImage")]
public void SaveImage([FromBody]object content)
{
var obj = JsonConvert.DeserializeObject<ObjectImage>(content.ToString());
_db.Images.Find(obj.ObjectId).Image = obj.Image;
_db.SaveChanges();
}

Mock returns null value when ReturnResult is a custom object but works as expected when it is a primitive type bool

I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.

WebAPI: using async methods in business logic

The aim is to make controller that uses async method in my custom service.
Controller:
[Route("api/data/summary")]
[HttpGet]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects();
return Ok(result);
}
Service:
public static async Task<IEnumerable<DataObject>> GetDataObjects()
{
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri(path);
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess);
var content = await response.Content.ReadAsStringAsync();
var result = MakeEntities(content); // some logic
return result;
}
}
But I came across with the problem that controller's action returns empty result before service actually finished processing data.
Could you please advice how to implement it correctly?
Your code is OK and controller doesn't seem to return a value before GetDataObjects returns value.
Except for the situations below:
MakeEntities uses some asynchronous operation and you don't await it inside MakeEntities. So MakeEntities return task.
Exception rises while your code is running. Make sure GetDataObjects and MakeEntities code works fine.
The aim is to make controller that uses async method in my custom service.
Controller:
[HttpGet]
[Route("api/data/summary")]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects().ConfigureAwait(false);
return Ok(result);
}
Service:
public static async Task<ResponseEntity> GetDataObjects()
{
ResponseEntity response = new ResponseEntity();
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.BaseAddress = new Uri(path);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<ResponseEntity>(responseString);
return response;
}
}

Using HttpResponseMessage.EnsureSuccessStatusCode()

Are the following two methods, getData1Async() and getData2Async() are essentially the same? If so why don't I need EnsureSuccessStatusCode() in getData2Async() method?
class Program
{
static void Main(string[] args)
{
try
{
string uri = "https://www.blahblah.com/getdata";
Task<string> x = getData1Async(uri);
System.Diagnostics.Debug.WriteLine(x.Result);
Task<string> y = getData2Async(uri);
System.Diagnostics.Debug.WriteLine(y.Result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static async Task<string> getData1Async(string uri)
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
static async Task<string> getData2Async(string uri)
{
var httpClient = new HttpClient();
return await httpClient.GetStringAsync(uri);
}
}
getData1Async - here you are getting the object of type HttpResponseMessage and if you don't ensure that response has completed successfully and call response.Content.Read..., the answer will be indeterministic.
getData2Async - directly calls httpClient itself to get the string which internally makes sure that it only returns when data has been received.

Categories