Correct way to make async web request - c#

Hi I need to write a proper async web request function in C# to get some data in JSON format and deserialize it in strongly typed object. I came up with this solution:
public async Task<TReturnType> MakeAsyncRequest<TReturnType>(string url) where TReturnType : class
{
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
var response = await request.GetResponseAsync();
var json = await Task.Run(() => ReadStreamFromResponse(response));
var deserializedObject = await Task.Run(() => JsonConvert.DeserializeObject<TReturnType>(json));
return deserializedObject;
}
catch (Exception)
{
// TODO: Handle exception here
throw;
}
}
private string ReadStreamFromResponse(WebResponse response)
{
using (var responseStream = response.GetResponseStream())
using (var sr = new StreamReader(responseStream))
{
return sr.ReadToEnd();
}
}
And usage would be like this:
var myData = await MakeAsyncRequest<IEnumerable<SomeObject>>(http://example.com/api/?getdata=all);
The issue I'm facing is that sometimes my web request fires and never returns, is there a proper way to handle that? Maybe playing with timers is an option but I don't like to go there.
Also I would be thankful if you could improve on my function, maybe there is a better way to do things and I just don't see it.

Related

HttpClient GetAsync not working as expected

When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time

Using async to load form data in background - MVVM

I can't seem to get async to work the way I want. I'm pretty unfamiliar with it in general, but my research has led me to it. Here's my situation: I have a XAML form I'm loading that needs data from a web service. I can get the data, no problem. The issue is that I want the form to load with a "Please wait. Data loading..." screen that I have made, but nothing loads until the query finishes which can take up to 10 seconds, which is a poor user experience. Can anyone let me know what I'm doing wrong?
In my ViewModel main section, I call LoadData() to get the ball rolling.
I also had a thought...is async what I really want here?
public async void LoadData()
{
IsLoading = Visibility.Visible;
MonitorData downloadInfo = await GetWebApiInfo();
try
{
AssignDataToControls(downloadInfo);
IsLoading = Visibility.Collapsed;
Console.WriteLine("Loaded successfully.");
}
catch (Exception e)
{
IsLoading = Visibility.Collapsed;
Console.WriteLine("Error: " + e);
}
}
private void AssignDataToControls(MonitorData mon)
{
MainPanel.Clear();
mon.MainPanel.ToList().ForEach(x => MainPanel.Add(x));
Information = mon.Information;
MonitorText = mon.MonitorText;
ProgressData = mon.progList;
}
public async Task<MonitorData> GetWebApiInfo()
{
main = new MonitorData();
var url = "::::::::WEB API ADDRESS::::::::";
var request = (HttpWebRequest)WebRequest.Create(url);
//Task<HttpWebRequest> req = await (HttpWebRequest)WebRequest.Create(url);
//HttpWebRequest request = await GetWebRequest(url);
WebResponse response = request.GetResponse();
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream, Encoding.Unicode);
string responseFromServer = reader.ReadToEnd();
var deserializer = new JavaScriptSerializer();
main = deserializer.Deserialize<MonitorData>(responseFromServer);
reader.Dispose();
response.Dispose();
return main;
}
The compiler will tell you exactly what's wrong with that approach. Specifically, your GetWebApiInfo method is going to run synchronously because you never use await.
In this case, you can use HttpClient to download the information asynchronously. It's a lot easier than mucking around with WebResponse and stuff:
private static readonly HttpClient _client = new HttpClient();
public async Task<MonitorData> GetWebApiInfo()
{
var url = "::::::::WEB API ADDRESS::::::::";
string responseFromServer;
using (var dataStream = await _client.GetStreamAsync())
using (var reader = new StreamReader(dataStream, Encoding.Unicode))
responseFromServer = await reader.ReadToEndAsync();
var deserializer = new JavaScriptSerializer();
return deserializer.Deserialize<MonitorData>(responseFromServer);
}
In general, when you want to "make something async", you should start from the lowest-level APIs. E.g., don't start by marking GetWebApiInfo with async; start with the actual HTTP transfer and call it with await. Then let async/await grow from there.
You may find my NotifyTask<T> type helpful; it allows you to do things like show/hide busy indicators with data binding.

How to use HttpClient without async

