Getting response body on failed request with HttpRequestException - c#

I am trying to log failed requests from my HttpRequestException.
My server returns error code and additional JSON payload at the response body. I need to access that JSON. How do I read the response body in case of an errored request? I know that the actual response is not null. It's an API and I confirm that it returns JSON payload with 4xx status codes, giving detailed insight about the error.
How do I access it? Here is my code:
using (var httpClient = new HttpClient())
{
try
{
string resultString = await httpClient.GetStringAsync(endpoint);
var result = JsonConvert.DeserializeObject<...>(resultString);
return result;
}
catch (HttpRequestException ex)
{
throw ex;
}
}
I am trying to get data in throw ex line, but I couldn't find a way.

Exactly as #Frédéric suggested, if you use GetAsync method you'll get the proper HttpResponseMessage object which give more information about the response. To get details when error occurs you can desearlize the errors to an Exception or your custom exception object from Response content like below:
public static Exception CreateExceptionFromResponseErrors(HttpResponseMessage response)
{
var httpErrorObject = response.Content.ReadAsStringAsync().Result;
// Create an anonymous object to use as the template for deserialization:
var anonymousErrorObject =
new { message = "", ModelState = new Dictionary<string, string[]>() };
// Deserialize:
var deserializedErrorObject =
JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);
// Now wrap into an exception which best fullfills the needs of your application:
var ex = new Exception();
// Sometimes, there may be Model Errors:
if (deserializedErrorObject.ModelState != null)
{
var errors =
deserializedErrorObject.ModelState
.Select(kvp => string.Join(". ", kvp.Value));
for (int i = 0; i < errors.Count(); i++)
{
// Wrap the errors up into the base Exception.Data Dictionary:
ex.Data.Add(i, errors.ElementAt(i));
}
}
// Othertimes, there may not be Model Errors:
else
{
var error =
JsonConvert.DeserializeObject<Dictionary<string, string>>(httpErrorObject);
foreach (var kvp in error)
{
// Wrap the errors up into the base Exception.Data Dictionary:
ex.Data.Add(kvp.Key, kvp.Value);
}
}
return ex;
}
Usage:
using (var client = new HttpClient())
{
var response =
await client.GetAsync("http://localhost:51137/api/Account/Register");
if (!response.IsSuccessStatusCode)
{
// Unwrap the response and throw as an Api Exception:
var ex = CreateExceptionFromResponseErrors(response);
throw ex;
}
}
Here's the source article detailing more about handling the HttpResponseMessage and it's content.

Use GetAsync instead of GetStringAsync. GetAsync will not throw an exception and will allow you to access response content, status code and any other header you may require.
See this page for more.

Essentially what #RyanGunn posted but implemented in your code.
You should be able to ReadAsStringAsync from the resultString.Content
I am working on an SDK that employs similar code except we use a switch statement to check for various HttpStatusCodes that we intend to return before the DeserializeObject line.
using (var httpClient = new HttpClient())
{
try
{
string resultString = await httpClient.GetStringAsync(endpoint);
var result = JsonConvert.DeserializeObject<...>(resultString.Content.ReadAsStringAsync().Result);
return result;
}
catch (HttpRequestException ex)
{
throw ex;
}
}

Related

blazor httpClient.PostJsonAsync status code exception

