Async call with HttpClient in PCL - c#

I have a PCl in which I want to make a async call usingg HttpClient. I coded like this
public static async Task<string> GetRequest(string url)
{
var httpClient = new HttpClient() { MaxResponseContentBufferSize = int.MaxValue };
HttpResponseMessage response = await httpClient.GetAsync(url);
return response.Content.ReadAsStringAsync().Result;
}
But await is showing error "cannot await System.net.http.httpresponsemessage" like message.
If I use code like this than everything goes well but not in async way
public static string GetRequest(string url)
{
var httpClient = new HttpClient() { MaxResponseContentBufferSize = int.MaxValue };
HttpResponseMessage response = httpClient.GetAsync(url).Result;
return response.Content.ReadAsStringAsync().Result;
}
I just want that this method executes in async way.
This is the screen shot:

Follow the TAP guidelines, don't forget to call EnsureSuccessStatusCode, dispose your resources, and replace all Results with awaits:
public static async Task<string> GetRequestAsync(string url)
{
using (var httpClient = new HttpClient() { MaxResponseContentBufferSize = int.MaxValue })
{
HttpResponseMessage response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
If your code doesn't need to do anything else, HttpClient has a GetStringAsync method that does this for you:
public static async Task<string> GetRequestAsync(string url)
{
using (var httpClient = new HttpClient() { MaxResponseContentBufferSize = int.MaxValue })
return await httpClient.GetStringAsync(url);
}
If you share your HttpClient instances, this can simplify to:
private static readonly HttpClient httpClient =
new HttpClient() { MaxResponseContentBufferSize = int.MaxValue };
public static Task<string> GetRequestAsync(string url)
{
return httpClient.GetStringAsync(url);
}

If you are using a PCL platform that supports .net4 then I suspect you need to install the Microsoft.bcl.Async nuget.

Related

HttpClient in async/sync implementation returns WaitingForActivation

I'm having a problem with async to sync implementation of HttpClient.
Id = 8, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
I know what I'm doing is probably a bad practice and it would be ideal to make all the path async, but that's a request that the company is making me, so I have to do like this.
Project is build in NET Standard 1.1, to be used as a NuGet package and to be compatible with Framework and Core as well.
Here's my main client construction...
private static HttpClient _client;
private static Uri _baseAddress;
private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings
{ DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore };
public Client() { }
private Client(string baseUrl, Config config)
{
_baseAddress = new Uri(baseUrl);
_client = new HttpClient { Timeout = TimeSpan.FromSeconds(config.Timeout) };
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("X-API-KEY", config.Token);
}
private Client _paymentClient;
private Client _mainClient;
public Client Create(bool payment, Config config = null)
{
if (!payment)
{
_mainClient = _mainClient ?? new Client("https://api.address.com/", config);
return _mainClient;
}
_paymentClient = _paymentClient ?? new Client("https://payment.address.com/", config);
return _paymentClient;
}
public void Dispose() => _client.Dispose();
private static async Task<T> Send<T>(HttpMethod method, string url, object data = null)
{
var uri = new UriBuilder(_baseAddress);
uri.Path += url;
var request = new HttpRequestMessage(method, uri.Uri);
if (data != null)
request.Content = new StringContent(JsonConvert.SerializeObject(data, _settings), Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
T result = default;
if (response.IsSuccessStatusCode)
{
if (response.Content.Headers.ContentType.MediaType == "application/json")
{
var responseObj = JsonConvert.DeserializeObject<Response<T>>(content, _settings);
if (responseObj.HasError)
throw new Safe2PayException(responseObj.ErrorCode, responseObj.Error);
responseObj.ResponseDetail = result;
}
}
else throw new Exception((int) response.StatusCode + "-" + response.StatusCode);
request.Dispose();
response.Dispose();
return result;
}
And the Send<T> method is supposed to be a general treatment to process the request and response, wrapped on generic calls like this:
internal Task<T> Get<T>(string url) => Send<T>(HttpMethod.Get, url);
//OR even async...
internal async Task<T> Get<T>(string url) => await Send<T>(HttpMethod.Get, url);
Which are called like this, to send and receive data..
private Client Client { get; }
public CheckoutRequest(Config config) => Client = new Client().Create(true, config);
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
My problem is that the client is always getting me a WaitingfForActivation or even Running or WaitingToRun, doesn't matter if I change it to...
Task.Run(() => Send<T>(HttpMethod.Get, url));
//or
Task.Run(() => Send<T>(HttpMethod.Get, url).Result);
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url));
//or
Task.Run(async () => await Send<T>(HttpMethod.Get, url).ConfigureAwait(false));
I've been trying to find what I'm doing wrong, tried to change all the awaits, but I'm not being sucessful with this, so any help will be very much appreciated.
I suspect your problem is here:
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
You didn't show your code for Post<T>(), but I assume it's also an async Task<T> method, which means response is a Task<T> and your code is basically doing this:
Start a task.
Return a description of the incomplete task.
When I assume this is really what you want:
Start the task.
Wait for the task to complete.
Return the result of the task.
Ideally, this should be an async method, and you can await the task:
public async Task<object> Credit(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
If you absolutely must wait for the task synchronously (there are very few reasons to need to) then you can use .GetAwaiter().GetResult():
public object Credit(Transaction transaction)
{
var response = Client.Post<Transaction>("v2/Payment", transaction).GetAwaiter().GetResult();
return response;
}
The main benefit of .GetAwaiter().GetResult() instead of .Result is that, in the case of exceptions, it will throw the actual exception instead of an AggregateException.
Also, you can make your Create() method static:
public static Client Create(bool payment, Config config = null)
Then you don't need to initialize the class just to call it:
public CheckoutRequest(Config config) => Client = Client.Create(true, config);
Update: If you want async and non-async versions of the same method, you can follow the same standard that Microsoft uses and name the async method with the Async suffix. The non-async version can just call the async version. For example:
public async Task<object> CreditAsync(Transaction transaction)
{
var response = await Client.Post<Transaction>("v2/Payment", transaction);
return response;
}
public object Credit(Transaction transaction)
{
return CreditAsync(transaction).GetAwaiter().GetResult();
}

Async and await may be elided?

I have this simple code :
public async Task<string> GetAsync()
{
var httpClient = new HttpClient();
return await httpClient.GetStringAsync("...");
}
However Resharper says :
This warning is gone when I use a variable :
public async Task<string> GetAsync()
{
var httpClient = new HttpClient();
var st = await httpClient.GetStringAsync("...");
return st;
}
I already know the danger when doing
using (var httpClient = new HttpClient())
return httpClient.GetStringAsync("...");
(Task will be canceled)
But that's not my case here since I'm using await ( and not using using).
Question:
Why does Resharper warn me?
Your method "can" be rewritten as follows:
public Task<string> GetAsync()
{
var httpClient = new HttpClient();
return httpClient.GetStringAsync("...");
}
thus avoiding the overhead of compiling an async method and then doing a costly control flow switch with await. The functionality is still the same. That's what R# is telling you about - you can omit the async/await and avoid an unnecessary overhead.
However, I put the "can" in quotes, as your code is smelly, because first of all, HttpClient is an IDisposable, so you should dispose of it after usage. Then the async/await will be necessary:
public async Task<string> GetAsync()
{
using(var httpClient = new HttpClient())
{
return await httpClient.GetStringAsync("...");
}
}
since this will be translated into an equivalent of
public async Task<string> GetAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetStringAsync("...");
httpClient.Dispose();
return result;
}
This one you absolutely should fix. Secondly, a thing to consider is that creating HttpClients can silently destabilise your app, as HttpClients should be reused. See this blog post and this SE Stack Exchange post.

Get & Post in ASP.NET Blazor

With the help of a few samples available on the internet, I am able to develop a ASP.NET Core Hosted Blazor Application.
But While Calling an api as follow
private async Task Refresh()
{
li.Clear();
li = await Http.GetJsonAsync<SampleModel[]>("/api/Sample/GetList");
StateHasChanged();
}
private async Task Save()
{
await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
await Refresh();
}
In the line below:
await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
How can I check status code of this HTTP call?
If there occurs any problem in API call than I want to display a message.
But when I do:
HttpResponseMessage resp = await Http.SendJsonAsync(HttpMethod.Post, "api/Sample/Add", obj);
Then it says:
can not cast void to HttpResponse Message
I am using below methods:
GetJsonAsync() // For HttpGet
SendJsonAsync() // For HttpPost And Put
DeleteAsync() // For HttpDelete
How can I verify the status code here ?
The thing is that you are using blazor's HttpClientJsonExtensions extensions,
Which internally usually calls
public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content);
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
{
var requestJson = JsonUtil.Serialize(content);
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
if (typeof(T) == typeof(IgnoreResponse))
{
return default;
}
else
{
var responseJson = await response.Content.ReadAsStringAsync();
return JsonUtil.Deserialize<T>(responseJson);
}
}
The GET requests use HttpContext.GetStringAsync internally
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
{
var responseJson = await httpClient.GetStringAsync(requestUri);
return JsonUtil.Deserialize<T>(responseJson);
}
while the normal HttpClient API still exists and can be used just as in those extension methods.
Those extension methods simply wrap the default HttpClient calls.
If you desire to have access to response status you would need to write your own wrappers that expose the desired functionality or just use the default API
Try this:
var response = await Http.SendJsonAsync <HttpResponseMessage>(HttpMethod.Post, "api/Sample/Add", obj);