Hello I'm following to this guide
static async Task<Product> GetProductAsync(string path)
{
Product product = null;
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}
return product;
}
I use this example on my code and I want to know is there any way to use HttpClient without async/await and how can I get only string of response?
Thank you in advance
is there any way to use HttpClient without async/await and how can I get only string of response?
HttpClient was specifically designed for asynchronous use.
If you want to synchronously download a string, use WebClient.DownloadString.
Of course you can:
public static string Method(string path)
{
using (var client = new HttpClient())
{
var response = client.GetAsync(path).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
return responseContent.ReadAsStringAsync().GetAwaiter().GetResult();
}
}
}
but as #MarcinJuraszek said:
"That may cause deadlocks in ASP.NET and WinForms. Using .Result or
.Wait() with TPL should be done with caution".
Here is the example with WebClient.DownloadString
using (var client = new WebClient())
{
string response = client.DownloadString(path);
if (!string.IsNullOrEmpty(response))
{
...
}
}

How to await a byte[]

I have a MVC 4 app. One of the things it does is send an email by calling another service using the WebClient like so:
var serializer = new JavaScriptSerializer();
string requestData = serializer.Serialize(new
{
EventID = 1,
SubscriberID = studentId,
ToList = loginModel.EmailAddress,
TemplateParamVals = strStudentDetails,
});
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = await client.UploadData(URI, Encoding.UTF8.GetBytes(requestData));
}
I wanted to make use of UploadDataAsync but got the error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle.
So I thought of creating a wrapper function & making it asynchronous while still using UploadDataAsync.
private async Task<bool> SendEmailAsync(long studentId, LoginModel loginModel)
{
try
{
string URI = ConfigurationManager.AppSettings["CommunicationmanagerURL"] + "PostUserEvent";
var serializer = new JavaScriptSerializer();
string requestData = serializer.Serialize(new
{
EventID = 1,
SubscriberID = studentId,
ToList = loginModel.EmailAddress,
TemplateParamVals = strStudentDetails,
});
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = await client.UploadData(URI, Encoding.UTF8.GetBytes(requestData));
}
}
catch (Exception ex)
{
// Log exceptions ...
}
}
This doesn't build and gives the error:
Cannot await 'byte[]'
Any ideas how to solve this?
you can not await a synchrone function, you have to call the [..]Async one:
var result = await client.UploadDataAsync(URI, Encoding.UTF8.GetBytes(requestData));
The function you call returns a byte[], which is of course not awaitable.
UploadDataAsync returns a Task<byte[]> instead, this one you want to use:
http://msdn.microsoft.com/de-de/library/ms144225(v=vs.110).aspx
edit:
The Method seems to return void, which is kind of awaitable, too.. in terms of fire and forget.
The UploadData method you are using in your sample code is a synchronous operation. As the compiler is saying, you cannot await a synchronous operation. The problem is not the byte[] type but the operation itself.
If you want to use await, you'll need to use an asynchronous operation. WebClient offers two asynchronous versions of the UploadData method :
UploadDataAsync
UploadDataTaskAsync
To properly use the await functionnality, you'll need to use UploadDataTaskAsync.
EDIT : My first answer was about the warning you got from the compiler. What you are trying to do is wrap the call to the synchronous operation in a Task to make it asynchronous.
You can achieve this by creating your own Task and returning it :
private async Task<bool> SendEmailAsync(long studentId, LoginModel loginModel)
{
Task<bool> uploadTask = Task.Factory.StartNew<bool>(
() =>
{
try
{
string URI = ConfigurationManager.AppSettings["CommunicationmanagerURL"] + "PostUserEvent";
var serializer = new JavaScriptSerializer();
string requestData = serializer.Serialize(new
{
EventID = 1,
SubscriberID = studentId,
ToList = loginModel.EmailAddress,
TemplateParamVals = strStudentDetails,
});
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadData(URI, Encoding.UTF8.GetBytes(requestData));
return true;
}
}
catch (Exception ex)
{
// Log exceptions ...
return false;
}
});
return uploadTask;
}
The underlying reason is a bit complicated; it has to do with how EAP components (such as WebClient) notify their platform that they are doing work.
The easiest solution is to replace WebClient with HttpClient.

Getting the Response of a Asynchronous HttpWebRequest