When using the HTTP in Blazor client side it only works if the response is a success response, but if it is not found or bad response it gives exception and doesn't complete the code.
I want to parse the object I send in the response even if the request is not successful I mean 400 or 404, I send an object with error list so I need to get it.
It gives me error in the console that the request is not successful.
If I make the request to be (OK) then it works, but I need to send 400 status with the object "RequestResult" how I could manage this?
var result = await _httpClient.PostJsonAsync<RequestResult>("api/account/auth", authModel);
if (result.Successful)
{
await _localStorage.SetItemAsync("Token", authModel.SecurityToken);
AuthData.AuthToken= result.Token;
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(result.Token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);
return result;
}
return result;
and this is the controller code when i change BadRequest to Ok it work
public IActionResult Post([FromBody]AuthModel model)
{
var res = _authManager.SignInUser(model);
if (!res.Successful)
{
return BadRequest(new RequestResult { Successful = false, Errors = new List<string>() { res?.errors } });
}
return Ok(new RequestResult { Successful = true ,Token=res.access_token});
}
PostJsonAsync works this way. It will throw an exception if the content cannot be parsed. Here is a suggested workaround:
Required using statement: using System.Text.Json;
var response = await _httpClient.PostAsync("api/account/auth", authModel);
if (response.IsSuccessStatusCode)
{
//parse result as following
using (var sr = await response.Content.ReadAsStreamAsync())
{
var parsedResult = JsonSerializer.DeserializeAsync<RecipeDetailDto>(sr);
}
}
else
{
//If the bad request content/body is a json object
//parse error content
using (var sr = await response.Content.ReadAsStreamAsync())
{
//If the bad request content is a json
//var parsedErrorResult = JsonSerializer.DeserializeAsync<yourErroObjset>(sr);
}
//OR if the content is string
var errorResult = await response.Content.ReadAsString();
}
Did not testet it, but it should give you the context how to approach this.

Catch External API Error Message as in Postman Body

I am using the following code to complete an External API call.
WebResponse response = request.GetResponse();
string JSONResult = null;
var data = response.GetResponseStream();
using (var reader = new StreamReader(data))
{
JSONResult = reader.ReadToEnd();
}
When there is an exception on the external API, the request.GetResponse throws an error. However, I cannot get the message that is displayed, e.g.
{
"Message": "No HTTP resource was found that matches the request URI '<site>/Foo'.",
"MessageDetail": "No type was found that matches the controller named 'Foo'."
}
Whilst this is displayed in Fiddler and Postman, I cannot get this message anywhere when it is thrown as an exception.
How do I get this specific details when an error on an external API's call is made?
You need to catch the exception and then read the response stream of the exception. Reading the exception's response stream is the same as reading the response of the request. Here is how:
WebRequest request =
WebRequest.Create("http://...");
WebResponse response = null;
try
{
response = request.GetResponse();
}
catch (WebException webEx)
{
if (webEx.Response != null)
{
using (var errorResponse = (HttpWebResponse)webEx.Response)
{
using (var reader = new StreamReader(errorResponse.GetResponseStream()))
{
string error = reader.ReadToEnd();
// TODO: use JSON.net to parse this string
}
}
}
}
Do not put all your code inside the above try block because you are only try(ing) and catch(ing) the request.GetResponse(). The rest of your code needs to go outside that try catch block so you can catch the exceptions from that code separately.

RestSharp authentication in Xamarin

I've found one thread on the Xamarin forums about the same issue, but the guy didn't get any responses, so I'm guessing this is a rare issue related to Xamarin (Android).
The code snippet below works perfectly fine if I use valid credentials, but if I use wrong credentials, or if there is any other reason why the app can't authenticate, a WebException is thrown (400 at wrong credentials, 500 at server error etc.).
The problem is that I don't know how to handle the exception, it throws the exception when it goes into the Post() method...
private void Authenticate()
{
if (Credentials != null && client.Authenticator == null)
{
RestClient authClient = new RestClient(client.BaseUrl);
RestRequest authRequest = new RestRequest("/token", Method.POST);
UserCredentials userCred = Credentials as UserCredentials;
if (userCred != null)
{
authRequest.AddParameter("grant_type", "password");
authRequest.AddParameter("username", userCred.UserName);
authRequest.AddParameter("password", userCred.Password);
}
var response = authClient.Post<AccessTokenResponse>(authRequest);
response.EnsureSuccessStatusCode();
client.Authenticator = new TokenAuthenticator(response.Data.AccessToken);
}
}
Server responses in the range of 4xx and 5xx throw a WebException. You need to catch it, get the status code from WebException and manage the response.
try{
response = (HttpWebResponse)authClient.Post<AccessTokenResponse>(authRequest);
wRespStatusCode = response.StatusCode;
}
catch (WebException we)
{
wRespStatusCode = ((HttpWebResponse)we.Response).StatusCode;
// ...
}
If you need the numeric value of the HttpStatusCode just use:
int numericStatusCode = (int)wRespStatusCode ;
You use a try..catch block to catch the Exception, then add whatever error handling logic is appropriate.
try {
var response = authClient.Post<AccessTokenResponse>(authRequest);
response.EnsureSuccessStatusCode();
} catch (WebException ex) {
// something bad happened, add whatever logic is appropriate to notify the
// user, log the error, etc...
Console.Log(ex.Message);
}

