I have created a function that returns an object using async/await. I would like to make the function generic so that it can return whatever object that I pass in. The code is boilerplate except for the objects being returned. I would like to be able to call GetAsync and have it return the correct object
public Patron getPatronById(string barcode)
{
string uri = "patrons/find?barcode=" + barcode;
Patron Patron = GetAsync(uri).Result;
return Patron;
}
private async Task<Patron> GetAsync(string uri)
{
var client = GetHttpClient(uri);
var content = await client.GetStringAsync(uri);
JavaScriptSerializer ser = new JavaScriptSerializer();
Patron Patron = ser.Deserialize<Patron>(content);
return Patron;
}
What about a generic method?
private async Task<T> GetAsync<T>(string uri)
{
var client = GetHttpClient(uri);
var content = await client.GetStringAsync(uri);
var serializer = new JavaScriptSerializer();
var t = serializer.Deserialize<T>(content);
return t;
}
Normally, you should place this method into another class and make it public, in order it can be used by methods in different classes.
Regarding the way you call this method, you could try the following:
// I capitalized the first letter of the method,
// since this is a very common convention in .NET
public Patron GetPatronById(string barcode)
{
string uri = "patrons/find?barcode=" + barcode;
var Patron = GetAsync<Patron>(uri).Result;
return Patron;
}
Note: In the above snippet I assumed that you haven't moved the GetAsync into another class. If you move it, then you have to make a slight change.
Update
I'm not following what you mean by your note. Do I need to make GetPatronById a task function as well - like Yuval has done below?
I mean something like this:
// The name of the class may be not the most suitable in this case.
public class Repo
{
public static async Task<T> GetAsync<T>(string uri)
{
var client = GetHttpClient(uri);
var content = await client.GetStringAsync(uri);
var serializer = new JavaScriptSerializer();
var t = serializer.Deserialize<T>(content);
return t;
}
}
public Patron GetPatronById(string barcode)
{
string uri = "patrons/find?barcode=" + barcode;
var Patron = Repo.GetAsync<Patron>(uri).Result;
return Patron;
}
Generic can be easily done with:
private async Task<T> GetAsync(string uri)
{
var client = GetHttpClient(uri);
var content = await client.GetStringAsync(uri);
return JsonConvert.DeserializeObject<T>(content);
}
Things to note:
JavaScriptSerializer has been deprecated for ages, avoid using it. Try out Json.NET instead.
This:
Patron Patron = GetAsync(uri).Result;
is dangerous and can cause potential deadlocks, especially in Web API. You need to go "async all the way":
public Task<Patron> GetPatronByIdAsync(string barcode)
{
string uri = $"patrons/find?barcode={barcode}";
return GetAsync<Patron>(uri);
}
And only your top most level invoker need await on the Task. Possibly some controller action:
public async Task SomeAction()
{
await GetPatronByIdAsync("hello");
}
Related
I have problem with calling API in my Discord Bot, I am trying to learn async, await but it seems as I am stuck.
This is my Call API class where I am calling API.
public async Task<Root> GetInfoAsync()
{
string path = "https://onemocneni-aktualne.mzcr.cz/api/v2/covid-19/zakladni-prehled.json";
Root data = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
data = JsonConvert.DeserializeObject<Root>(json);
}
return data;
}
public async Task<string> VypisAsync()
{
Root data = await this.GetInfoAsync();
int deaths = data.data[0].umrti,
recovered = data.data[0].vyleceni,
positive = data.data[0].aktivni_pripady,
hospitalized = data.data[0].aktualne_hospitalizovani;
return $"Aktualni situace v ČR:\n" +
$"vyleceni: {recovered}\n" +
$"aktualne nemocni: {positive}\n" +
$"hospitalizovani: {hospitalized}\n" +
$"zemreli: {deaths}";
}
And here is my covid command
public class CovidModule : ModuleBase<SocketCommandContext>
{
// ~void -> current covid situation in CZ
[Command("covid")]
[Summary("shows current covid situation in CZ")]
public async Task<Task> CovidAsync()
{
CovidApi api = new CovidApi();
return ReplyAsync(await api.VypisAsync());
}
}
I know that all others commands return Task, but I don't know how to make it like that.
ReplyAsync() is an async method, so you need to await it.
Your CovidAsync() method isn't returning an actual value, so in the synchronous world its return value would be void. Since it's an async method you return Task instead.
public async Task CovidAsync()
{
CovidApi api = new CovidApi();
await ReplyAsync(await api.VypisAsync());
}
As an aside, it would be better to have CovidApi as a member of your CovidModule. That way you don't need to keep newing it up in each method.
I have the following code, would like to create dynamic return type of ActionResult of function getResponse, so it can be used as a generic function for all other models like ActionResult.ActionResult etc etc.
The reason I am having an explicit return type is because it needs to be working with Swashbuckle openAPI
which response can be in XML, Json, Text...
and it looks like it only works by using ActionResult
What is the correct syntax of public async Task> getResponse(HttpResponseMessage results)??? Thanks, guys.
public async Task<ActionResult<Contact>> Get(string referenceId = "af2f8f37-c1d9-40d3-9f29-08d5dab10621")
{
Settings.ReferenceID = referenceId;
//same as get a single Contact;
var url = Settings.PutContactUrl;
Log.Information($"Endpoint hit: {url}");
var requestData = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(url),
};
var results = await _client.SendAsync(requestData);
return getResponse(results);
if (!results.IsSuccessStatusCode)
return StatusCode((int)results.StatusCode, JsonConvert.DeserializeObject(results.Content.ReadAsStringAsync().Result));
//need to cast type to provide xml result
var contact = JsonConvert.DeserializeObject<Contact>(results.Content.ReadAsStringAsync().Result);
//var abc = _mapper.Map<Contact>(contact);
return StatusCode((int)results.StatusCode, contact);
}
[NonAction]
public async Task<ActionResult<T>> getResponse(HttpResponseMessage results)
{
if (results.IsSuccessStatusCode)
{
switch (results.StatusCode)
{
case HttpStatusCode.NotFound:
return StatusCode((int)results.StatusCode, results.Content.ReadAsStringAsync().Result);
default:
return StatusCode((int)results.StatusCode, JsonConvert.DeserializeObject(results.Content.ReadAsStringAsync().Result));
}
}
return StatusCode((int)results.StatusCode, JsonConvert.DeserializeObject(results.Content.ReadAsStringAsync().Result));
}
I have an interface that is defined as follows:
internal interface IHttpService
{
Task SendGetRequest(string param);
}
And the following concrete class (obviously there is compilation errors):
public class HttpService : IHttpService
{
private readonly HttpClient client;
private const string httpLink = "https://somesite.org/search?q=";
private const string httpSuffix = "&format=json&ads=1";
public HttpService()
{
client = new HttpClient();
client.DefaultRequestHeaders.Add("user-agent", "myapp");
}
public async Task SendGetRequest(string param)
{
var response = await client.GetAsync(httpLink + param + httpSuffix);
return response.Content.ReadAsStringAsync();
}
}
So I obviously get a compilation error when returning the ReadAsStringAsync function, but I want my viewmodel to get the response from this function. My viewmodel is as follows:
public SearchViewModel()
{
httpService = (App.Current as App).Container.GetService<IHttpService>();
SearchCommand = new RelayCommand(() =>
{
// Will need to do some proper validation here at some point
var response = await httpService.SendGetRequest(httpStringToSend);
});
}
I'm sure i'm missing something but i'm not entirely sure what...
ReadAsStringAsync is asynchronous and needs to be awaited.
You also need to use the generic Task<T> as your return type rather than Task, because your asynchronous operation is returning a value i.e. string.
public async Task<string> SendGetRequest(string param)
{
var response = await client.GetAsync(httpLink + param + httpSuffix);
return await response.Content.ReadAsStringAsync();
}
HttpResponseMessage is also IDisposable so you should add a using block:
public async Task<string> SendGetRequest(string param)
{
using (var response = await client.GetAsync(httpLink + param + httpSuffix))
{
return await response.Content.ReadAsStringAsync();
}
}
I am trying to write a function which uses Task and TaskCompletion.
My problem is that after login, the result is not returned. I used similar code before and it was working. I do not know what causes for this situation.
public async Task<byte[]> Sign(byte[] documentContent)
{
var service = new SignServiceWrapper("https://example.com?wsdl");
var loginResult = await Task.Run(() => service.Login(loginRequest));
//....
}
and my SignServiceWrapper class
public class SignServiceWrapper
{
private static string _webServiceUrl;
private BrokerClientClient client;
public SignServiceWrapper(string webServiceUrl)
{
_webServiceUrl = webServiceUrl;
}
public Task<loginResponse> Login(loginRequest request)
{
var tcs = new TaskCompletionSource<loginResponse>();
ClientGenerator.WebServiceUrl = _webServiceUrl;
ClientGenerator.InitializeService();
client = ClientGenerator.ServiceClient;
client.loginCompleted += (sender, loginResult) =>
{
if (loginResult.Error != null)
tcs.SetException(loginResult.Error);
else
tcs.TrySetResult(loginResult.Result);
};
client.loginAsync(request);
return tcs.Task;
}
// ...
}
If I call my login function like that it works
var loginResult = Task.Run(() => service.Login(loginRequest));
loginResult.Wait();
I know that there is kind of a deadlock but I don't know how to solve this here and which object.
Here is a working .NET Fiddle.
I think your .Login method is trying to do too much. The first thing that I noticed (and can only imagine how it's implemented) is the static ClientGenerator, that has static mutable state. This which is alarming and a very specific code smell. I would love to see what the client itself looks like and how that is implemented as that would certainly help to better answer this question.
Based on what you shared thus far (and assuming that the client.loginAsync returns a Task<loginResponse>), I would say that you could do the following:
public class SignServiceWrapper
{
private static string _webServiceUrl;
private BrokerClientClient client;
public SignServiceWrapper(string webServiceUrl)
{
_webServiceUrl = webServiceUrl;
}
public Task<loginResponse> LoginAsync(loginRequest request)
{
ClientGenerator.WebServiceUrl = _webServiceUrl;
ClientGenerator.InitializeService();
client = ClientGenerator.ServiceClient;
return client.loginAsync(request);
}
// ...
}
You could then consume this as such:
public async Task<byte[]> Sign(byte[] documentContent)
{
var service = new SignServiceWrapper("https://example.com?wsdl");
var loginResult = await service.LoginAsync(loginRequest);
//...
}
If the client.loginAsync doesn't return what you're looking for, then you'll need to approach this doing something similar to your current approach. Or if you are locked in to the event-based async pattern, you have other considerations - like whether or not you want to support cancellation, IsBusy, progress, incremental results and if you have the ability to have the event args inherit the System.ComponentModel.AsyncCompletedEventArgs, etc...
One final consideration, if the client.loginAsync is Task returning, even if it doesn't return the loginResponse you need to await it like so:
public async Task<loginResponse> Login(loginRequest request)
{
var tcs = new TaskCompletionSource<loginResponse>();
ClientGenerator.WebServiceUrl = _webServiceUrl;
ClientGenerator.InitializeService();
client = ClientGenerator.ServiceClient;
client.loginCompleted += (sender, loginResult) =>
{
if (loginResult.Error != null)
tcs.SetException(loginResult.Error);
else
tcs.TrySetResult(loginResult.Result);
};
await client.loginAsync(request);
return tcs.Task;
}
Update
After discussion with OP this .NET Fiddle seemed to align with his needs.
Change var loginResult = await Task.Run(() =>service.Login(loginRequest));
To var loginResult = await service.Login(loginRequest);
There is problem with my code. How can I solve this problem? This problem in await operator.
public MyModel()
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://api.vkontakte.ru/method/video.get?uid=219171498&access_token=d61b93dfded2a37dfcfa63779efdb149653292636cac442e53dae9ba6a049a75637143e318cc79e826149");
string googleSearchText = await response.Content.ReadAsStringAsync();
JObject googleSearch = JObject.Parse(googleSearchText);
IList<JToken> results = googleSearch["response"].Children().Skip(1).ToList();
IList<MainPage1> searchResults = new List<MainPage1>();
foreach (JToken result in results)
{
MainPage1 searchResult = JsonConvert.DeserializeObject<MainPage1>(result.ToString());
searchResults.Add(searchResult);
}
You're trying to use await within a constructor. You can't do that - constructors are always synchronous.
You can only use await within a method or anonymous function with the async modifier; you can't apply that modifier to constructors.
One approach to fixing this would be to create a static async method to create an instance - that would do all the relevant awaiting, and then pass the results to a simple synchronous constructor. Your callers would then need to handle this appropriately, of course.
public static async Task<MyModel> CreateInstance()
{
string googleSearchText;
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(...))
{
googleSearchText = await response.Content.ReadAsStringAsync();
}
}
// Synchronous constructor to do the rest...
return new MyModel(googleSearchText);
}
You can't use await in the constructor of a class.
An async method returns a Task object which can be executed async. A constructor does not have a return type and thus can't return a Task object, and thus can't be awaited.
A simple fix for this problem is create a Init function:
public MyModel()
{
}
public async Task Init()
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://api.vkontakte.ru/method/video.get?uid=219171498&access_token=d61b93dfded2a37dfcfa63779efdb149653292636cac442e53dae9ba6a049a75637143e318cc79e826149");
string googleSearchText = await response.Content.ReadAsStringAsync();
JObject googleSearch = JObject.Parse(googleSearchText);
IList<JToken> results = googleSearch["response"].Children().Skip(1).ToList();
IList<MainPage1> searchResults = new List<MainPage1>();
foreach (JToken result in results)
{
MainPage1 searchResult = JsonConvert.DeserializeObject<MainPage1>(result.ToString());
searchResults.Add(searchResult);
}
}
Then when you create your model:
var model = new MyModel();
await model.Init();