HttpClient PostAsync does not return - c#

I've seen a lot of question about this, and all points to me using ConfigureAwait(false), but even after doing so, it still doesn't returned any response. When I run the debugger, the code stops at the PostAsync and does not continue with my code. Am I doing something wrong here? Does it have to do with me calling an API via HTTPS?
Here's the code:
public async static Task<PaymentModel> AddAsync(Card card)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:", "hidden"))));
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
var cardJson = JsonConvert.SerializeObject(card);
var postRequest = new StringContent(cardJson, Encoding.UTF8, "application/json");
var request = await client.PostAsync(new Uri("https://sample-3rd-party-api/api/endpoint/here"), postRequest).ConfigureAwait(false);
var content = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
}
EDIT:
In response to the comments below, the code is contained from a method AddAsync(Card card) called from a button click with a handler:
public async void OnExecute(object sender, EventArgs args)
{
//some code here
payment = await PaymentModel.AddAsync(card).ConfigureAwait(false);
}
EDIT 2:
I tried pinging the API, but it returns a request timed out, but when I tried it using Postman, it's doing fine (the API is just a Sandbox which is open for all, so it's okay to share this):
EDIT 3:
I think the problem lies with where I don't have an SSL certificate to access the API. I have a PHP server that connects to the same API and I have to set SSL_VERIFYPEER to false just so I can access it (don't worry, I added a cacert now so its on true again). Can the same issue be happening here? If so, what can I do to create a valid certificate for my xamarin forms app

You can use this
var json = JsonConvert.SerializeObject(card);
using (var client = new HttpClient())
{
var t = await client.PostAsJsonAsync("https://sample-3rd-party-api/api/endpoint/here", json);
Response R =JsonConvert.DeserializeObject<Response>((JsonConvert.DeserializeObject(t.Content.ReadAsStringAsync().Result.ToString())).ToString());
}

What's most likely happening here is your OnExecute method has a return type of void instead of Task which prevents the UI thread from being able to await it. Try either changing that return type to Task or creating a new UI thread to perform this work. I wouldn't worry about the ping timing out as long as Postman works. Many public web servers disable their ping response.

Does it have to do with me calling an API via HTTPS?
As you are remaining in the same network and calling the same API from POSTMAN and .NET HTTP Client and only getting success with POSTMAN.So this issue gets cancelled.
Next
tried pinging the API, but it returns a request timed out
This is answered on top of mine.
Can you Please try setting the timeout option for HTTPClient while initializing.
client.Timeout = TimeSpan.FromSeconds(10);
and if still Problem persists please setup Fiddler and compare both the req sent from POstman and .NET client

So I think the problem is resolved now since I'm able to receive content from the request, what I did was simply follow the docs here: https://learn.microsoft.com/en-us/xamarin/cross-platform/app-fundamentals/transport-layer-security?tabs=windows
It looks like my settings are outdated in platform level.
Update the HttpClient implementation and SSL/TLS implementation
options to enable TLS 1.2 security.
Update the HttpClient Implementation option to enable TSL 1.2
security. (NSUrlSession (iOS 7.0+)

I was having the same issue and below trick fixed the issue.
Change your var request = await client.PostAsync(...); as below
var task = client.PostAsync(new Uri("https://sample-3rd-party-api/api/endpoint/here"), postRequest);
var request = task.GetAwaiter().GetResult();

Related

HttpClient requests not working for a particular domain in Azure

I have an Azure function that sends a request to a URL and sends back the response. This function kept failing with timeout error for URLs from a particular domain (confidential).
To debug this, I created a very minimal Azure function:
var content = string.Empty;
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
content = await response.Content.ReadAsByteArrayAsync();
}
return new OkObjectResult(content);
This code works fine in local. When I try using the deployed Azure function, it works for all the other domains I tried (ex: https://google.com) but it hits request timeout error for a particular domain after trying for about 90 seconds. The error happens at this particular line: _httpClient.GetAsync(url). Again, it works fine for this (confidential) domain in local.
I have tried deploying the Azure function to two completely different Azure service plans and regions. Same result. It doesn't work for URLs from the required domain. Works for URLs of other domains.
Error:
System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..
Update (solution):
I tried sending a request from Postman, copied the code from there for C# and deployed it to the Azure function and it is now working for the problematic domain. Something like below:
var client = new RestClient(url);
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
The key here is client.Timeout = -1, which seems to have fixed the problem.
Now, in my original code, I tried setting HttpClient's timeout to Timeout.InfiniteTimeSpan both in Startup configuration as well as at individual request level but it did not work.
services.AddHttpClient("AzureTestClient", options =>
{
options.Timeout = Timeout.InfiniteTimeSpan;
});
Am I setting the timeout wrong in the HttpClient solution?
If you are using a Consumption plan then maybe the confidential URL need to whitelist the whole Azure Data center. You can follow the guide here or consider upgrading the Consumption plan to a premium one and have a dedicated linked VNET.
Maybe your local machine is already linked to the domain/whitelisted so azure function operates from different range.
Another reason maybe the URL returns a different HttpStatusCode that is't Successful range (200-299) so it fails with "EnsureSuccessStatusCode" in the old code?
Normally for the http code initialization, I did something like that:
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddHttpClient("AzureTestClient",
options => { options.Timeout = Timeout.InfiniteTimeSpan; });
}
Then when I want to use it, I do like that in any other function and it worked:
var client = clientFactory.CreateClient("AzureTestClient");

Concurrency issue using HttpClient and HttpRequestMessage

