Working on a project where a WPF front end, and trying to get a handle on async calls to HttpClient and I've been going around and around trying to get PostAsync to work, but it routinely appears to deadlock, or at least the post response times out, even with large values for timeout, and a visible response in fiddler.
So, after a while I decided to try out a few other methods on HttpClient, and they worked, first try. No idea why.
I'm clean all the way to my WPF button with awaits, asyncs, and .ConfigureAwait(false) (I think):
Button:
private async void Generate_Suite_BTN_Click(object sender, RoutedEventArgs e)
{
await suiteBuilder.SendStarWs().ConfigureAwait(false);
}
XmlDoc Load:
internal async Task SendStarWs()
{
var xmlDoc = new XmlDocument();
xmlDoc.Load("C:\\Temp\\file.xml");
await StarWSClient.SendStarMessage(xmlDoc).ConfigureAwait(false);
}
SendMessage:
private static readonly HttpClient Client = new HttpClient {MaxResponseContentBufferSize = 1000000};
public static async Task<STARResult> SendMessage(vars)
{
var response = await SendRequestAsync(url, contentNew, Client).ConfigureAwait(false);
return new STARResult(response, hash);
}
This call '500s' against my endpoint immediately, which I'd expect:
var response = await SendRequestAsync(url, contentNew, Client).ConfigureAwait(false);
private static async Task<HttpResponseMessage> SendRequestAsync(string adaptiveUri, StringContent content, HttpClient httpClient)
{
HttpResponseMessage responseMessage = null;
try
{
responseMessage = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, adaptiveUri)).ConfigureAwait(false);
}
catch (Exception ex)
{
if (responseMessage == null)
responseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.InternalServerError,
ReasonPhrase = $"SendRequestAsync failed: {ex.Message}"
};
}
return responseMessage;
}
The Post variant returns a TaskCancellationException, with timeout message regardless of timeout value:
var response = await PostRequestAsync(url, contentNew, Client).ConfigureAwait(false);
private static async Task<HttpResponseMessage> PostRequestAsync(string adaptiveUri, StringContent content, HttpClient httpClient)
{
HttpResponseMessage responseMessage = null;
try
{
responseMessage = await httpClient.PostAsync(adaptiveUri, content).ConfigureAwait(false);
}
catch (Exception ex)
{
if (responseMessage == null)
responseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.InternalServerError,
ReasonPhrase = $"PostRequestAsync failed: {ex.Message}"
};
}
return responseMessage;
}
My endpoint responds normally to our other software, so I'm pretty sure the endpoint is solid, I can't fathom why the post response is blocked, when the send isn't.
SendAsync can make any http verb request depending on how you set that property. PostAsync and similar are just convenience methods. Those convenience methods use SendAsync internally which is why when you derive a handler you only need to override SendAsync and not all of the send methods.
To your other question though:
When you use SendAsync you need to create the content and pass it. Your only sending an empty message. The 500 likely means that the api got null from the model binding and kicked you back. Just as #John commented.
Related
My code to fetch a huge set of data from an API is like this
public static async Task<model> GetDataAsyncs(string url)
{
// Initialization.
mymodel responseObj = new mymodel();
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
using (var client = new HttpClient(httpClientHandler))
{
// Setting Base address.
client.BaseAddress = new Uri(apiBasicUri);
// Setting content type.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Initialization.
HttpResponseMessage response = new HttpResponseMessage();
// HTTP Get
response = await client.GetAsync(url ).ConfigureAwait(false);
// Verification
if (response.IsSuccessStatusCode)
{
// Reading Response.
string result = response.Content.ReadAsStringAsync().Result;
responseObj.Status = true;
responseObj.Data = result;
}
}
}
return responseObj;
}
And I am calling above function like this inside my controller
public ActionResult myActionMethod()
{
string res= helper.GetDataAsync("url").Result.Data;
}
Ocassionally this throws an error system.threading.tasks.taskcanceledexception a task was canceled . This does not occurs every time. Can anyone please point out what I am doing wrong here?
I can't say for sure why this is happening, but there are some red flags in your code that can be cleaned up and might resolve this.
The first is your use of .ConfigureAwait(false). It can cause some unintended consequences, so I suggest you don't use it. I talk about it more in an article I recently wrote.
Second, use await instead of .Result whenever possible, which is almost always. Using .Result can also cause unintended, hard-to-debug consequences. In your code, I see no reason you can't use await.
Third, the documentation of HttpClient says:
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
So you can declare a static HttpClient and reuse that every time you need it.
Fourth, there's no need for this line:
HttpResponseMessage response = new HttpResponseMessage();
You're instantiating a new HttpResponseMessage here, but then immediately overwriting it in the next line.
Making those changes, your code could look like this:
private static HttpClient _client = new HttpClient(
new HttpClientHandler {ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }}
) {
BaseAddress = new Uri(apiBasicUri),
DefaultRequestHeaders = {
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
public static async Task<model> GetDataAsyncs(string url)
{
mymodel responseObj = new mymodel();
var response = await _client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
responseObj.Status = true;
responseObj.Data = result;
}
return responseObj;
}
And then change your controller action to be async and use await:
public async Task<ActionResult> myActionMethod()
{
var res = (await helper.GetDataAsync("url")).Data;
}
See if you still end up getting exceptions after making those changes.
I am trying to post data from my app from a register page to a web service (testing with requestb.in) however when I try to use the below code it puts the app into break mode, then when I use break points to find where the problem is it just shows that "await PostRequest(...)" is causing the problem.
I have installed System.Net.Http on both the Portable project and the android project.
public async Task<JObject> PostAsync(string uri, string data)
{
var httpClient = new HttpClient();
var response = await httpClient.PostAsync(uri, new StringContent(data));
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
return await Task.Run(() => JObject.Parse(content));
}
Then for the button clicked where this method is called:
async void NextBtnClicked(object sender, EventArgs)
{
await PostAsync("https://requestb.in/MYURL", Username.Text);
}
Username.Text is the string from an entry field in the XAML class, This will recreate my problem
Here is the generic method I made to post data to an API.
As #maccettura pointed out above, it is best practice to reuse HttpClient and I've included that in the code, below.
HttpClient Post
static readonly Lazy<HttpClient> _clientHolder = new Lazy<HttpClient>(() => CreateHttpClient(TimeSpan.FromSeconds(60)));
static HttpClient Client => _clientHolder.Value;
protected static async Task<HttpResponseMessage> PostObjectToAPI<T>(string apiUrl, T data)
{
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(data)).ConfigureAwait(false);
var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
try
{
return await Client.PostAsync(apiUrl, httpContent).ConfigureAwait(false);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return null;
}
}
static HttpClient CreateHttpClient(TimeSpan timeout)
{
var client = new HttpClient();
client.Timeout = timeout;
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
return client;
}
This snippet was taken from my BaseHttpClientService.cs that I copy/paste into any app that needs to use HttpClient:
https://github.com/brminnick/XamList/blob/master/XamList/Services/BaseHttpClientService.cs
Troubleshoot your application using UWP so that you can see what kind of exception it is throwing. Try also to downgrade the two Nuget packages.
I'm posting a bytearray from an Android App in Xamarin.Forms to an .NET Core 2.0 WebAPI. However, I'm getting an exception saying that the NetworkStream already is disposed;
Code making the request;
public async Task PostImageAsync(ImageDTO image)
{
var content = new MultipartFormDataContent();
var byteArrayContent = new ByteArrayContent(image.Content);
content.Add(byteArrayContent, image.FileTile, image.FileName);
try
{
using (var httpClient = GetNewHttpClient())
{
SetBearerToken(httpClient);
var response = await httpClient.PostAsync($"{_apiUrl}/api/images/upload", content);
if (response.IsSuccessStatusCode)
{
}
else
{
}
}
}
catch (Exception e)
{
//Exception occurs here
var msg = e.GetBaseException().Message;
throw;
}
}
Code to get the HttpClient
private HttpClient GetNewHttpClient()
{
//HttpClientHandler is a global variable
var httpClient = new HttpClient(HttpClientHandler, false) {BaseAddress = new Uri(_apiUrl)};
return httpClient;
}
API Endpoint
[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
if (file == null || file.Length == 0) return BadRequest();
return Ok();
}
EDIT - SetBearerToken Method
private static void SetBearerToken(HttpClient client)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", App.StoredToken);
}
The Exception:
cannot access a disposed object. Object name: 'System.Net.Sockets.NetworkStream'.
It feels like a really obvious mistake I'm making here, but I can't get my head around it. Anybody has any ideas?
Don't dispose objects inside async functions
A using statement in an async method is "odd" in that the Dispose
call may execute in a different thread to the one which acquired the
resource (depending on synchronization context etc) but it will still
happen... assuming the thing you're waiting for ever shows up or
fail, of course. (Just like you won't end up calling Dispose in
non-async code if your using statement contains a call to a method
which never returns.)
#jon-skeet https://stackoverflow.com/a/16566605/2228916
Don’t dispose of the HttpClient:
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Also noticed that you set _apiUrl as the BaseAddress and prefix the url in the post. Pick one or the other.
I am trying to call a web api and I want to see what response I get. WOuld it be 200, 204 or 500.
I am trying it fot thr first time.
public void foo()
{
RunAsync(); // dont know what it return type will be
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("pid=23&lang=en-us");
}
}
Here, the code stops on the last line. How can I do that ?
You can use the StatusCode property of your HttpResponseMessage.
HttpResponseMessage response = await client.GetAsync("pid=23&lang=en-us");
if (response.IsSuccessStatusCode)
{
//was success
var result = await response.Content.ReadAsStringAsync();
//checck result string now
//you can also deserialize the response to your custom type if needed.
}
else
{
var statusCode = response.StatusCode;
//do something with this
}
Here is the official documentation of HttpStatusCode enumeration which gives you a complete list of possible status code values.
Since your method returns a Task, you should await it when you call it.
public async Task foo()
{
await RunAsync();
}
Is there anything wrong with my code here? I keep getting this error:
System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times.
My HttpRequestMessage is inside a Func so I figured I get a brand new request each time I pass in func().
public async Task<HttpResponseMessage> GetAsync(HttpRequestMessage request)
{
return await RequestAsync(() => request);
}
public async Task<HttpResponseMessage> RequestAsync(Func<HttpRequestMessage> func)
{
var response = await ProcessRequestAsync(func);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
WaitForSomeTime();
response = await ProcessRequestAsync(func);
}
return response;
}
private async Task<HttpResponseMessage> ProcessRequestAsync(Func<HttpRequestMessage> func)
{
var client = new HttpClient();
var response = await client.SendAsync(func()).ConfigureAwait(false);
return response;
}
You are calling the same func parameter twice:
var response = await ProcessRequestAsync(func);
//...
response = await ProcessRequestAsync(func);
In this case func returns the same request every single time. It doesn't generate a new one every time you call it. If you truly need a different request each time then func needs to return a new message each call:
var response = await GetAsync(() => new HttpRequestMessage()); // Create a real request.
public async Task<HttpResponseMessage> GetAsync(Func<HttpRequestMessage> requestGenerator)
{
return await RequestAsync(() => requestGenerator());
}
I had the same issue, but no repetition in my code. Turned out I had added a watch on an asynchronous process. That watch called the process while I stepped through the code, so that when I got to the line I was trying to debug it crashed with this error message.
Removing all watches solved the problem.
Leaving this here for other people who might have the same problem.