4

I'm a little confused. I'm trying to post to my web service in an async manner, ideally I want to start the request, show a loading spinner on the UI and then when the async request finishes process the response and either show an error if there is one, or do another operation with the result.

Here is my code, I call the request here and pass some data in.

private void SignInExecute()
{

        if (Username == null || Password == null)
        {
            LoginOutput = "Please provide a username or password.";
        }
        else
        {
            this.webService.SendLoginRequest("http://localhost:3000/client_sessions", "username=" + Username + "&password=" + Password);

        }

}

And here is the actual web request code:

public void SendLoginRequest(string url, string postdata)
{
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.Method = "POST";

        request.ContentType = "application/x-www-form-urlencoded";

        request.Accept = "application/json";

        byte[] byteArray = Encoding.UTF8.GetBytes(postdata);

        request.CookieContainer = new CookieContainer();

        request.ContentLength = byteArray.Length;

        Stream dataStream = request.GetRequestStream();

        dataStream.Write(byteArray, 0, byteArray.Length);

        dataStream.Close();

        ((HttpWebRequest)request).KeepAlive = false;

        request.BeginGetResponse(new AsyncCallback(GetLoginResponseCallback), request);


    }

    private static void GetLoginResponseCallback(IAsyncResult asynchronousResult)
    {
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;

        // End the operation
        HttpWebResponse response =  (HttpWebResponse)request.EndGetResponse(asynchronousResult);
        Stream streamResponse = response.GetResponseStream();
        StreamReader streamRead = new StreamReader(streamResponse);
        string responseString = streamRead.ReadToEnd();

        Console.WriteLine(responseString);

        // Close the stream object
        streamResponse.Close();
        streamRead.Close();
        response.Close();
    }

So to sum up. I want to be able to return the response back to the object which originally gave the call for the web request to start. Any help?

benjgorman
  • 702
  • 10
  • 31

1 Answers1

2

You need to tell the BeginGetResponse to go back to the same context in which it was called via SynchronizationContext.Current. Something like this (the code does not have proper error checking, so you should think about that properly) (Also, Platinum Azure is correct that you should use a using to let your streams close properly (and guaranteed):

In your SendLoginRequest:

//Box your object state with the current thread context
object[] boxedItems = new []{request, SynchronizationContext.Current};
request.BeginGetResponse(new AsyncCallback(GetLoginResponseCallback), 
    boxedItems);

The getresponse code:

private static void GetLoginResponseCallback(IAsyncResult asynchronousResult)
{
//MY UPDATE 
    //Unbox your object state with the current thread context
    object[] boxedItems = asynchronousResult.AsyncState as object[];
    HttpWebRequest request = boxedItems[0] as HttpWebRequest;
    SynchronizationContext context = boxedItems[1] as SynchronizationContext;

    // End the operation
    using(HttpWebResponse response =  
        (HttpWebResponse)request.EndGetResponse(asynchronousResult))
    {
        using(Stream streamResponse = response.GetResponseStream())
        {
            using(StreamReader streamRead = new StreamReader(streamResponse))
            {
                string responseString = streamRead.ReadToEnd();

                Console.WriteLine(responseString);
//MY UPDATE
                //Make an asynchronous call back onto the main UI thread 
                //(context.Send for a synchronous call)
                //Pass responseString as your method parameter 
                //If you have more than one object, you will have to box again
                context.Post(UIMethodToCall, responseString);
            }
        }
    }
}

To implement your UI processing

public static void UIMethodCall(object ObjectState)
{
    String response = ObjectState as String;
    label1.Text = String.Format("Output: {0}", response);
    //Or whatever you need to do in the UI...
}

Now, I would test this out first, though. My understanding of Microsoft's implementation of event driven async was that the response was context-aware, and knew which context to return to. So, before jumping to the assumption that you are not on the same context, test it out by trying to update the UI (this will cause a thread context exception if you are not on the calling (UI) thread)

Justin Pihony
  • 66,056
  • 18
  • 147
  • 180
  • You should probably use try/finally or `using` to make sure the resources get closed properly in case of an exception. – Platinum Azure Mar 26 '12 at 18:15
  • @PlatinumAzure I have updated the code as that is better practice, you are right. You also reminded me to remark that the code does not have error checking. – Justin Pihony Mar 26 '12 at 18:20
  • I think I understand what you have wrote apart from the last line context.Post(UIMethodToCall, StateObjectThatUIWillActOn); Could you expand on that a little further? – benjgorman Mar 26 '12 at 18:36
  • @benjgorman Updated my code. let me know if that makes sense now? – Justin Pihony Mar 26 '12 at 18:41
  • That does yeah, would the UIMethodCall be in the same class as the web requests however. Or would it be in the same class as the SignInExecute method? There is also a NullException at this line. HttpWebRequest request = boxedItems[0] as HttpWebRequest; Which I'm not sure how to fix? – benjgorman Mar 26 '12 at 18:43
  • @benjgorman You can put the UIMethodCall wherever you want. If it is in the same class, then you can leave the code as is. If it is in another class, however, then you would call context.Post(ClassName.UIMethodCall, ... As to your question about a NullException, that is because I had a typo in the SendLoginRequest method (I have fixed it). I had the BeginGetResponse passing in the context, and not the boxed object. So, you would get a null trying to cast AsyncState to an object[]. – Justin Pihony Mar 26 '12 at 18:55
  • That's great, last question. This line flags an error saying Error An object reference is required for the non-static field, method, or property. context.Post(UIMethodCall, responseString); – benjgorman Mar 26 '12 at 19:05