My code looks something like this:
private readonly HttpClient _httpClient;
public SomeConstructor(HttpClient httpClient){
_httpClient = httpClient;
}
public void SomeMethod(string reqUrl, string payload){
var result = GetResponseStringAsync(reqUrl, payload).GetAwaiter().GetResult();
// do something with result
}
private async Task<string> GetResponseStringAsync(string reqUrl, string payload){
using (var req = new HttpRequestMessage("POST", reqUrl)){
using (var content = new StringContent(payload)){
// Attach content headers here (specific for each request)
req.Content = content;
// Attach request headers here (specific for each request)
// using req.Headers.TryAddWithoutValidation()
using (var response = await _httpClient.SendAsync(req))
{
return await response.Content.ReadAsStringAsync();
}
}
}
}
I need to send API requests that have different, signed headers per request, otherwise I will get back 401 (Unauthorized). That said, when I send a single request, I always got 200, indicating that the authorization headers are sent correctly. However, if I send multiple requests at once (say with concurrency level set to 10), only 1 request got 200 back, whereas the other 9 got 401s. If I click on these 9 links individually, however, I got 200s for every single one of them, as expected.
It seems to me that somehow, there's a concurrency issue that results in the proper headers not being attached to their corresponding requests, even when I create a new HttpRequestMessage for each request. HttpClient and HttpRequestMessage both are supposedly thread-safe but could someone provide an explanation as to why I'm still getting weird results when sending multiple requests at once?
Add:
I have something like this in my AppHost: Container.Register<ISomeConstructor>(x => new SomeConstructor(new HttpClient())); so I am sure I'm not accidentally modifying the
client anywhere else
Placing a lock around the HttpClient (just before the SendAsync call) makes it work and returns 200s 100% of the time, further convincing me that it's a concurrency issue
I'm deploying and running on Mono 6.8.0.105 -- could this be a Mono issue? I couldn't find any issues/bug reports on this though

App Center push works in Postman but not my C# application

I have a service in my C# application which uses App Center's api to push notifications. All my request come back with 401s (Unauthorised) yet, when I used the same details on postman i.e. content, header auth, owner_name and app_name it works successfully and sends the application.
This is very confusing and I am wondering if Postman handles some extra bits and pieces which I am missing out.
C# Push Notification Service
private async Task<bool> PostHttpRequest(PushNotificationModel pushNotificationModel)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("X-API-Token", _appCenterApiToken);
var url = "https://api.appcenter.ms/v0.1/apps/myowner/myapp/push/notifications";
var content = JsonConvert.SerializeObject(pushNotificationModel);
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.PostAsync(url, byteContent);
return response.IsSuccessStatusCode;
}
I debugged this code and used the same data being passed to the client to use on Postman, hence why Model data is absent for example.
Help is much appreciated!
Put the code below in your startup form.
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
I figured out the issue using Telerik Fiddler. This helpful tool allows you to actively view HTTP traffic to and from your machine. Now the mistake was a silly one on my part but it boiled down to changing this:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("X-API-Token", _appCenterApiToken);
to:
_httpClient.DefaultRequestHeaders.Add("X-API-Token", _appCenterApiToken);
As the first one was actually appearing in the HTTP header as Authorization : X-API-Token apiToken rather than X-API-Token : apiToken it caused the request to be bad, hence returning 401 (Unauthorised)

HttpClient request on non TLS 1.0

I am attempting to perform Post requests to a server in my Xamarin forms app, however we recently updated our security policies and are no longer using TLS 1.0 This change appears to have broken the post request and now it times out.
To test this we re-enabled TLS 1.0 just for this service, and I can confirm it does now work. We of course would prefer to force better ciphers, is there any adjustments that can be made to the following request that could ensure it works once we return to the previous policy.
public static async Task<LoginResponse> LoginRESTSend(FormUrlEncodedContent payload)
{
var cl = new HttpClient();
cl.Timeout = TimeSpan.FromSeconds(10);
HttpResponseMessage request = await cl.PostAsync((RESTURL + "/login"), payload);
request.EnsureSuccessStatusCode();
var response = await request.Content.ReadAsStringAsync();
LoginResponse res = JsonConvert.DeserializeObject<LoginResponse>(response);
return res;
}
Edit
I don't believe this is a duplicate of this as the issue is not about sending over https (which I believe it is actually already doing) but more about the TLS version it's using.

Calling a Web api from another Web api

Is it somehow possible to make a Web api that calls another web api?
I am using the code below to access a web api from my web api, but it never return from the call. If I use the code from a console app, it is working fine.
public void DoStuff(){
RunAsync().Wait();
}
public static async Task RunAsync(){
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:53452/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET
HttpResponseMessage response = await client.GetAsync("umbraco/api/Member/Get?username=test");
if (response.IsSuccessStatusCode)
{
string user = await response.Content.ReadAsAsync<string>();
}
}
I also went through the same problem, after much research I discovered that the await operator does not stop the work if the HttpClient returns error 500. To work around the problem I used Task.Wait().
var response = client.GetAsync ("umbraco/api/Member/Get?username=test");
response.Wait ();
I hope this helps others.
Yes you can make a call to a remote web api within the action method of a web api controller.
Lets eliminate the obvious first.
If you set a breakpoint at the start of this action method it is getting hit right? If not then the issue lies in the routing not the action method.
If you set a breakpoint at the if statement does it get hit or is the client.GetAsync() call never returning?
If you haven't done already you may wish to use a tool like fiddler (http://www.telerik.com/fiddler) to compare the request & response from a working use of the api and this broken one. I know you said it is identical to a working implementation but I have found fiddler invaluable to verify exactly what is being sent "on the wire".

Categories

Resources