23

How I can convert my traditional HttpWebRequest "POST" call with Async / Await pattern, Here with this I am attaching my current code, Any one please help me to convert this code using Async / Await pattern for windows phone 8.

public void GetEnvironmentVariables(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback)
{
    CredentialsCallback = getResultCallback;
    ErrorCallback = getErrorCallback;
    var uri = new Uri(BaseUri);
    var request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = "POST";
    request.ContentType = "application/json";
    var jsonObject = new JObject
    {
        new JProperty("apiKey",_api),
        new JProperty("affiliateId",_affid),
    };
    var serializedResult = JsonConvert.SerializeObject(jsonObject);
    byte[] requestBody = Encoding.UTF8.GetBytes(serializedResult);

    request.BeginGetRequestStream(GetRequestStreamCallback, new object[] { request, requestBody });

}

private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
{
    var request = (HttpWebRequest)((object[])asynchronousResult.AsyncState)[0];
    using (var postStream = request.EndGetRequestStream(asynchronousResult))
    {
        var byteArray = (byte[])((object[])asynchronousResult.AsyncState)[1];

        // Write to the request stream.
        postStream.Write(byteArray, 0, byteArray.Length);

    }
    request.BeginGetResponse(GetResponseCallback, request);
}

private void GetResponseCallback(IAsyncResult asynchronousResult)
{
    var request = (HttpWebRequest)asynchronousResult.AsyncState;
    try
    {
        var response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
        if (response != null)
        {
            var reader = new StreamReader(response.GetResponseStream());
            string responseString = reader.ReadToEnd();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err))
                CredentialsCallback(Credentails);
            else
            {
                if (Credentails != null)
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
            }
        }
    }
    catch (WebException we)
    {
            var reader = new StreamReader(we.Response.GetResponseStream());
            string responseString = reader.ReadToEnd();
            Debug.WriteLine(responseString);
            ErrorCallback(we);

    }
} 
Ian Griffiths
  • 14,302
  • 2
  • 64
  • 88
Nitha Paul
  • 1,401
  • 1
  • 14
  • 30
  • 2
    [`What have you tried`](http://mattgemmell.com/2008/12/08/what-have-you-tried/)? What difficulties did you encounter with your code? – Darin Dimitrov Jan 29 '13 at 07:05
  • I am new to this world and I tired an implementation with "TaskCompletionSource" by setting the response object. But it seems like something from the root I am missing. I was looking for a perfect implementation by an experienced one, if I get such support it would be helpful for a student like me. – Nitha Paul Jan 29 '13 at 07:09
  • Don't hesitate to show the code you have so far. This way people will be able to see and explain what is wrong with it and hopefully provide you with a better alternatives. But I am afraid that right now you are asking for other people doing the job for you and StackOverflow is not the right place for such kind of requests. – Darin Dimitrov Jan 29 '13 at 07:10
  • No I didn't mean it. As I told I pretty new to this environment and didn't know much about the language features. If I put my code here the one who reads the code may misunderstand, because I don't know how to explain my real problem. If you have the patience to help me, then give me the guidelines otherwise just ignore my question. – Nitha Paul Jan 29 '13 at 07:16

3 Answers3

20

Since Windows Phone 8 doesn't seem to offer the TAP methods you need such as GetRequestStreamAsync the first thing to do is write a little wrapper to provide them for yourself:

public static class WebRequestAsyncExtensions
{
    public static Task<Stream> GetRequestStreamAsync(this WebRequest request)
    {
        return Task.Factory.FromAsync<Stream>(
            request.BeginGetRequestStream, request.EndGetRequestStream, null);
    }

    public static Task<WebResponse> GetResponseAsync(this WebRequest request)
    {
        return Task.Factory.FromAsync<WebResponse>(
            request.BeginGetResponse, request.EndGetResponse, null);
    }
}

Note the use of Task.Factory.FromAsync - this is the preferred way to get an await-friendly wrapper around an APM-based async API such as those offered by WebRequest. This is far more efficient than using Task.Factory.StartNew as suggested by someone else, because that would spin up a new thread, whereas this won't need to.

With this in place, you can now write your code in the same way you would on platforms where these TAP-style methods are available (e.g. Windows 8 store apps, desktop apps, etc.):

public async Task GetEnvironmentVariablesAsync(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback)
{
    CredentialsCallback = getResultCallback;
    ErrorCallback = getErrorCallback;
    var uri = new Uri(BaseUri);
    var request = (HttpWebRequest) WebRequest.Create(uri);
    request.Method = "POST";
    request.ContentType = "application/json";
    var jsonObject = new JObject
    {
        new JProperty("apiKey",_api),
        new JProperty("affiliateId",_affid),
    };
    var serializedResult = JsonConvert.SerializeObject(jsonObject);
    byte[] requestBody = Encoding.UTF8.GetBytes(serializedResult);

    // ASYNC: using awaitable wrapper to get request stream
    using (var postStream = await request.GetRequestStreamAsync())
    {
        // Write to the request stream.
        // ASYNC: writing to the POST stream can be slow
        await postStream.WriteAsync(requestBody, 0, requestBody.Length);
    }

    try
    {
        // ASYNC: using awaitable wrapper to get response
        var response = (HttpWebResponse) await request.GetResponseAsync();
        if (response != null)
        {
            var reader = new StreamReader(response.GetResponseStream());
            // ASYNC: using StreamReader's async method to read to end, in case
            // the stream i slarge.
            string responseString = await reader.ReadToEndAsync();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err))
                CredentialsCallback(Credentails);
            else
            {
                if (Credentails != null)
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
            }
        }
    }
    catch (WebException we)
    {
        var reader = new StreamReader(we.Response.GetResponseStream());
        string responseString = reader.ReadToEnd();
        Debug.WriteLine(responseString);
        ErrorCallback(we);

    }
}

