I've randomly issue with my code. I've list of 10 000 urls for testing and randomly it never finished, message "FINISH" isn't printed without any error/exception. I'm fighting with this issue 2 days without solution, any suggest?
Short code to reporduce the problem:
urlist for testing https://www.dropbox.com/s/0x6w1ej0fuxedw9/url.txt
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ProblemApp
{
static class AsyncForEach
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
}
public class Form1 : Form
{
public Form1()
{
Load += OnLoad;
}
private void OnLoad(object sender, EventArgs eventArgs)
{
Go();
}
private async Task<bool> Open(string openurl)
{
Uri uri;
if (!Uri.TryCreate(openurl, UriKind.Absolute, out uri)) return false;
try
{
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(100));
using (var httpClient = new HttpClient())
{
using (var req = new HttpRequestMessage(HttpMethod.Get, new Uri(openurl)))
{
using (
HttpResponseMessage response =
await
httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead,
timeout.Token).ConfigureAwait(false))
{
if (response != null &&
(response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.NotFound))
{
using (
Stream responseStream =
await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
int read;
int offset = 0;
var rawResponse = new byte[8192];
while (
(read =
await
responseStream.ReadAsync(rawResponse, 0, 8192, timeout.Token)
.ConfigureAwait(false)) != 0)
{
offset += read;
if (offset > 1024000)
{
return false;
}
}
}
}
else
{
return false;
}
}
}
}
}
catch (WebException ex)
{
// Console.WriteLine(ex.Message);
} catch (HttpRequestException ex)
{
// Console.WriteLine(ex.Message);
}
return true;
}
private async void Go()
{
IEnumerable<string> lines = File.ReadLines("url.txt");
await
lines.ForEachAsync(500,
async line =>
{
bool result = await Open(line).ConfigureAwait(false);
});
Console.WriteLine("FINISH");
}
}
}
ForEachAsync from last example: http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx
With HttpCompletionOption.ResponseContentRead program is working OK
Related
Following on:
How to download files using HttpClient with a ProgressBar?
The project is WinForms .NET 6
How to solve the erro :
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 2: cannot convert from 'System.Threading.Tasks.ParallelLoopState' to 'System.Threading.CancellationToken' WinFormsApp1 D:\Csharp\WinFormsApp1\ResourceDownloader.cs 63 Active
On line number 63
var dataBytes = await client.Value.GetByteArrayAsync(site, token);
The full class code :
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace WinFormsApp1
{
public class ResourceDownloader
{
private static Lazy<HttpClient> client = new(() => {
HttpClientHandler handler = CreateHandler(autoRedirect: true);
var client = new HttpClient(handler, true) { Timeout = TimeSpan.FromSeconds(60) };
client.DefaultRequestHeaders.Add("User-Agent", #"Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0");
client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
client.DefaultRequestHeaders.ConnectionClose = true;
return client;
}, true);
private static HttpClientHandler CreateHandler(bool autoRedirect)
{
return new HttpClientHandler()
{
AllowAutoRedirect = autoRedirect,
CookieContainer = new CookieContainer(),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
}
public record Website(string Url, byte[]? Data, bool Completed = true, Exception? Ex = null);
public record ProgressReport(Website Site, int PercentageComplete);
private static object syncObj = new object();
private static ConcurrentBag<Website> processed = default!;
private static int progressCount = 0;
private static int totalCount = 0;
public static bool IsBusy { get; internal set; } = false;
public static async Task<List<Website>> Download(IProgress<ProgressReport> progress, IList<string> sites, CancellationTokenSource cts)
{
IsBusy = true;
processed = new ConcurrentBag<Website>();
progressCount = 0;
totalCount = sites.Count;
try
{
ParallelOptions options = new()
{
MaxDegreeOfParallelism = 8,
CancellationToken = cts.Token
};
await Parallel.ForEach(sites, options, async (site, token) => {
try
{
var dataBytes = await client.Value.GetByteArrayAsync(site, token);
ReportProgress(progress, dataBytes, site, null);
}
catch (Exception ex)
{
ReportProgress(progress, null, site, ex);
}
});
}
// To Debug / Log
catch (TaskCanceledException) { Debug.Print("The operation was canceled"); }
finally { IsBusy = false; }
return processed.ToList();
}
private static void ReportProgress(IProgress<ProgressReport> progress, byte[]? data, string site, Exception? ex)
{
lock (syncObj)
{
progressCount += 1;
var percentage = progressCount * 100 / totalCount;
Website website = new(site, data, ex is null, ex);
processed.Add(website);
progress.Report(new ProgressReport(website, percentage));
}
}
}
}
Parallel.ForEach is not Task-aware and does not handle asynchronous workloads (correctly, at least), and overloads accepting handler with 2 parameters (like ForEach<TSource>(IEnumerable<TSource>, Action<TSource,ParallelLoopState>)) use ParallelLoopState as second parameter of the handler.
You need to use Parallel.ForEachAsync (for example this overload, which accepts Func<TSource, CancellationToken, ValueTask>) to correctly handle async workloads.
Data is being retrieve from the API succesfully, as I can see it here,
response
and then goes to the jsonstring, but never gets to the CantGet variable
I need it to be store in my property so I can use the value.
This is my API return:
[{"CantPremio":"70"}
Then this is my property:
using System;
using System.Collections.Generic;
using System.Text;
namespace ServLottery
{
public class GetCantPremio
{
public long CantPremio { get; set; }
}
}
This is the Get task
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
namespace ServLottery
{
public class RestClient
{
HttpClient client = new HttpClient();
public async Task<T> Get<T>(string URL)
{
try
{
var response = await client.GetAsync(URL);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var jsonstring = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonstring);
}
}
catch
{
}
return default(T);
}
}
}
Finally this is the call:
private async void GetCantDisponible()
{
try
{
RestClient client = new RestClient();
var CantGet = await client.Get<GetCantPremio>("https://servicentroapi.azurewebsites.net/api/GetNumber");
if (CantGet != null)
{
PremiosCantLocal = CantGet.CantPremio.ToString();
}
}
catch (Exception ex)
{
throw ex;
}
}
The api you are accessing is returning an array. So you must deserialize not a simple object but a list.
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<T>>(jsonstring);
Replace the line that deserializes with this one. Should solve the problem
Like kelvin said, set the List<T> for the json array. And then foreach the collection to get the CantPremio.
RestClient:
public class RestClient
{
HttpClient client = new HttpClient();
public async Task<List<T>> Get<T>(string URL)
{
try
{
var response = await client.GetAsync(URL);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var jsonstring = await response.Content.ReadAsStringAsync();
var s = Newtonsoft.Json.JsonConvert.DeserializeObject<List<T>>(jsonstring);
return s;
}
}
catch
{
}
return default(List<T>);
}
}
GetCantDisponible:
private async void GetCantDisponible()
{
try
{
RestClient client = new RestClient();
var CantGet = await client.Get<GetCantPremio>("https://servicentroapi.azurewebsites.net/api/GetNumber");
if (CantGet != null)
{
foreach (var item in CantGet)
{
var PremiosCantLocal = item.CantPremio.ToString();
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Screenshot:
As mentioned, your API is returning an array but you're trying to deserialize it to a single instance. I'd suggest changing the call site of your client to pass a list for the type parameter:
List<GetCantPremio> CantGet = await client.Get<List<GetCantPremio>>("https://servicentroapi.azurewebsites.net/api/GetNumber");
Note that CantGet is now a List. If you are only looking for one object you could just add on a FirstOrDefault():
GetCantPremio CantGet = await client.Get<List<GetCantPremio>>("https://servicentroapi.azurewebsites.net/api/GetNumber")?.FirstOrDefault();
I am testing an HTTP Request to my API while the server is down.
It should receive an error response, but instead, it returns null and it gives me this exception:
System.ObjectDisposedException: Cannot access a closed Stream.
This happens in Android only, iOS I get an error response. this is my code:
using (HttpClient client = new HttpClient())
{
try
{
//pedido de token
var loginInfo = new StringContent(JsonConvert.SerializeObject(userAuth).ToString(), Encoding.UTF8, "application/json");
var requestToken = await client.PostAsync(URLs.url + URLs.getToken, loginInfo);
var receiveToken = await requestToken.Content.ReadAsStringAsync();
It doesn't reach the ReadAsString, throws the exception in the PostAsync.
Don't dispose of HttpClient. It is designed to be reused and handle multiple simultaneous requests.
Here's more info about how HttpClient works: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Here is a generic implementation that I use for all HttpClient services in my Xamarin.Forms apps:
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Http;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Xamarin.Forms;
namespace NameSpace
{
public abstract class BaseHttpClientService
{
#region Constant Fields
static readonly Lazy<JsonSerializer> _serializerHolder = new Lazy<JsonSerializer>();
static readonly Lazy<HttpClient> _clientHolder = new Lazy<HttpClient>(() => CreateHttpClient(TimeSpan.FromSeconds(30)));
#endregion
#region Fields
static int _networkIndicatorCount = 0;
#endregion
#region Events
public static event EventHandler<string> HttpRequestFailed;
#endregion
#region Properties
static HttpClient Client => _clientHolder.Value;
static JsonSerializer Serializer => _serializerHolder.Value;
#endregion
#region Methods
protected static async Task<T> GetObjectFromAPI<T>(string apiUrl)
{
using (var responseMessage = await GetObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<T>(responseMessage).ConfigureAwait(false);
}
protected static async Task<HttpResponseMessage> GetObjectFromAPI(string apiUrl)
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.GetAsync(apiUrl).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
protected static async Task<TResponse> PostObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PostObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PostObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Post, apiUrl, requestData);
protected static async Task<TResponse> PutObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PutObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PutObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(HttpMethod.Put, apiUrl, requestData);
protected static async Task<TResponse> PatchObjectToAPI<TResponse, TRequest>(string apiUrl, TRequest requestData)
{
using (var responseMessage = await PatchObjectToAPI(apiUrl, requestData).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> PatchObjectToAPI<T>(string apiUrl, T requestData) => SendAsync(new HttpMethod("PATCH"), apiUrl, requestData);
protected static async Task<TResponse> DeleteObjectFromAPI<TResponse>(string apiUrl)
{
using (var responseMessage = await DeleteObjectFromAPI(apiUrl).ConfigureAwait(false))
return await DeserializeResponse<TResponse>(responseMessage).ConfigureAwait(false);
}
protected static Task<HttpResponseMessage> DeleteObjectFromAPI(string apiUrl) => SendAsync<object>(HttpMethod.Delete, apiUrl);
static HttpClient CreateHttpClient(TimeSpan timeout)
{
HttpClient client;
switch (Device.RuntimePlatform)
{
case Device.iOS:
case Device.Android:
client = new HttpClient();
break;
default:
client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip });
break;
}
client.Timeout = timeout;
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
return client;
}
static async Task<HttpResponseMessage> SendAsync<T>(HttpMethod httpMethod, string apiUrl, T requestData = default)
{
using (var httpRequestMessage = await GetHttpRequestMessage(httpMethod, apiUrl, requestData).ConfigureAwait(false))
{
try
{
UpdateActivityIndicatorStatus(true);
return await Client.SendAsync(httpRequestMessage).ConfigureAwait(false);
}
catch (Exception e)
{
OnHttpRequestFailed(e.Message);
Report(e);
throw;
}
finally
{
UpdateActivityIndicatorStatus(false);
}
}
}
protected static void UpdateActivityIndicatorStatus(bool isActivityIndicatorDisplayed)
{
if (isActivityIndicatorDisplayed)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = true);
_networkIndicatorCount++;
}
else if (--_networkIndicatorCount <= 0)
{
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage.IsBusy = false);
_networkIndicatorCount = 0;
}
}
static async ValueTask<HttpRequestMessage> GetHttpRequestMessage<T>(HttpMethod method, string apiUrl, T requestData = default)
{
var httpRequestMessage = new HttpRequestMessage(method, apiUrl);
switch (requestData)
{
case T data when data.Equals(default(T)):
break;
case Stream stream:
httpRequestMessage.Content = new StreamContent(stream);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
break;
default:
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(requestData)).ConfigureAwait(false);
httpRequestMessage.Content = new StringContent(stringPayload, Encoding.UTF8, "application/json");
break;
}
return httpRequestMessage;
}
static async Task<T> DeserializeResponse<T>(HttpResponseMessage httpResponseMessage)
{
httpResponseMessage.EnsureSuccessStatusCode();
try
{
using (var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var reader = new StreamReader(contentStream))
using (var json = new JsonTextReader(reader))
{
if (json is null)
return default;
return await Task.Run(() => Serializer.Deserialize<T>(json)).ConfigureAwait(false);
}
}
catch (Exception e)
{
Report(e);
throw;
}
}
static void OnHttpRequestFailed(string message) => HttpRequestFailed?.Invoke(null, message);
static void Report(Exception e, [CallerMemberName]string callerMemberName = "") => Debug.WriteLine(e.Message);
#endregion
}
}
I had the same problem (it worked fine in UWP but this error on Android).
Please see this linked question for what fixed it for me: HttpClient.SendAsync throws ObjectDisposedException on Xamarin.Forms Android but not on UWP
All the examples using Microsoft WebSockets over a web-api that I've seen so far use IIS, the implementation is on the get method the HTTP connection is upgraded to a websocket and an instance of websocket handler is passed to the HTTPContext
public HttpResponseMessage Get() {
if (HttpContext.Current.IsWebSocketRequest) {
var noteHandler = new NoteSocketHandler();
HttpContext.Current.AcceptWebSocketRequest(noteHandler);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
What am trying to achieve is to do the same on an OWIN pipeline. The problem am facing is the connection is being upgraded to use Websockets but it is not utilizing my websocket handler. Where am I going wrong? Please suggest.
Controller utilizing OwinContext (Followed the example WebSockets in Nancy using OWIN),
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, async wsEnv => {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
//should I pass the handler to an event?
var handler = new NoteSocketHAndler();
});
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
Handler Code:
using System;
using Socket = Microsoft.Web.WebSockets;
using Newtonsoft.Json;
public class NoteSocketHandler : Socket.WebSocketHandler {
private static Socket.WebSocketCollection connections = new Socket.WebSocketCollection();
public NoteSocketHandler() {
}
public override void OnOpen() {
connections.Add(this);
}
public override void OnClose() {
connections.Remove(this);
}
public override void OnMessage(string message) {
ChatMessage chatMessage = JsonConvert.DeserializeObject<ChatMessage>(message);
foreach (var connection in connections) {
connection.Send(message);
}
}
}
I finally figured out how to resolve the issue. You can find the code below, also I've written a basic app which uses websockets on OWIN.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin;
namespace NoteApp.WebService.Controller {
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NoteApp.WebService.Handler;
using WebSocketAccept = System.Action<
System.Collections.Generic.IDictionary<string, object>, // WebSocket Accept parameters
System.Func< // WebSocketFunc callback
System.Collections.Generic.IDictionary<string, object>, // WebSocket environment
System.Threading.Tasks.Task>>;
using WebSocketCloseAsync = System.Func<
int, // closeStatus
string, // closeDescription
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
using WebSocketReceiveAsync = System.Func<
System.ArraySegment<byte>, // data
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task<
System.Tuple< // WebSocketReceiveTuple
int, // messageType
bool, // endOfMessage
int>>>; // count
// closeStatusDescription
using WebSocketReceiveResult = System.Tuple<int, bool, int>;
using WebSocketSendAsync = System.Func<
System.ArraySegment<byte>, // data
int, // message type
bool, // end of message
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
public class NoteController : ApiController {
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, ProcessSocketConnection);
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
private async Task ProcessSocketConnection(IDictionary<string, object> wsEnv) {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
//pass the sendasync tuple and the cancellation token to the handler. The handler uses the sendasync method to send message. Each connected client has access to this
var handler = new NoteSocketHandler(wsSendAsync, CancellationToken.None);
handler.OnOpen();
var buffer = new ArraySegment<byte>(new byte[100]);
try {
object status;
while (!wsEnv.TryGetValue("websocket.ClientCloseStatus", out status) || (int)status == 0) {
WebSocketReceiveResult webSocketResultTuple = await wsRecieveAsync(buffer, CancellationToken.None);
int count = webSocketResultTuple.Item3;
handler.OnMessage(Encoding.UTF8.GetString(buffer.Array, 0, count));
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw ex;
}
handler.OnClose();
await wsCloseAsync((int)WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
T GetValue<T>(IDictionary<string, object> env, string key) {
object value;
return env.TryGetValue(key, out value) && value is T ? (T)value : default(T);
}
}
}
I'm new in C# and I'd like to crate library for my RESTlike API in Windows Phone application.
My api structure is sort of
http://mysiteurl/api/method_name.json
So I would like to call api with (method_name, params) and return specified class.
When I launch this code on my Windows Phone 8.1 program freeze and nothing happens. Also I can see anything on serverside (any call)
API.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SampleProg.Model;
namespace SampleProg.Lib
{
public class Api
{
public async Task<VersionClass> versionInfo()
{
Debug.WriteLine("versionInfo()");
var result = await this.Post<VersionClass>("version", "{}", null);
Debug.WriteLine(result);
return result;
}
public async Task<TReturnType> Post<TReturnType>(string methodName, string data, Dictionary<string, string> headers)
{
var webClient = new WebClient();
webClient.Headers["Content-Type"] = "application/json";
webClient.Encoding = Encoding.UTF8;
var uri = new Uri(String.Format("http://mysiteurl/api/{0}.json", methodName));
if (headers != null)
{
foreach (var key in headers.Keys)
{
webClient.Headers[key] = headers[key];
}
}
return await Post<TReturnType>(webClient, uri, data);
}
private async Task<TReturnType> Post<TReturnType>(WebClient webClient, Uri uri, string jsonData)
{
TReturnType returnObject = default(TReturnType);
var taskCompletionSource = new TaskCompletionSource<TReturnType>();
webClient.UploadStringCompleted += (s, e) =>
{
var result = e.Result;
try
{
Debug.WriteLine(result);
returnObject = JsonConvert.DeserializeObject<TReturnType>(result);
taskCompletionSource.SetResult(returnObject);
}
catch (Exception ex)
{
var newEx = new Exception(
string.Format("Failed to deserialize server response: {0}", result), ex);
taskCompletionSource.SetException(newEx);
}
};
webClient.UploadStringAsync(uri, "POST", jsonData);
return await taskCompletionSource.Task;
}
}
}
App.xaml.cs (I call this from public App() {} ):
var api = new Api();
var apiTask = api.versionInfo();
VersionClass version = (VersionClass)apiTask.Result;
Debug.WriteLine(version.num);
VersionClass.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace SampleProg.Model
{
public class VersionClass
{
[JsonProperty("version")]
public int num { get; set; }
}
}
Could you tell me what is wrong? I use this code (at bottom) http://sandor68.rssing.com/chan-11557297/all_p1.html as base
This:
VersionClass version = (VersionClass) apiTask.Result;
Is deadlocking your app.
What's happening is the SynchronizationContext is trying to marshal control back to the UI thread after the await inside your inner method.
You should always await on a Task:
VersionClass version = (VersionClass) await apiTask;
Also, be aware that your wrapping of UploadStringAsync which returns a Task is already made for you OOTB via WebClient.UploadStringTaskAsync
Edit
If you choose to continue your version of Post, you should not await on the returned Task, you should let the user do that, and unregister the handler from the UploadStringCompleted:
private async Task<TReturnType> Post<TReturnType>(WebClient webClient, Uri uri, string jsonData)
{
TReturnType returnObject = default(TReturnType);
var taskCompletionSource = new TaskCompletionSource<TReturnType>();
UploadStringCompletedEventHandler handler = null;
handler = (s, e) =>
{
webClient.UploadStringCompleted -= handler;
var result = e.Result;
try
{
Debug.WriteLine(result);
returnObject = JsonConvert.DeserializeObject<TReturnType>(result);
taskCompletionSource.SetResult(returnObject);
}
catch (Exception ex)
{
var newEx = new Exception(
string.Format("Failed to deserialize server response: {0}", result), ex);
taskCompletionSource.SetException(newEx);
}
};
webClient.UploadStringCompleted += handler;
webClient.UploadStringAsync(uri, "POST", jsonData);
return taskCompletionSource.Task;
}