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.
Related
I'm quite new to .Net Core Web API and have spent a few days looking for an answer but couldn't find exactly what I am looking for. What I want to know is how to retrieve the custom object that is pass from an API action back to the client via an ActionResult (BadRequest(), NotFound()...etc.)
So I created a new Web API project in VS2019 and updated the default Get method of the WeatherForecastController like this:
[HttpGet]
public ActionResult<IEnumerable<WeatherForecast>> Get()
{
return NotFound(new { Message = "Could not find data", Suggestion = "Refine your search" });
}
When testing in Postman, I can get the expected output of Status = 404 and body is
{
"message": "Could not find data",
"suggestion": "Refine your search"
}
But in the client project, I just don't know how I can retrieve that custom error object.
My client code is like this:
public async Task OnGet()
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44377/");
WeatherForcasts = await httpClient.GetFromJsonAsync<WeatherForcast[]>("weatherforecast");
}
catch (HttpRequestException ex)
{
hasError = true;
}
}
I understand that if the API action does not return a success status code (such as 200) then this will raise an HttpRequestException. But I can't find a away to get that custom error object out from the HttpRequestException.
Any help will be very much appreciated!
Change your code to this:
public async Task OnGet()
{
using var client = new HttpClient();
var baseAddress ="https://localhost:44377");
client.BaseAddress = new Uri(baseAddress);
var response= await client.GetAsync(baseAddress);
var statusCode = response.StatusCode.ToString(); // shoud be "NotFound"
var stringData = await response.Content.ReadAsStringAsync();
var data= JsonConvert.DeserializeObject<object>(stringData);// should be
// "{{"message":"Could not find data","suggestion": "Refine your search"}}"
....
}
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.
I have a HttpPut API method that edits an object it is passed and saves into the database successfully. This works fine, however from my MVC application, the httpClient.PutAsync which I use to call my API Put method returns internal server error, even though the API Put method does not.
I am not sure what is going wrong, the API method works fine, but somehow the MVC HttpClient still gets an internal server error.
API PUT METHOD
[HttpPut]
public IActionResult Put([FromBody] School school)
{
try
{
var schoolExists = _schoolRepository.SchoolExists(school.Id);
if (!schoolExists) return NotFound();
if (!ModelState.IsValid) return BadRequest();
var schoolData = Mapper.Map<School, Data.School>(school);
var updatedClass = _schoolRepository.UpdateSchool(schoolData);
if (!updatedClass) return Json(GetHttpResponseMessage(HttpStatusCode.InternalServerError));
var route = CreatedAtRoute("GetSchool", school);
return route;
}
catch (Exception e)
{
return LogException(e);
}
}
The method above works fine and my changes are saved into the database and CreatedAtRouteResult object is returned from the API method.
MVC HTTPCLIENT
public async Task<T> PutObject(string path, T content, string accessToken)
{
using (var httpClient = new HttpClient())
{
try
{
SetBaseUri(httpClient, accessToken);
var serialisezContent = CreateHttpContent(content);
var httpResponse = await httpClient.PutAsync(path, serialisezContent);
if (httpResponse.StatusCode == HttpStatusCode.InternalServerError) throw new Exception("Problem accessing the api");
return JsonConvert.DeserializeObject<T>(GetResult(httpResponse));
}
catch (Exception ex)
{
throw ex;
}
}
}
The method above is where is issue is, this line var httpResponse = await httpClient.PutAsync(path, serialisezContent); returns internal server error still. I have the same implementation for my POST and that works just fine.
SETBASEURI()
private void SetBaseUri(HttpClient httpClient, string accessToken)
{
httpClient.BaseAddress = new Uri(BaseUri);
httpClient.DefaultRequestHeaders.Authorization =
_authenticationHeaderValueCreator.CreateAuthenticationHeaderValue("bearer", accessToken);
}
CreateHttpContent()
public ByteArrayContent CreateHttpContent<TParam>(TParam httpObject)
{
var content = JsonConvert.SerializeObject(httpObject);
var buffer = System.Text.Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return byteContent;
}
I bet your API Put method indeed returns HTTP 500 error with message No route matches the supplied values. You could check it in Fiddler.
And the problem is with following line:
var route = CreatedAtRoute("GetSchool", school);
CreatedAtRoute method takes a route name as first argument. I doubt that you have a route named GetSchool. It's rather an action name in the same controller. And CreatedAtRoute will not throw exception for unknown route, it will just return 500 error code.
To fix this problem, use CreatedAtAction method instead of CreatedAtRoute:
var route = CreatedAtAction("GetSchool", school);
I think the problem is that the result from the API is failing to serialize. Try to manually serialize the result yourself in a unit-test and see where it fails.
When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time
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;
}
}