Im wondering if theres an easy way to get the response of an async httpwebrequest.
I have already seen this question here but all im trying to do is return the response (which is usually json or xml) in the form of a string to another method where i can then parse it/ deal with it accordingly.
Heres some code:
I have these two static methods here which i think are thread safe as all the params are passed in and there are no shared local variables that the methods use?
public static void MakeAsyncRequest(string url, string contentType)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = contentType;
request.Method = WebRequestMethods.Http.Get;
request.Timeout = 20000;
request.Proxy = null;
request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
}
private static void ReadCallback(IAsyncResult asyncResult)
{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
try
{
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult))
{
Stream responseStream = response.GetResponseStream();
using (StreamReader sr = new StreamReader(responseStream))
{
//Need to return this response
string strContent = sr.ReadToEnd();
}
}
manualResetEvent.Set();
}
catch (Exception ex)
{
throw ex;
}
}
Assuming the problem is that you're having a hard time getting to the returned content, the easiest path would likely be using async/await if you can use it. Even better would be to switch to HttpClient if you're using .NET 4.5 since it's 'natively' async.
Using .NET 4 and C# 4, you can still use Task to wrap these and make it a bit easier to access the eventual result. For instance, one option would be the below. Note that it has the Main method blocking until the content string is available, but in a 'real' scenario you'd likely pass the task to something else or string another ContinueWith off of it or whatever.
void Main()
{
var task = MakeAsyncRequest("http://www.google.com", "text/html");
Console.WriteLine ("Got response of {0}", task.Result);
}
// Define other methods and classes here
public static Task<string> MakeAsyncRequest(string url, string contentType)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = contentType;
request.Method = WebRequestMethods.Http.Get;
request.Timeout = 20000;
request.Proxy = null;
Task<WebResponse> task = Task.Factory.FromAsync(
request.BeginGetResponse,
asyncResult => request.EndGetResponse(asyncResult),
(object)null);
return task.ContinueWith(t => ReadStreamFromResponse(t.Result));
}
private static string ReadStreamFromResponse(WebResponse response)
{
using (Stream responseStream = response.GetResponseStream())
using (StreamReader sr = new StreamReader(responseStream))
{
//Need to return this response
string strContent = sr.ReadToEnd();
return strContent;
}
}
"Even better would be to switch to HttpClient if you're using .Net 4.5 since it's 'natively' async." - absolutely right answer by James Manning.
This question was asked about 2 years ago. Now we have .Net Framework 4.5, which provides powerful asynchronous methods. Use HttpClient. Consider the following code:
async Task<string> HttpGetAsync(string URI)
{
try
{
HttpClient hc = new HttpClient();
Task<Stream> result = hc.GetStreamAsync(URI);
Stream vs = await result;
StreamReader am = new StreamReader(vs);
return await am.ReadToEndAsync();
}
catch (WebException ex)
{
switch (ex.Status)
{
case WebExceptionStatus.NameResolutionFailure:
MessageBox.Show("domain_not_found", "ERROR",
MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
//Catch other exceptions here
}
}
}
To use HttpGetAsync(), make a new method that is "async" too. async is required, because we need to use "await" in GetWebPage() method:
async void GetWebPage(string URI)
{
string html = await HttpGetAsync(URI);
//Do other operations with html code
}
Now if you want to get web-page HTML source code asynchronously, just call GetWebPage("web-address..."). Even Stream reading is asynchronous.
NOTE: to use HttpClient .Net Framework 4.5 is required. Also you need to add System.Net.Http reference in your project and add also "using System.Net.Http" for easy access.
For further reading how this approach works, visit: http://msdn.microsoft.com/en-us/library/hh191443(v=vs.110).aspx
Use of Async: Async in 4.5: Worth the Await
Once you go async, you can never go back. From there you only really have access to the async's callback. you can ramp up the complexity of this and do some threading & waithandles but that can be rather a painful endeavor.
Technically, you can also sleep the thread when you need to wait for the results, but I don't recommend that, you may as well do a normal http request at that point.
In C# 5 theyhave async/await commands that will make it easier to get the results of the async call to the main thread.
public static async Task<byte[]> GetBytesAsync(string url) {
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
using (var content = new MemoryStream())
using (var responseStream = response.GetResponseStream()) {
await responseStream.CopyToAsync(content);
return content.ToArray();
}
}
public static async Task<string> GetStringAsync(string url) {
var bytes = await GetBytesAsync(url);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}

Categories

Resources