May I know what is the proper way of disposing Handler? or should I really need to dispose it?
Because Microsoft is also diposing the Handler https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler?view=netcore-3.1
Here is my static Handler.
private static HttpClientHandler handlerWithProxy = new HttpClientHandler
{
UseCookies = false,
UseDefaultCredentials = true,
DefaultProxyCredentials = CredentialCache.DefaultCredentials,
Proxy = new WebProxy($"{MyProxy.ProxyHost}:{MyProxy.ProxyPort}", false),
UseProxy = true,
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
};
And here where I'm calling the dispose. Is it correct?
private static async Task<JsonDocument> ResponseMessage(HttpRequestMessage request, CancellationToken token)
{
HttpCompletionOption option = HttpCompletionOption.ResponseHeadersRead;
using (HttpResponseMessage response = MyProxy.UseProxy ? await clientWithProxy.SendAsync(request, option, token).ConfigureAwait(false)
: await client.SendAsync(request, option, token).ConfigureAwait(false))
{
token.ThrowIfCancellationRequested();
HttpStatusCode status = response.StatusCode;
using (Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
if (stream == null || stream.CanRead == false) { return default; }
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
var json = await JsonDocument.ParseAsync(stream, options).ConfigureAwait(false);
if (!response.IsSuccessStatusCode) { throw new InvalidDataException($"Error occured: {ParseError(uri, json, status)}"); }
//is this right of calling the dispose if it is already null?
if (handler == null) { handler.Dispose(); }
return json;
}
}
}
This answer will be short, but sweet:
Handlers are tied to the HttpClient at creation. You don't dispose of those. Just create your HttpClient with it and forget about it. That example on MS's site is not a typical usage scenario.
Make sure that when you create your HttpClient you make it static and in the class scope:
private static readonly HttpClient clientWithProxy = new HttpClient(handlerWithProxy);
You should use reuse the same HttpClient throughout the lifetime of your application for all HTTP requests.
Related
I'm trying to make this return a templated object which is a different type that what is sent. How do I do it?
UserSSS user = new UserSSS();
ReturnObject foo = await _callServer.PostAsync($"{Constants.SERVER_URL}/profiles/v1/HelloObject", user);
public async Task<T> PostAsync<T>(string url, T toPost)
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
#if DEBUG
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
#endif
string json = JsonConvert.SerializeObject(toPost);
StringContent httpContent = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (var client = new HttpClient(httpClientHandler))
{
var ret = await client.PostAsync(url, httpContent);
string contents = await ret.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(contents);
}
}
} ```
Do you mean different generic input and output types? If yes then it's simple as:
public async Task<TOut> PostAsync<TIn, TOut>(string url, TIn toPost)
and in the return
return JsonConvert.DeserializeObject<TOut>(contents);
I have a program which does API call using HttpClient and overriding DelegatingHandler class to retry request on failure as shown below.
class TestHandler
{
public static void APICallTest()
{
var handler = new HttpClientHandler() { Credentials = CredentialCache.DefaultNetworkCredentials };
var client = new HttpClient(new RetryMessageHandler(handler));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Clear();
client.Timeout = TimeSpan.FromSeconds(90);
client.DefaultRequestHeaders.Host = "lab.abc.xyz.def.net";
ServicePointManager.ServerCertificateValidationCallback
+= (sender, cert, chain, sslPolicyErrors) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
for (int i = 0; i < 499; i++)
{
try
{
using (HttpResponseMessage res =
client.GetAsync("https://abc.xyz.def.net/rest/").Result)
{
if (res != null)
{
Console.WriteLine("response: " + res);
}
}
}
catch (Exception ex)
{
throw ex;
}
}
}
}
public class RetryMessageHandler : DelegatingHandler
{
public RetryMessageHandler(HttpMessageHandler innerhandler):base(innerhandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
var exceptions = new List<Exception>();
for (int attempt = 0; attempt < 3; attempt++)
{
await Task.Delay(5 * attempt).ConfigureAwait(false);
try
{
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (System.Net.Http.HttpRequestException ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
The program works with successful response from API. In 500 requests 2-3 requests fail with Forbidden 403. The unsuccessful API calls is random. The logs in API server show that the failed request had no Credentials.
Does anyone have idea on the reason for random failure?
How do I check if Credentails is sent in every request?
I want to send a http post that can take a couple of seconds to reply without freezing my UI, currently this code just hangs my application when the method is callled.
What am I doing wrong and how do i achieve my goal?
private async Task<string> DoHttpClientPost(string method, IDictionary<string, object> args = null)
{
{
HttpClientHandler handler = new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
handler.Proxy = null;
HttpResponseMessage response;
using (var myHttpClient = new HttpClient(handler))
{
myHttpClient.DefaultRequestHeaders.ExpectContinue = false;
myHttpClient.DefaultRequestHeaders.Add("Accept-Charset", "ISO-8859-1,utf-8");
myHttpClient.DefaultRequestHeaders.Add(APPKEY_HEADER, CustomHeaders.GetValues(APPKEY_HEADER));
myHttpClient.DefaultRequestHeaders.Add(SESSION_TOKEN_HEADER, CustomHeaders.GetValues(SESSION_TOKEN_HEADER));
myHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json-rpc"));
var call = new JsonRequest { Method = method, Id = 1, Params = args };
var jsonObject = JsonConvert.Serialize<JsonRequest>(call);
var content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json-rpc");
response = await myHttpClient.PostAsync(new Uri(EndPoint), content);
}
Console.WriteLine("\nCalling: " + method + " With args: " + JsonConvert.Serialize<IDictionary<string, object>>(args));
string jsonResponse = await response.Content.ReadAsStringAsync();
return jsonResponse;
}
}
public T Invoke<T>(string method, IDictionary<string, object> args = null)
{
if (method == null)
throw new ArgumentNullException("method");
if (method.Length == 0)
throw new ArgumentException(null, "method");
var jsonString = DoHttpClientPost(method, args).Result;
var jsonResult = JsonConvert.Deserialize<JsonResponse<T>>(jsonString);
return jsonResult.Result;
}
var jsonString = DoHttpClientPost(method, args).Result;
This is your culprit. If you call .Result on a Task from the UI thread it will hang.
You'll need to async all the way up - so Invoke should be async and return a Task<T> and await the DoHttpClientPost call, the caller should be async etc. etc. etc.
You have to make two changes
Modify this line from
response = await myHttpClient.PostAsync(new Uri(EndPoint), content);
to
response = await myHttpClient.PostAsync(new Uri(EndPoint), content).ConfigureAwait(false);
And looks like your intention is to wait for the post call to complete and return the results, so modify this line from
var jsonString = DoHttpClientPost(method, args).Result;
to
var jsonStringTask = DoHttpClientPost(method, args);
jsonStringTask.Wait(); //wait for http post call to complete.
var jsonString = jsonStringTask.Result;
I have a Xamarin multiplatform application and I need people to log into my application. To check the credentials I have a API. But the code I created to call my API gives me the following error :
System.InvalidOperationException: Cannot send the same request message
multiple times
private HttpResponseMessage GetResponse(HttpMethod method, string requestUri, object value)
{
HttpRequestMessage message = new HttpRequestMessage(method, requestUri);
if (Login != null)
{
message.Headers.Add("Authorization", "Basic " + Login.AuthenticatieString);
}
if (value != null)
{
string jsonString = JsonConvert.SerializeObject(value);
HttpContent content = new StringContent(jsonString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = content;
}
#if DEBUG
ServicePointManager.ServerCertificateValidationCallback =
delegate (object s, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ return true; };
#endif
HttpResponseMessage response = Client.SendAsync(message).Result;
var resp = response.Content.ReadAsStringAsync();
return response;
}
That code is being called on a button click by the following code :
Models.LoginModel login = new Models.LoginModel();
login.Login = username.Text;
login.Wachtwoord = password.Text;
var result = new ApiRequest()
.Post("api/login", login);
And if I put a breakpoint on the line where the error occurs
HttpResponseMessage response = Client.SendAsync(message).Result;
I can see that it is only executed once.
Update 1 :
The .Post function simply calls the GetResponse method like below :
public HttpResponseMessage Put(string requestUri, object value)
{
return GetResponse(HttpMethod.Put, requestUri, value);
}
As mentioned in my comment, you probably need to remove the line var resp = response.Content.ReadAsStringAsync(); from the GetResponse(...) method.
The ReadAsStringAsync() method will dispose the instance of the HttpResponseMessage class making it no longer valid. Since you do not return the resp variable but the (at this point already disposed) response variable, I assume later on somewhere in the code you make another call to this object. Which will result in the InvalidOperationException being thrown.
So try using the following function:
private HttpResponseMessage GetResponse(HttpMethod method, string requestUri, object value)
{
HttpRequestMessage message = new HttpRequestMessage(method, requestUri);
if (Login != null)
{
message.Headers.Add("Authorization", "Basic " + Login.AuthenticatieString);
}
if (value != null)
{
string jsonString = JsonConvert.SerializeObject(value);
HttpContent content = new StringContent(jsonString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = content;
}
#if DEBUG
ServicePointManager.ServerCertificateValidationCallback =
delegate (object s, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ return true; };
#endif
HttpResponseMessage response = Client.SendAsync(message).Result;
return response;
}
I have a method to POST some data to server. Server may return aswer means I need change URL and send data to other server. I use for it HttpClient class from 4.5 framework. I use either do-while loop to repeat requests until I do not need redirecton. But there is a problem.
The question is why if I create HttpClient instance outside loop, second await didn't happen and my programm get out of loop, but if I create HttpClient instance inside the loop again and again - all is fine? Can I reuse one HttpClient for several POST requsts inside do-while loop inside async method?
My working code sample:
public async Task<bool> GameLogin()
{
JToken r;
do
{
var clientHandler = new HttpClientHandler
{
CookieContainer = this.myCContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
};
var client = new HttpClient(clientHandler);
client.DefaultRequestHeaders.Add("client-ver", Versoin);
client.DefaultRequestHeaders.Add("method", "SignIn");
client.DefaultRequestHeaders.Add("authKey", AppParams["auth_key"].ToString());
var content = new StringContent(this.SendStr);
var answer = await client.PostAsync(this.CurrentUrl, content);
var rawString = await answer.Content.ReadAsStringAsync();
DinamicData = JObject.Parse(rawString);
r = DinamicData["r"];
if (r == null) continue;
this.CurrentUrl = string.Format("http://{0}/main.ashx", r);
} while (r != null);
return DinamicData.Type != JTokenType.Null;
}
My did not working code sample:
public async Task<bool> GameLogin()
{
var clientHandler = new HttpClientHandler
{
CookieContainer = this.myCContainer,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
};
var client = new HttpClient(clientHandler);
client.DefaultRequestHeaders.Add("client-ver", Versoin);
client.DefaultRequestHeaders.Add("method", "SignIn");
client.DefaultRequestHeaders.Add("authKey", AppParams["auth_key"].ToString());
var content = new StringContent(this.SendStr);
JToken r;
do
{
var answer = await client.PostAsync(this.CurrentUrl, content);
var rawString = await answer.Content.ReadAsStringAsync();
DinamicData = JObject.Parse(rawString);
r = DinamicData["r"];
if (r == null) continue;
this.CurrentUrl = string.Format("http://{0}/main.ashx", r);
} while (r != null);
return DinamicData.Type != JTokenType.Null;
}
And my second code did not wait await in secont loop turn.
Okay, now we've got the exception...
I suspect the problem isn't reusing the HttpClient - it's reusing this:
var content = new StringContent(this.SendStr);
I suspect that content is probably being disposed by the HttpClient's first call, which makes the second call fail.
Just move that line inside the loop (it's inside the loop in your working code), and I expect all will be well.