How to deal with HTML error codes and timeout from HttpClient()

I'm using HttpClient to connect to a server (see simplified code below). I cant figure out how I would respond to HTML error codes (e.g. 403) and timeouts so I can report what the result is.
When I encounter a 403 error code an error pop-up occurs in Visual Studio. But I can figure out how I convert this into try in the code. i.e. is the name of the exception present in the error pop-up?
using System.Net.Http;
HttpClient client = new HttpClient();
var response = client.PostAsync(dutMacUrl, null).Result;
var result = response.Content.ReadAsStringAsync().Result;
you can use async/await feature to simplify your code and avoid using Result.
for example
public async Task<string> Foo(string uri)
{
var client = new HttpClient();
try
{
var response = await client.PostAsync(uri, null);
}
catch (Exception ex)
{
//here you handle exceptions
}
// use this if (response.StatusCode != HttpStatusCode.OK) { do what you want }
// or this if (response.IsSuccessStatusCode) { do what you want }
var result = await response.Content.ReadAsStringAsync();
return result;
}
If you are using webAPI another option is to use IHttpActionResult
public object IHttpActionResult mymethod()
{
// instantiate your class or object
IEnumerable<yourClass> myobject = new IEnumerable<yourClass>();
// assuming you want to return a collection
try
{
// do stuff
// handle dto or map result back to object
return Ok(myobject)
}
catch(Exception e)
{
// return a bad request if the action fails
return BadRequest(e.Message)
}
}
This would allow you to make a call to your api endpoint and either return a successful response with the updated object or return a bad request if the endpoint fails.

Using HttpResponseMessage to view errors

I can use the following code which works fine to log in using my Web API. However, whensomething goes wrong and an error is returned, I don't know how to get at the contect of the HttpResponseMessage. If I just use the ReadAsStringAsync() method, I get the error in the string, but what type is it? If I know the type I can get the object.
HttpResponseMessage response = await client.PostAsJsonAsync("api/Login", loginObject);
if (response.IsSuccessStatusCode)
{
var _logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
}
else
{
// an error has occured, but what is the type to read?
var test = await response.Content.ReadAsStringAsync();
}
On the server it is returning;
BadRequest(ModelState).
Thanks for any help.
EDIT: I have since resolved the issue like this;
var value = await response.Content.ReadAsStringAsync();
var obj = new { message = "", ModelState = new Dictionary<string, string[]>() };
var x = JsonConvert.DeserializeAnonymousType(value, obj);
The result returned back is an JSON object with a "Message" and a "ModelState" properties.
The "ModelState" state value is an object, whose properties are arrays of strings. The property list of "ModelState" varies from time to time depending on which property is invalid.
Hence, to get a strong-type returned response, why don't you manipulate the ModelState yourself on the server side, and then pass the object to the BadRequest() method
Here is just grabbing raw json in text of error message...
if (!response.IsSuccessStatusCode)
{
dynamic responseForInvalidStatusCode = response.Content.ReadAsAsync<dynamic>();
Newtonsoft.Json.Linq.JContainer msg = responseForInvalidStatusCode.Result;
result = msg.ToString();
}
Try IOStreamReader. This is vb.net, but that's not too hard to convert:
IOStreamReader = New IO.StreamReader(Response.GetResponseStream)
RespStr = IOStreamReader.ReadToEnd
Or
Dim HttpReq As Net.HttpWebRequest = Nothing
Dim HttpStatus As Net.HttpStatusCode = Nothing
HttpResp = CType(HttpReq.GetResponse(), Net.HttpWebResponse)
'verify the response
HttpStatus = HttpResp.StatusCode
try following :
try
{
HttpResponseMessage response = await client.PostAsJsonAsync("api/Login", loginObject);
response.EnsureSuccessStatusCode();
var _logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
return _logonResponse;
}
catch (Exception ex)
{
throw ex;
}

Categories

Resources