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*/)
Related
The client and a generic method for the API requests are created here:
public class Client : IDisposable
{
private HttpClient _client;
private void CreateClient()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("KEY", token);
}
public void Dispose() => _client?.Dispose();
public enum Method { GET, POST, PUT, DELETE }
public HttpResponseMessage Request(string url, Method method, object data, HttpContent request)
{
if (data != null)
request = new StringContent(Serialize(data), Encoding.UTF8, "application/json");
switch (method)
{
case Method.POST: return _client.PostAsync(url, request).Result;
case Method.PUT: return _client.PutAsync(url, request).Result;
case Method.DELETE: return _client.DeleteAsync(url).Result;
default: return _client.GetAsync(url).Result;
}
}
public Task<HttpResponseMessage> RequestAsync(string url, Method method, object data, HttpContent request)
{
if (data != null)
request = new StringContent(Serialize(data), Encoding.UTF8, "application/json");
switch (method)
{
case Method.GET: return _client.GetAsync(url);
case Method.POST: return _client.PostAsync(url, request);
case Method.PUT: return _client.PutAsync(url, request);
case Method.DELETE: return _client.DeleteAsync(url);
default: return _client.GetAsync(url);
}
}
public string Post(string url, object data) =>
Request(url, Method.POST, data, null).Content.ReadAsStringAsync().Result;
public Task<HttpResponseMessage> PostAsync(string url, object data) =>
RequestAsync(url, Method.POST, data, null);
//UTILS
private static string Serialize(object data) =>
data == null
? string.Empty
: JsonConvert.SerializeObject(data, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
I'm trying to call these methods to specific classes, to simplify the usage of it for the customer. For example, to create a new checkout for a transaction in a credit card:
public class Checkout : SDK
{
private static Client client;
public Checkout() => client = new Client();
public static async Task Credit(object data) =>
await client.PostAsync(url, data);
}
The request needs to be mounted based on a few models, that can have this structure and I'm trying to generate it in a simple way, like this:
public async Task Test()
{
var transaction = new Transaction
{
PaymentMethod = new PaymentMethod { Code = "1" },
Application = "Test",
Vendor = "Name",
Customer = new Customer
{
//Customer details...
},
Products = new List<TransactionProduct>
{
//Products...
}
};
var teste = Checkout.Credit(transaction);
Console.WriteLine(teste);
}
And all I get as return is:
System.Threading.Tasks.Task`1[System.Threading.Tasks.VoidTaskResult]
Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
I've tried to add await for the Checkout.Credit call, but I get:
CS0815 Test C# Cannot assign void to an implicitly-typed variable
Unit testing this with a simple HttpClient requests works like a charm, but I'm not being able to identify the problem on my project structure, so any help will be very much appreciated.
Task is the return type for an async method that does not have a return value.
Or, to put it another way, async wraps T values into Task<T> (or void returns into Task), and await unwraps those values. Since Credit returns Task, the type of the expression Checkout.Credit(transaction) is Task, and the type of the expression await Checkout.Credit(transaction) is void. And you cannot assign void to var teste; that's what the compiler error is saying.
To fix this, give your async method return types. In particular:
public static async Task<HttpResponseMessage> Credit(object data) =>
await client.PostAsync(url, data);
On a side note, this is quite strange:
public string Post(string url, object data) => ...;
public Task<HttpResponseMessage> PostAsync(string url, object data) => ...;
Usually, if you have a Method and a MethodAsync where Method has some return type TResult, then MethodAsync will have the return type Task<TResult>, not Task<SomeCompletelyDifferentType>. A more natural API would be something like this:
public async Task<HttpResponseMessage> PostAsync(string url, object data)
{
var result = await Request(url, Method.POST, data, null);
return await result.Content.ReadAsStringAsync();
}
I am working on a mobile app, and the problem is that when I perform a async request ( PostAsync ) using Net.Http my program stops running.
Here is my request class, where I perform the requests using Net.Http.
...
namespace BSoft.Requests
{
public class Requests
{
public Requests(){}
public static string HostName = "https://dev5.360businesssoft.com/";
private static readonly HttpClient httpClient = new HttpClient();
public static async Task<string> PerformPostRequest(Dictionary<string, string> values, string path)
{
string url = HostName + path;
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
HttpResponseMessage response = await httpClient.PostAsync(url, content);
string responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
}
and here is my login class, where I call call the request and display the result as string.
...
namespace BSoft.Login
{
public class Login
{
public Login()
{
}
public static void PerformLogin(string username, string password, bool remember)
{
var values = new Dictionary<string, string>();
values.Add("User", username);
values.Add("Password", password);
var ReturnedObj = Requests.Requests.PerformPostRequest(values, "test.php").Result;
System.Diagnostics.Debug.WriteLine(ReturnedObj);
}
}
}
This is a screenshot of the app, you can notice that the button is freezed
The call to Result is blocking the gui thread. Instead, await the result:
var ReturnedObj = await Requests.Requests.PerformPostRequest(values, "test.php");
System.Diagnostics.Debug.WriteLine(ReturnedObj);
Your call to Result will block the gui thread until PerformPostRequest completes, so there's not really a lot of point using the async features here. If you really don't want the code to execute asynchronously then you might as well remove the calls to the async methods and make the calls synchronous.
Try
string returnedString = await Requests.Requests.PerformPostRequest(values, "test.php");
Previously i handled all my http requests in a single class but i would like to move the http login functionality to a different class but now i cant access the http client response.IsSuccessStatusCode
this is my original code whic works
var http = new HttpClient();
var url = String.Format(shared.AppDetails.domainurl+"/v2auth/default/login");
var response2 = await http.PostAsync(url, credentials);
if (response.IsSuccessStatusCode)
{
//do after login stuff
}
Now i would like to move the login logic to a different class that is in a different folder(auth->dbhelpers)
class LoginHttp
{
public static async Task<object> loginAsync(String username, String password)
{
var values = new Dictionary<string, string>
{
{ "username",username },
{ "password", password }
};
var credentials = new FormUrlEncodedContent(values);
var http = new HttpClient();
var url = String.Format(shared.AppDetails.domainurl + "/v2auth/default/login");
var response = await http.PostAsync(url, credentials);
return response;
}
}
So am now trying to access the returned response via
var responsefromhttplogin = auth.dbhelpers.AuthHttp.loginAsync(login_username.Text, login_password.Password);
if (responsefromhttplogin .IsSuccessStatusCode) //this fails
{
//do after login stuff
}
How can i get the retrned response be of type HttpClient again?
Am getting an error of
Task<Objct> does not contain defination for IsSuccessStatusCode
Make your loginAsync method return Task instead. Currently you are returning an object, then you will have access to the IsSuccessStatusCode
class LoginHttp{
public static async Task<HttpResponseMessage> loginAsync(String username, String password)
{
var values = new Dictionary<string, string>
{
{ "username",username },
{ "password", password }
};
var credentials = new FormUrlEncodedContent(values);
var http = new HttpClient();
var url = String.Format(shared.AppDetails.domainurl + "/v2auth/default/login");
var response = await http.PostAsync(url, credentials);
return response;
}
}
You also need to use await in your calling method, otherwise you get a Task back
var responsefromhttplogin = await auth.dbhelpers.AuthHttp.loginAsync(login_username.Text, login_password.Password);
if (responsefromhttplogin.IsSuccessStatusCode)
{
//do after login stuff
}
Try to change your code to await async method like this:
var responsefromhttplogin = await auth.dbhelpers.AuthHttp.loginAsync(login_username.Text, login_password.Password);
if (responsefromhttplogin.IsSuccessStatusCode)
{
//do after login stuff
}
Now after you add await this responsefromhttplogin.IsSuccessStatusCode should be accessible.
And also change your method to return Task<HttpResponseMessage> instead of Task<object>
You are returning Task<object> from loginAsync(), to access the result of the task you can use Task.Result.
I would consider to narrow down the type that you are returning from object to HttpResponseMessage.
Judging from your code, you are returning a Task<object>
You should either cast the return response; as return (HttpResponseMessage) response;
Or change the return type to be Task<HttpResponseMessage>
Relevant documentation
My Question: How do I do this?
So, I hadn't touched anything .Net in about 6 years until this week. There's a lot that I've forgotten and even more that I never knew and while I love the idea of the async/await keywords, I'm having a slight problem implementing the following requirements for a client's API implementation:
The ServerAPI class has a method for each of the API methods, taking appropriate input parameters (e.g. the method Login takes in an id and a password, makes the API call and returns the result to the caller).
I want to abstract away the JSON so that my API methods return the actual object you're fetching (e.g. the Login method above returns a User object with your auth token, uid, etc.)
Some API methods return a 204 on success or no meaningful content (not meaningful in my usecase maybe I only care about success/failure), for these I'd like to return either a bool (true = success) or the status code.
I'd like to keep the async/await (or equivalent) design, because it seems to really work well so far.
For some methods, I might need to just return the HttpResponseMessage object and let the caller deal with it.
This is roughly what I have so far and I'm not sure how to make it compliant with the above OR whether I'm even doing this right. Any guidance is appreciated (flaming, however, is not).
// 200 (+User JSON) = success, otherwise APIError JSON
internal async Task<User> Login (string id, string password)
{
LoginPayload payload = new LoginPayload() { LoginId = id, Password = password};
var request = NewRequest(HttpMethod.Post, "login");
JsonPayload<LoginPayload>(payload, ref request);
return await Execute<Account>(request, false);
}
// 204: success, anything else failure
internal async Task<Boolean> LogOut ()
{
return await Execute<Boolean>(NewRequest(HttpMethod.Delete, "login"), true);
}
internal async Task<HttpResponseMessage> GetRawResponse ()
{
return await Execute<HttpResponseMessage>(NewRequest(HttpMethod.Get, "raw/something"), true);
}
internal async Task<Int32> GetMeStatusCode ()
{
return await Execute<Int32>(NewRequest(HttpMethod.Get, "some/intstatus"), true);
}
private async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate)
{
if (authenticate)
AuthenticateRequest(ref request); // add auth token to request
var tcs = new TaskCompletionSource<RESULT>();
var response = await client.SendAsync(request);
// TODO: If the RESULT is just HTTPResponseMessage, the rest is unnecessary
if (response.IsSuccessStatusCode)
{
try
{
// TryParse needs to handle Boolean differently than other types
RESULT result = await TryParse<RESULT>(response);
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
}
else
{
try
{
APIError error = await TryParse<APIError>(response);
tcs.SetException(new APIException(error));
}
catch (Exception e)
{
tcs.SetException(new APIException("Unknown error"));
}
}
return tcs.Task.Result;
}
This is the APIError JSON structure (it's the status code + a custom error code).
{
"status": 404,
"code":216,
"msg":"User not found"
}
I would prefer to stay with System.Net, but that's mostly because I don't want to switch all my code over. If what I want is easier done in other ways then it's obviously worth the extra work.
Thanks.
Here is an example of how I've done it using MVC API 2 as backend. My backend returns a json result if the credentials are correct. UserCredentials class is the exact same model as the json result. You will have to use System.Net.Http.Formatting which can be found in the Microsoft.AspNet.WebApi.Client NugetPackage
public static async Task<UserCredentials> Login(string username, string password)
{
string baseAddress = "127.0.0.1/";
HttpClient client = new HttpClient();
var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("xyz:secretKey"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);
var form = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", username },
{ "password", password },
};
var Response = await client.PostAsync(baseAddress + "oauth/token", new FormUrlEncodedContent(form));
if (Response.StatusCode == HttpStatusCode.OK)
{
return await Response.Content.ReadAsAsync<UserCredentials>(new[] { new JsonMediaTypeFormatter() });
}
else
{
return null;
}
}
and you also need Newtonsoft.Json package.
public class UserCredentials
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
//more properties...
}
i would use a Deserializer.
HttpResponseMessage response = await client.GetAsync("your http here");
var responseString = await response.Content.ReadAsStringAsync();
[Your Class] object= JsonConvert.DeserializeObject<[Your Class]>(responseString.Body.ToString());
So, first to address the you need Newtonsoft.Json comments, I really haven't felt the need yet. I've found the built in support to work well so far (using the APIError Json in my original question:
[DataContract]
internal class APIError
{
[DataMember (Name = "status")]
public int StatusCode { get; set; }
[DataMember (Name = "code")]
public int ErrorCode { get; set; }
}
I have also defined a JsonHelper class to (de)serialize:
public class JsonHelper
{
public static T fromJson<T> (string json)
{
var bytes = Encoding.Unicode.GetBytes (json);
using (MemoryStream mst = new MemoryStream(bytes))
{
var serializer = new DataContractJsonSerializer (typeof (T));
return (T)serializer.ReadObject (mst);
}
}
public static string toJson (object instance)
{
using (MemoryStream mst = new MemoryStream())
{
var serializer = new DataContractJsonSerializer (instance.GetType());
serializer.WriteObject (mst, instance);
mst.Position = 0;
using (StreamReader r = new StreamReader(mst))
{
return r.ReadToEnd();
}
}
}
}
The above bits I already had working. As for a single method that would handle each request execution based on the type of result expected while it makes it easier to change how I handle things (like errors, etc), it also adds to the complexity and thus readability of my code. I ended up creating separate methods (all variants of the Execute method in the original question:
// execute and return response.StatusCode
private static async Task<HttpStatusCode> ExecuteForStatusCode (HttpRequestMessage request, bool authenticate = true)
// execute and return response without processing
private static async Task<HttpResponseMessage> ExecuteForRawResponse(HttpRequestMessage request, bool authenticate = true)
// execute and return response.IsSuccessStatusCode
private static async Task<Boolean> ExecuteForBoolean (HttpRequestMessage request, bool authenticate = true)
// execute and extract JSON payload from response content and convert to RESULT
private static async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate = true)
I can move the unauthorized responses (which my current code isn't handling right now anyway) into a new method CheckResponse that will (for example) log the user out if a 401 is received.
I'm currently developing an Android app using the Xamarin framework, so I'm writing it in c#. In this app I'm fetching data from a REST-api, like this:
//Method implemented from interface IOnScrollListener
public async void OnScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
//lots of other stuff that's not important for this example
List<Message> messageList = await PopulateMessagebox(arg1, arg2));
}
The method is being called when you reach the bottom of a list (hence the IOnScrollListener implementation). However; the screen freezes when I reach the bottom, and the method is being invoked. And I'm curious as to how this can be, considering I'm using an ansync method call? Shouldn't it run on another thread, allowing the main thread to be uninterrupted/not having to wait?
How would I go about fixing this, so that the app would be responsive while it, in the background, fetches additional info from the API?
Edit: Here's the populateMessageBox-method
public async Task<List<Message>> PopulateMessagebox(Constants.MMBType type, int skipSize)
{
string messagesJson = await _dal.GetMessageBox(type, skipSize, url);
var mess = new List<Message>();
try
{
if (!string.IsNullOrEmpty(messagesJson))
{
mess = Serializer.DeserializeMessagebox(messagesJson);
}
}
catch (Exception e)
{
Logger.Logg("Failed to de-serialize the messagebox:" + e);
throw new AppException(ErrorMessages.SerializationError);
}
return mess;
}
public async Task<string> GetMessageBox(Constants.MMBType type, int skipSize, string url)
{
return await GetMessageBoxJSON(type, skipSize, url);
}
--
private async Task<string> GetMessageBoxJSON(Constants.MMBType type, int skipSize, string url)
{
string res = null;
if (type == Constants.MMBType.Messagebox)
{
string param = string.Format("?$filter=Status ne '{0}' and Status ne '{1}'&$skip={2}",
Constants.NO.Nob.Status.Sent, Constants.NO.Nob.Status.SentCorres, skipSize);
string request = url + param;
res = await DownloadAndHandle(request);
}
And a couple more calls like this, ends us up here:
protected virtual async Task<string> SetupAndDownloadString(string uri)
{
string responseText;
if (Session.CookieJar.Size() > 0)
{
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("ApiKey", AppContext.ApiKey);
request.Headers.Add("Accept", Constants.ApiFormat);
request.Headers.Add("Cookie", Session.CookieJar.ToString());
HttpResponseMessage response = await client.SendAsync(request);
responseText = response.Content.ReadAsStringAsync().Result;
}
else
{
throw new AppException(ErrorMessages.InsufficientAuthorization);
}
return responseText;
}