I've got an Async method that calls an API to retrieve a JSON via HttpClient with the following code block-
//Assemble the url
string url = "https:someapi.com";
//Call API
var http = new HttpClient();
var response = new HttpResponseMessage();
try
{
response = await http.GetAsync(url);
}
catch (HttpRequestException exception)
{
//The server name or address could not be resolved
var dialog = new MessageDialog("The server name or address could not be resolved!");
dialog.Title = "API Response";
dialog.Commands.Add(new UICommand { Label = "Ok", Id = 0 });
var res = await dialog.ShowAsync();
if ((int) res.Id == 0)
{
exception.ExceptionHandled = true;// Cant' do this!
}
}
return result;
This is for an app development. I was trying to make to app more robust so that while there is no internet or data connection the app should should return that it can't call to the API service and show an error rather than crashing. But I just can't find an way to set the ExceptionHandled property to true. Is there a better way to do this?
P.S. The app crashes and debugger breaks when Ok button is clicked
You can use the IsSuccessStatusCode property from HttpClient to validate if it is a successful http response. Instead of catching a HttpRequestException, you can handle the failure in the else statement.
try
{
response = await http.GetAsync(url);
if(response.IsSuccessStatusCode)
{
//handle success
}
else
{
//handle failure
}
}
finally
{
http.Dispose();
}
Furthermore, a rule of thumb is when you use an IDisposable object, you need to wrap it inside a using statement or handle the dispose in the finally block
using(var http = HttpClient())
{
try
{
response = await http.GetAsync(url);
if(response.IsSuccessStatusCode)
{
//handle success
}
else
{
//handle failure
}
}
}
Related
I have a C# function that carries out some validation on the front end of Blazor WASM. I have noticed something weird that I can't understand but essentially if my Http Status code is set to a 2XX.. then my wait response.WriteStringAsync(ex.Message); shows on my Blazor front End
If I have my status code set as 4XX or 5XX it doesn't show my hard coded exception messages but a general response such as net_status_code_error 404
try {
switch (name) {
case "Invalid":
throw new SpecialException("Validation exception.");
case "Bad":
throw new Exception("System exception.");
}
var url = $"{UrlRoot}/func?name={name}";
var req2 = new HttpRequestMessage(HttpMethod.Post, url){};
req2.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken.Token);
using var httpClient = new HttpClient();
var resp2 = await httpClient.SendAsync(req2);
var responseBody = await resp2.Content.ReadAsStringAsync();
if (!resp2.IsSuccessStatusCode)
{
throw new Exception($"{resp2.StatusCode}: {responseBody}");
}
}
catch (SpecialException Ex)
{
logger.LogWarning(Ex, name);
var response = req.CreateResponse(HttpStatusCode.BadRequest);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
await response.WriteStringAsync(Ex.Message);
return response;
}
Now at the moment this just shows the Bad Request generic error but not my ex.Message on the front end? I don't understand why it works with 2XX but not other errors
I send a request to the API and sometimes receive the response with an HTTP 429 status code (TooManyRequests).
On average, for 10 requests, 2 will return 429 response and the remaining 8 return the correct value.
It also happened to me that when it was the first request (so there is no option for TooManyRequests)
public static List<ResponseObject> GetProductsFromRestAPI(int[] ProductIdArray )
{
List<ResponseObject> products = new List<ResponseObject>();
string action;
for (int i = 0; i < ProductIdArray.Length; i++)
{
action = "products/" + ProductIdArray[i].ToString();
client = AddHeadersToClient(action, new RestClient("https://api.usedbythiscode.com/")); //required by this API.
var currentProduct = RequestsLib.GetProduct(client, action);
products.Add(currentProduct);
}
return products;
}
public static Product GetProduct(RestClient restClient, string action) //todo test this for bugs
{
var result = new Product();
var request = new RestRequest(action, Method.GET);
var response = SendRequest(restClient, request);//Here I sometimes get response with 429.
//.. Other code
return result;
}
public static async Task<IRestResponse> SendRequest(RestClient restClient, RestRequest request)
{
return await restClient.ExecuteGetAsync(request);
}
Temporarily resolved it by sending another request with do while loop and usually second request return right answer.
do
{
SendRequest(restClient, request);
}
while (StatusCode != 200);
Where could the cause of the error lie?
Is it possible that I have unclosed requests?
Is creating multiple RestSharp clients a good practice?
EDIT:
The problem was on the server side. All I had to do was report the bug to the admins who provided the API. Thank you for help.
429 is Too Many Requests. Most APIs have some kind of rate-limiting in place so that a single client can't take down their server with too many requests.
The proper response for 429 is to retry. I recommend using Polly for retry logic, but be aware that HandleTransientHttpError doesn't consider 429 a transient error.
I agree with #mason, you should use async method with Task<> and await response Here is the part of login side of my mobileApp-project in Xamarin. You may want to see how to use async with Task<> easily.
public async Task<BSUser> ValidateUser(string userName, string password)
{
string url = Xamarin.Essentials.Preferences.Get(Constants.URL_KEY, "") + "/api/Validateuser";
HttpClient _Client = new HttpClient();
var data = new Dictionary<string, string>
{
{"userName", userName},
{"password", password}
};
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage httpResponse = await _Client.PostAsync(url, content);
if (httpResponse.IsSuccessStatusCode)
{
try {
var responseData = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject(responseData).ToString();
UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(result);
BSUser value = new BSUser();
value.UserName = userInfo.userCode;
return value;
}
catch (Java.Net.SocketException e)
{
Console.WriteLine("Hata", e);
return null;
}
}
else
{
return null;
}
}
catch (SystemException)
{
return null;
}
}
I'm having a problem with the .NET HttpClient class. Sometimes the snippet below throws a TaskCanceledException, and i can't debug this because is random (I had the bad luck of Apple reject my Xamarin app for that). Can someone explain to me the reason for this exception?
public static HttpResultModel RecoveryPassword(string email)
{
HttpClient httpClient = new HttpClient();
try
{
var url = String.Format(Constants.SERVER_ADDRESS + "/user/forgotPassword/{0}/", email);
var request = new HttpRequestMessage(new HttpMethod("POST"), url)
{
Content = new StringContent(email, Encoding.UTF8, "application/json"),
};
//to be more specific, this line throws the exception
var result = httpClient.SendAsync(request).Result;
string message = result.Content.ReadAsStringAsync().Result;
if (result.IsSuccessStatusCode)
{
var response = JsonConvert.DeserializeObject<HttpResultModel>(message);
response.OperationSuccess = true;
return response;
}
else
{
var response = JsonConvert.DeserializeObject<HttpResultModel>(message);
response.OperationSuccess = false;
return response;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
This is due to either one of the two reasons:
A server disconnection
a timeout by the Http client. The default for HttpClient is 100 seconds.
You can set this to an infinite timespan.
httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;
each request can be then be set to specific timeouts if needed, as the HttpClient
timeout is on a higher level
I found the request in asp.net core custom middleware can be only read once, and after that I have to manually set the request back to the Request.Body. Is this recommended way to read the request?
public async Task Invoke(HttpContext context)
{
var request = context.Request;
string xmlstring;
using (System.IO.MemoryStream m = new System.IO.MemoryStream())
{
try
{
if (request.Body.CanSeek == true) request.Body.Position = 0;
request.Body.CopyTo(m);
xmlstring = System.Text.Encoding.UTF8.GetString(m.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(xmlstring));
await _next.Invoke(context);
}
I tried to "copy" the stream to another one, but doesn't help. I doubt all custom middleware has this step of setting request body back, so here to ask if I do it in proper way.
The correct steps should be:
enablerewind
read from body
do rewind
Please note: without set body back to originalRequestBody, it will only work once, and if you try to call the same web api again, it will fail.
Please see below sample code
public async Task Invoke(HttpContext context)
{
var originalRequestBody = context.Request.Body;
context.Request.EnableRewind();
try
{
using (System.IO.MemoryStream m = new MemoryStream())
{
context.Request.Body.CopyTo(m);
var s = System.Text.Encoding.UTF8.GetString(m.ToArray());
}
//this line will rewind the request body, so it could be read again
context.Request.Body.Position = 0;
await _next(context);
}
catch (Exception ex)
{
}
finally
{
//important, otherwise, even current request will succeed, following request will fail
context.Request.Body = originalRequestBody;
}
}
Here is the code to create a client and POST an object. It is my understanding that setting AllowAutoRedirect = true would enable the ability for the client to follow the redirect then do a GET and I would be able to deserialize said object. My testing has proven unsuccessful so far. Is there something that I may have overlooked?
Web API endpoint:
public HttpResponseMessage Post([FromBody] Contact contact) {
try {
// Add user
...
var msg = Request.CreateResponse(HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + "/" + customer.Person.PersonID);
return msg;
} catch (ValidationException tve) {
var apiError = new ApiResponseMessage { Message = "Invalid contact" };
foreach (var message in FilterErrors(tve.Messages)) {
if (message.Contains("required", StringComparison.OrdinalIgnoreCase)) {
apiError.Errors.Add(new ApiErrorMessage {
Code = ErrorCode.RequiredPropertyNotProvided,
Message = message
});
} else {
apiError.Errors.Add(new ApiErrorMessage {
Code = ErrorCode.PropertyNotValid,
Message = message
});
}
}
return Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
Client code:
public Contact Post(Contact contact)
{
try
{
var handler = new HttpClientHandler { AllowAutoRedirect = true};
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri(APIServer);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-ApiKey", APIKey.ToString());
var response = client.PostAsJsonAsync("v1/Contacts", contact).Result;
if (response.IsSuccessStatusCode)
{
Log.DebugFormat("Post v1/Contacts => {0} ({1})", response.StatusCode, response.ReasonPhrase);
var result = JsonConvert.DeserializeObject<Contact>(response.Content.ReadAsStringAsync().Result);
// This object is null
}
else
{
Log.ErrorFormat("Post v1/Contacts => {0} ({1})", response.StatusCode, response.ReasonPhrase);
var result = JsonConvert.DeserializeObject<ApiMessageResponse>(
response.Content.ReadAsStringAsync().Result);
}
}
}
catch (Exception exception)
{
Log.Error(exception);
}
return null;
}
Wireshark logs.
POST /v1/Contacts HTTP/1.1 (application/json)
HTTP/1.1 201 Created
Location: http://api01.example.com/v1/Contacts/10135052
and that's it, no GET (http://api01.example.com/v1/Contacts/10135052) after as far as I can tell.
From the log trace you added, it looks like the response from the POST is a 201 (Created); there is no redirect. The response does contain a URL (either in a header or the body, hard to tell) but it will not mean anything special. Your client will need to parse the URL itself and issue the subsequent GET request explicitly.