Note the four lines with // ASYNC: comments - these show where I've made changes. I've collapsed your method down to one, because that's a) possible once you're using async and await and b) much easier than trying to pass things from one method to the next using state arguments.

Notice that the second and fourth of these actually makes async some things you were previously doing synchronously: writing data into the request stream, and reading data out of the response stream. For a small request this probably doesn't matter, but if large amounts of data are being transferred, a synchronous call to Write or ReadToEnd may block. Fortunately, although Windows Phone 8 appears to be missing the TAP methods on WebRequest, it does offer them on Stream and StreamReader so this works without needing to write any extension methods.

Ian Griffiths
  • 14,302
  • 2
  • 64
  • 88
  • 6
    By the way, you asked for a answers from "credible and/or official sources". In answer to that, I wrote the most recent edition of O'Reilly's "Programming C#" - a complete rewrite. I also wrote Pluralsight's course on asynchronous use of the Task Parallel Library, and their course on the new async language features in C# 5. – Ian Griffiths Feb 05 '13 at 15:54
  • 1
    @IanGriffiths, I'm adding async to an existing WebClient wrapper lib and this has been helpful: thanks. I'm used to wrapping all my `Stream`s and `Reader`s with a `using`, but I see you don't do that here. Is that because Windows 8 Phone is missing this, it's bad practice with async, or just not worthwhile? – Brad Sep 04 '14 at 11:18
  • 1
    @Brad I was basing my code more or less directly on the code from the question. It uses a `using` statement with the request stream but doesn't bother with the response stream. My code does exactly the same. My goal was to make it as easy as possible to see how my code relates to the original code; fixing the lack of `using` statements would have made the connection harder to see. So that's why I left them out - doing the right thing would have obscured the specific point I was trying to illustrate. You should put them in for production code. – Ian Griffiths Sep 05 '14 at 13:51
  • @IanGriffiths, that's a great goal to keep in mind for answering questions. Thanks for the clarification. I was running into issues, but it ended up being not respecting the async context and causing deadlocks. – Brad Sep 05 '14 at 13:56
2

I'm new to the community, so here goes my first post. In this case, you can return anytype using a generic Task. This has worked well for me in the past.

Server Side

public class MyController : ApiController
{
    public Task<string> PostAsync()
    {
        return Task.Factory.StartNew(() =>
        {
            return "populate me with any type and data, but change the type in the response signature.";
        });
    }
}

Client Side

public class HomeController : Controller
{
    public Task<ViewResult> Index()
    {
        return Task.Factory.StartNew(() =>
        {
            var model = "use a provider, get some data, or something";
            return View(model);
        });
    }
}
user586588
  • 21
  • 2
  • 5
    Although this approach is appropriate when you need to wrap something fundamentally synchronous in an async wrapper, that's not the case for this example. The question involves an HttpWebRequest, which does actually provide async services, just not in the form expected by C# 5. It uses the old-school Async Programming Model (APM) which has been around since .NET 1.0. You can use the Task.Factory.FromAsync method to wrap APM operations efficiently as threadless tasks, which is a more efficient solution here. – Ian Griffiths Feb 05 '13 at 15:47
0

This should do the job:

    public async void GetEnvironmentVariables(Action<Credentials> getResultCallback, Action<Exception> getErrorCallback) {
        CredentialsCallback = getResultCallback;
        ErrorCallback = getErrorCallback;
        var uri = new Uri(BaseUri);
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.Method = "POST";
        request.ContentType = "application/json";

        var jsonObject = new JObject {
            new JProperty("apiKey", _api),
            new JProperty("affiliateId", _affid),
        };

        var serializedResult = JsonConvert.SerializeObject(jsonObject);
        var requestBody = Encoding.UTF8.GetBytes(serializedResult);

        var requestStream = request.GetRequestStream();
        requestStream.Write(requestBody, 0, requestBody.Length);

        await GetResponse(request);
    }

    private async Task GetResponse(WebRequest request) {
        Stream resStream = null;

        try {
            var response = await request.GetResponseAsync();

            if (response == null) {
                return;
            }

            resStream = response.GetResponseStream();
            if (resStream == null) {
                return;
            }

            var reader = new StreamReader(resStream);
            var responseString = await reader.ReadToEndAsync();
            Credentails = JsonConvert.DeserializeObject<Credentials>(responseString);
            if (Credentails != null && string.IsNullOrEmpty(Credentails.Err)) {
                CredentialsCallback(Credentails);
            }
            else {
                if (Credentails != null) {
                    ErrorCallback(new Exception(string.Format("Error Code : {0}", StorageCredentails.Err)));
                }
            }
        }
        catch (WebException we) {
            if (resStream != null) {
                var reader = new StreamReader(resStream);
                var responseString = reader.ReadToEnd();
                Debug.WriteLine(responseString);
            }
            ErrorCallback(we);
        }
    }
Ian Griffiths
  • 14,302
  • 2
  • 64
  • 88
lboshuizen
  • 2,746
  • 17
  • 20
  • 2
    The question was regarding Windows Phone 8, which does not provide the GetResponseAsync method that you're using here. (You can write your own version of that though, as my answer shows.) Also, you've only made two of the three potentially show bits asynchronous: the call to GetResponseStream could also block. – Ian Griffiths Feb 05 '13 at 15:52
  • 1
    ...in fact I've realised there are four places you can go async, The Write to the request stream could also potentially block too. (I've edited my reply to reflect that.) – Ian Griffiths Feb 05 '13 at 16:00