I studied over the Internet regarding Task Async method but cannot seem to find an approach to assign my return value in Task Async to another object. The first method is to prepare HTTP Request header and Uri.
public static async Task MainAsync()
{
string token = await AuthHelper.AcquireToken(tenantId, clientId, clientSecret);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.BaseAddress = new Uri("https://foo.net");
await GetValue(client);
}
}
The second method is to use GetAsync to call to an API to get the JSON and the two last lines I extract only value from the "Value" field in the JSON body.
public static async Task<String> GetValue(HttpClient client)
{
string url = $"/mykey/key01";
using (var httpResponse = await client.GetAsync(url))
{
httpResponse.EnsureSuccessStatusCode();
string responsContent = await httpResponse.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responsContent);
string value = json["value"].ToString();
return value;
}
}
Now I would like to use this value to assign to another object, but not sure how to do so. I managed to return the valid value. Is it possible to retrieve the value from another method or even different class?
[Updated] The main function is:
static void Main(string[] args)
{
try
{
MainAsync().Wait();
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
}
Update
To be more clear. The HTTP response message is a JSON format and I can return the value from Value property in this JSON. Now how I can to reuse the value from an external method or class
I'm not sure exactly what you are trying to achieve. And there would be thorough debates about your architecture, you can do something like this..
Update
Because your MainAsync is static it can be called form anywhere.
You just need to modify it a bit to return your result as follows :
public static async Task<string> MainAsync()
{
...
return await GetValue(client);
...
And somewhere else
public class MyAwesomeClass
{
public async Task DoMagic()
{
var newValueOfSomething = await MainAsync();
// hilarity ensues
}
}
You can Make it more generic and useful, something like below :
Your initial method can be changes to :
public async Task<T> GetContentAsync<T>(HttpClient client)
{
string url = $"/mykey/key01";
using (var httpResponse = await client.GetAsync(url))
{
httpResponse.EnsureSuccessStatusCode();
string responsContent = await httpResponse.Content.ReadAsStringAsync();
return Deserialize<T>(json);
}
}
private T Deserialize<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json, SerializationSettings);
}
You can now call method like :
var person = await GetContentAsync<Person>(/*http client*/)
I think I might be retarded or I'm asking too much out of C# but I can't get this to work.
What essentially I'm trying to do is to wrap an API-client with some logging functions and a method to request a new token from the API-server.
controller:
public class TestController : ApiController
{
[HttpGet]
[Route("api/model/get/{id}"]
public IHttpActionResult GetModel<Model>(int id)
{
var result = Service.DoHttp<Model>(ServiceClass.GetModel, id);
}
}
service:
public static class ServiceClass
{
private static readonly HttpClient client = new HttpClient() { BaseAddress = new Uri(Globals.ExternalApiPath) };
private static string TokenHeader = "";
public async static Task<HttpResponseMessage> GetModel(int id)
{
var response = client.GetAsync($"/api/get/{id}");
return await response;
}
public static T DoHttp<T>(Func<int, HttpResponseMessage> funk, int id)
{
try
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", TokenHeader);
var result = funk(id);
if (result.IsSuccessStatusCode)
{
return result.Content.ReadAsAsync<T>().Result;
}
else
{
throw new Exception(String.Format($"Unknown error! Unable to contact remote API! AccessToken: {TokenHeader} Status code: {result.StatusCode}"));
}
}
catch (Exception e)
{
// log ex
throw e;
}
}
}
But my Service.DoHttp(Service.GetModel, id); complains about it being the wrong return type.
What am I doing wrong or have I misunderstood the whole concept?
EDIT: Compiler complains about 'Task ServiceClass.GetModel(int)' has the wrong return type
Change the DoHttp method to the following.
public static T DoHttp<T>(Func<int, Task<HttpResponseMessage>> funk, int id)
As the GetModel method returns a Task you need to use a task as the return type of the Func too.
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 InvalidOperationException 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).
public async Task<HttpResponseMessage> getOne(HttpRequestMessage request, int id)
{
return CreateResponse(async () =>
{
var category = await _unitOfWork.Categories.GetSingleAsync(id);
var categoryVm = Mapper.Map<Category, CategoryViewModel>(category);
HttpResponseMessage response = request.CreateResponse<CategoryViewModel>(HttpStatusCode.OK, categoryVm);
return response;
});
}
Base Class
protected Task<IHttpActionResult> CreateResponse(Func<IHttpActionResult, Task> function)
{
IHttpActionResult response = null;
try
{
response = function.Invoke();
}
}
Read up on Cross cutting concerns.
You are giving yourself unnecessary trouble. Your example can be reduced to :
public async Task<IHttpActionResult> getOne(int id) {
var category = await _unitOfWork.Categories.GetSingleAsync(id);
var categoryVm = Mapper.Map<Category, CategoryViewModel>(category);
return Ok(categoryVm);
}
Try to keep controller lean.
Check this answer
I have an issue with a task blocking when I try to retrieve it's result.
I have the following piece of code I want executed synchronously (which is why I'm looking for the result)
I would ignore the reason each call has to be made (legacy software that requires multiple calls through different layers)
the call seems to break down after it starts the task for the final call to be made in the PostCreateProfile, I can see this request never makes it any further than this.
if (CreateProfile(demographics).Result) // Task blocks here
{
//dothing
}
private async Task<bool> CreateProfile(Demographics demographics)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(demographics);
return await profileService.Create(createProfileBindingModel);
}
public async Task<bool> Create(CreateProfileBindingModel model)
{
HttpResponseMessage response = await profileServiceRequest.PostCreateProfile(rootURL, model);
return response.IsSuccessStatusCode;
}
public Task<HttpResponseMessage> PostCreateProfile(string url, CreateProfileBindingModel model)
{
HttpContent contents = SerialiseModelData(model);
var resultTask = client.PostAsync(url, contents);
return resultTask;
}
The request will reach its destination if I was to change CreateProfile to an async void like so:
private async void CreateProfile(AppointmentController controller)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(controller);
await profileService.Create(createProfileBindingModel);
}
But I can't return the bool I want to use from this.
Can anyone point out what I am doing wrong?
You should never call .Result on a async/await chain.
Whatever code that calls CreateProfile(demographics) needs to be async too so it can do
if (await CreateProfile(demographics))
{
//dothing
}
Also, if you can you really should put .ConfigureAwait(false) wherever it is logically possible.
if (await CreateProfile(demographics).ConfigureAwait(false)) // depending on what dothing is you may not want it here.
{
//dothing
}
private async Task<bool> CreateProfile(Demographics demographics)
{
ProfileService profileService = new ProfileService();
CreateProfileBindingModel createProfileBindingModel = this.CreateProfileModel(demographics);
return await profileService.Create(createProfileBindingModel).ConfigureAwait(false);
}
public async Task<bool> Create(CreateProfileBindingModel model)
{
HttpResponseMessage response = await profileServiceRequest.PostCreateProfile(rootURL, model).ConfigureAwait(false);
return response.IsSuccessStatusCode;
}
public Task<HttpResponseMessage> PostCreateProfile(string url, CreateProfileBindingModel model)
{
HttpContent contents = SerialiseModelData(model);
var resultTask = client.PostAsync(url, contents);
return resultTask;
}