Implement Static HttpClient in Web App

Using Vs 2017 community and azure.
I have a web app MVC5, that has this class.
public static class SchedulerHttpClient
{
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
private static HttpClient _Client = new HttpClient();
public static HttpClient Client{ get { return _Client; } }//TODO: validate
public static async Task MainAsync()
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string baseAddress = ConfigurationManager.AppSettings["BaseAddress"];
string token = await AcquireTokenBySPN(tenantId, clientId, clientSecret).ConfigureAwait(false);
_Client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); //TODO ssmith: const or localization
_Client.BaseAddress = new Uri(baseAddress);
}
private static async Task<string> AcquireTokenBySPN(string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ConfigurationManager.AppSettings["ARMResource"]),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
private static async Task<dynamic> HttpPost(string tenantId, string payload)
{
var address = String.Format(ConfigurationManager.AppSettings["TokenEndpoint"], tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await _Client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync());
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
This class is meant to spin up a Httpclient, contact azure get a token, and set the client with it, this way i can re-use with authorization.
Issue is when and how to call the class, currently I have tried the Global.asx, the HomeController Constructor method and the Index method.
public HomeController()
{
//Init();
}
public async void Init()
{
await SchedulerHttpClient.MainAsync().ConfigureAwait(false);
}
public async Task<ActionResult> Index()
{
Init();
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
The error i get is
[InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]
Is my static class implemented correctly? If so, how would i instantiate the client and then re-use across my app?
UPDATE #Stephen Cleary:
public async Task<ActionResult> Index()
{
await SchedulerHttpClient.ClientTask.ConfigureAwait(false);
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
static async Task MakeARMRequests()
{
const string ResourceGroup = "fakegrp";
// Create the resource group
// List the Web Apps and their host names
var client = await SchedulerHttpClient.ClientTask;
var response = await client.GetAsync(
$"/subscriptions/{Subscription}/resourceGroups/{ResourceGroup}/providers/Microsoft.Web/sites?api-version=2015-08-01");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
foreach (var app in json.value)
{
Console.WriteLine(app.name);
foreach (var hostname in app.properties.enabledHostNames)
{
Console.WriteLine(" " + hostname);
}
}
}
Here is the refactored class as per suggestion.
public static class SchedulerHttpClient
{
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
{
var client = new HttpClient();
await MainAsync(client).ConfigureAwait(false);
return client;
});
public static Task<HttpClient> ClientTask => _Client.Value;
private static async Task MainAsync(HttpClient client)
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string baseAddress = ConfigurationManager.AppSettings["BaseAddress"];
string token = await AcquireTokenBySPN(client, tenantId, clientId, clientSecret).ConfigureAwait(false);
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); //TODO ssmith: const or localization
client.BaseAddress = new Uri(baseAddress);
}
private static async Task<string> AcquireTokenBySPN(HttpClient client, string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ConfigurationManager.AppSettings["ARMResource"]),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(client, tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
private static async Task<dynamic> HttpPost(HttpClient client, string tenantId, string payload)
{
var address = String.Format(ConfigurationManager.AppSettings["TokenEndpoint"], tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
Your problem is due to async void. As noted in my intro to async on ASP.NET article:
When an asynchronous handler completes the request, but ASP.NET detects asynchronous work that hasn’t completed, you get an Invalid­OperationException with the message, “An asynchronous module or handler completed while an asynchronous operation was still pending.” This is usually due to asynchronous code calling an async void method...
Also see my article on async best practices for other reasons to avoid async void.
In your case, you have a singleton resource that requires initialization, and that initialization must be asynchronous. You only want to start the initialization once, and all callers should share the initialization result, so a Lazy<T> seems appropriate. Since the initialization is asynchronous, it can be represented by a Task. Hence, a Lazy<Task>:
public static class SchedulerHttpClient
{
... // Same as above, but making MainAsync private.
public static readonly Lazy<Task> Initialize = new Lazy<Task>(() => MainAsync());
}
Usage:
public async Task<ActionResult> Index()
{
await SchedulerHttpClient.Initialize.Value.ConfigureAwait(false);
try
{
await MakeARMRequests().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
This will be enough to get your code working, but I would go a step further and refactor SchedulerHttpClient so that it only exposes the HttpClient after it has been initialized:
public static class SchedulerHttpClient
{
private static Lazy<Task<HttpClient>> _Client = new Lazy<Task<HttpClient>>(async () =>
{
var client = new HttpClient();
await MainAsync(client).ConfigureAwait(false);
return client;
});
public static Task<HttpClient> ClientTask => _Client.Value;
private static async Task MainAsync(HttpClient client) { ... }
private static async Task<string> AcquireTokenBySPN(HttpClient client, string tenantId, string clientId, string clientSecret) { ... }
private static async Task<dynamic> HttpPost(HttpClient client, string tenantId, string payload) { ... }
}
This forces your MakeARMRequests to await on SchedulerHttpClient.ClientTask instead of accessing the HttpClient directly, so you don't have to remember to do it in all your controller methods.
As a final note, you may want to "reset" the Lazy<T> if the initialization actually fails. That would complicate this homegrown solution sufficiently that I would recommend using my AsyncLazy<T> type instead (available on NuGet).

Client calling WebAPI stuck in await

I have a httpclient that is calling a WebAPI service. The GET reaches the service and returns the content but the client just keeps waiting...
Client code:
static async Task RunAsyncGet(string baseUri, string uri)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(baseUri);
HttpResponseMessage response = await client.GetAsync(uri); // <-- stuck here
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
IEnumerable<UserAccountModel> users = await response.Content.ReadAsAsync<IEnumerable<UserAccountModel>>();
//...
}
}
}
WebAPI code:
public class UserAccountController : ApiController
{
private IRepository _repo;
public UserAccountController(IRepository repo)
{
_repo = repo;
}
public HttpResponseMessage Get()
{
var s = _repo.GetAllUserAccounts();
IContentNegotiator negotiator = Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(typeof(AuthResponseModel), Request, Configuration.Formatters);
var bestMatchFormatter = result.Formatter;
var mediaType = result.MediaType.MediaType;
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new ObjectContent<IQueryable<UserAccount>>(s, bestMatchFormatter, mediaType)
};
}
}
Thoughts?
Further up in your client code (whatever ends up calling RunAsyncGet), some code is calling Task.Wait or Task<T>.Result. That will cause a deadlock if called from the UI thread, as I explain on my blog.
The proper solution is to change that Wait/Result to use await.
This is how I ended up calling the WebAPI:
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:23302");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("api/useraccount").Result;
if (response.IsSuccessStatusCode)
{
var t = response.Content.ReadAsAsync<IEnumerable<UserAccount>>().Result;
...
}
else
{
//Something has gone wrong, handle it here
}
}
It seems that your call to EnsureSuccessStatusCode is the likely culprit. That method actually returns a HttpResponseMessage that will have a HTTP status in the 200 range, or will throw an exception. So, you probably want something like:
static async Task RunAsyncGet(string baseUri, string uri)
{
var client = new HttpClient();
client.BaseAddress = new Uri(baseUri);
HttpResponseMessage response = await client.GetAsync(uri);
IEnumerable<UserAccountModel> users = await response.EnsureSuccessStatusCode().Content.ReadAsAsync<IEnumerable<UserAccountModel>>();
// ... the rest ...
}

Categories

Resources