HttpWebRequest Helper for Silverlight 2

by calbert 3/7/2008 5:12:00 PM

I wrote this helper class to assist with processing an Http Request via Silverlight 2. Since we can't provide a custom class by deriving from BrowserHttpWebRequest because it is internal I wrote this helper that will create and send the request and raise an event when the request completes. I am using it in my current game to send commands to the server and process the responses.

Edit: I revised the class slightly based on finding from Mike Briseno:

Edit: Some folks have experienced issues with this class after installing Silverlight 2 Beta 2. Be sure to check your domain access policy xml file for the new changes in beta 2 but if you experience issues feel free to contact me and we can try and figure them out.

Edit: Added HttpUtility.UrlEncode to the post values before writing them to the stream. Thanks for the suggestion Stefan!

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net;

namespace Lionsguard
{
    public class HttpHelper
    {
        private HttpWebRequest Request { get; set; }
        public Dictionary<string, string> PostValues { get; private set; }

        public event HttpResponseCompleteEventHandler ResponseComplete;
        private void OnResponseComplete(HttpResponseCompleteEventArgs e)
        {
            if (this.ResponseComplete != null)
            {
                this.ResponseComplete(e);
            }
        }

        public HttpHelper(Uri requestUri, string method, params KeyValuePair<string, string>[] postValues)
        {
            this.Request = (HttpWebRequest)WebRequest.Create(requestUri);
            this.Request.ContentType = "application/x-www-form-urlencoded";
            this.Request.Method = method;
            this.PostValues = new Dictionary<string, string>();
            if (postValues != null && postValues.Length > 0)
            {
                foreach (var item in postValues)
                {
                    this.PostValues.Add(item.Key, item.Value);
                }
            }
        }

        public void Execute()
        {
            this.Request.BeginGetRequestStream(new AsyncCallback(HttpHelper.BeginRequest), this);
        }

        private static void BeginRequest(IAsyncResult ar)
        {
            HttpHelper helper = ar.AsyncState as HttpHelper;
            if (helper != null)
            {
                if (helper.PostValues.Count > 0)
                {
                    using (StreamWriter writer = new StreamWriter(helper.Request.EndGetRequestStream(ar)))
                    {
                        foreach (var item in helper.PostValues)
                        {
                            writer.Write("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value));
                        }
                    }
                }
                helper.Request.BeginGetResponse(new AsyncCallback(HttpHelper.BeginResponse), helper);
            }
        }

        private static void BeginResponse(IAsyncResult ar)
        {
            HttpHelper helper = ar.AsyncState as HttpHelper;
            if (helper != null)
            {
                HttpWebResponse response = (HttpWebResponse)helper.Request.EndGetResponse(ar);
                if (response != null)
                {
                    Stream stream = response.GetResponseStream();
                    if (stream != null)
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            helper.OnResponseComplete(new HttpResponseCompleteEventArgs(reader.ReadToEnd()));
                        }
                    }
                }
            }
        }
    }

    public delegate void HttpResponseCompleteEventHandler(HttpResponseCompleteEventArgs e);
    public class HttpResponseCompleteEventArgs : EventArgs
    {
        public string Response { get; set; }

        public HttpResponseCompleteEventArgs(string response)
        {
            this.Response = response;
        }
    }
}

And this is how I am using it in my app:

        private void ProcessCommand(short cmd, string msg)
        {
            App app = App.Current as App;
            HttpHelper helper = new HttpHelper(app.ServerUri, "POST", 
                new KeyValuePair<string, string>("authKey", app.AuthKey),
                new KeyValuePair<string, string>("cmd", cmd.ToString()),
                new KeyValuePair<string, string>("msg", msg));
            helper.ResponseComplete += new HttpResponseCompleteEventHandler(this.CommandComplete);
            helper.Execute();

        }

        private void CommandComplete(HttpResponseCompleteEventArgs e)
        {
            txtAlert.Text = e.Response;
        }

For the VB Developers out there David Thiessen has converted my code to VB:

' Usage....

'Private Sub ProcessCommand(ByVal cmd As Short, ByVal msg As String)
'    Dim app As App = TryCast(App.Current, App)
'    Dim helper As New HttpHelper(app.ServerUri, "POST", New KeyValuePair(Of String, String)("authKey", app.AuthKey), New KeyValuePair(Of String, String)("cmd", cmd.ToString()), New KeyValuePair(Of String, String)("msg", msg))
'    AddHandler helper.ResponseComplete, AddressOf CommandComplete
'    helper.Execute()

'End Sub

'Private Sub CommandComplete(ByVal e As HttpResponseCompleteEventArgs)
'    txtAlert.Text = e.Response
'End Sub

''' <summary>
''' Converted C# code from http://www.cameronalbert.com/post/2008/03/HttpWebRequest-Helper-for-Silverlight-2.aspx
''' </summary>
''' <remarks></remarks>
Public Class HttpHelper

    Private Property Request() As HttpWebRequest
        Get
            Return _Request
        End Get
        Set(ByVal value As HttpWebRequest)
            _Request = value
        End Set
    End Property
    Private _Request As HttpWebRequest

    Public Property PostValues() As Dictionary(Of String, String)
        Get
            Return _PostValues
        End Get
        Private Set(ByVal value As Dictionary(Of String, String))
            _PostValues = value
        End Set
    End Property
    Private _PostValues As Dictionary(Of String, String)

    Public Event ResponseComplete As HttpResponseCompleteEventHandler
    Private Sub OnResponseComplete(ByVal e As HttpResponseCompleteEventArgs)
        RaiseEvent ResponseComplete(e)
    End Sub

    Public Sub New(ByVal requestUri As Uri, ByVal method As String, ByVal ParamArray postValues As KeyValuePair(Of String, String)())
        Me.Request = DirectCast(WebRequest.Create(requestUri), HttpWebRequest)
        Me.Request.ContentType = "application/x-www-form-urlencoded"
        Me.Request.Method = method
        Me.PostValues = New Dictionary(Of String, String)()
        For Each item In postValues
            Me.PostValues.Add(item.Key, item.Value)
        Next
    End Sub

    Public Sub Execute()
        Me.Request.BeginGetRequestStream(New AsyncCallback(AddressOf HttpHelper.BeginRequest), Me)
    End Sub

    Private Shared Sub BeginRequest(ByVal ar As IAsyncResult)
        Dim helper As HttpHelper = TryCast(ar.AsyncState, HttpHelper)
        If helper IsNot Nothing Then
            Using writer As New StreamWriter(helper.Request.EndGetRequestStream(ar))
                For Each item In helper.PostValues
                    writer.Write("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value))
                Next
            End Using
            helper.Request.BeginGetResponse(New AsyncCallback(AddressOf HttpHelper.BeginResponse), helper)
        End If
    End Sub

    Private Shared Sub BeginResponse(ByVal ar As IAsyncResult)
        Dim helper As HttpHelper = TryCast(ar.AsyncState, HttpHelper)
        If helper IsNot Nothing Then
            Dim response As HttpWebResponse = DirectCast(helper.Request.EndGetResponse(ar), HttpWebResponse)
            If response IsNot Nothing Then
                Dim stream As Stream = response.GetResponseStream()
                If stream IsNot Nothing Then
                    Using reader As New StreamReader(stream)
                        helper.OnResponseComplete(New HttpResponseCompleteEventArgs(reader.ReadToEnd()))
                    End Using
                End If
            End If
        End If
    End Sub
End Class

Public Delegate Sub HttpResponseCompleteEventHandler(ByVal e As HttpResponseCompleteEventArgs)

Public Class HttpResponseCompleteEventArgs
    Inherits EventArgs

    Public Property Response() As String
        Get
            Return _Response
        End Get
        Set(ByVal value As String)
            _Response = value
        End Set
    End Property
    Private _Response As String

    Public Sub New(ByVal response As String)
        Me.Response = response
    End Sub

End Class


 

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Game Development | General | Silverlight Games

Related posts

Comments

3/8/2008 4:37:13 AM

pingback

Pingback from games.rayncity.com

HttpWebRequest Helper for Silverlight 2

games.rayncity.com

3/8/2008 6:56:50 PM

Laumann

Wow, you are really getting things done fast Smile

And beautiful code!

Laumann dk

3/9/2008 5:56:18 AM

Laumann

FYI

I just downloaded the Farseer Engine, and saw that they use:
fps.SetValue(Canvas.ZIndexProperty, 1000);

So there is a Z-Index Smile

Laumann dk

3/13/2008 12:35:55 PM

Meghana

I tired this, and it does Not complete server request. I passed the server Uri to be http://www.microsoft.com and on the browser status pane, it just shows "connecting to microsoft.com" - nothing happens .. Any idea on how to make it work?

Meghana in

3/13/2008 10:00:46 PM

calbert

That has to do with the cross domain policy in Silverlight 2. See Scott Guthrie's post at weblogs.asp.net/.../...nd-populate-a-datagrid.aspx for more information. Look for the section titled "Cross Domain Network Access".

calbert us

3/14/2008 8:09:48 AM

Meghana

Great .. Thank you so much, Calbert! And again - thanks for publishing this very useful class!

Meghana in

3/14/2008 10:35:04 PM

calbert

Glad I could help Smile

calbert us

3/27/2008 4:18:35 AM

mike briseno

Hey Cameron, thank you very much for posting your helper class.

While the essence of it works great, I felt the need to modify it a bit more. While doing so, I ran into a few issues.


First:
If the response is null. Things blow up.

By example, I'm requesting a web service that has the signature:
public void IncrementCounter().
When that happens, the helper "dies" at:
helper.Request.BeginGetResponse(new AsyncCallback(HttpHelper.BeginResponse), helper);

Message: Error HRESULT E_FAIL has been returned from a call to a COM component.
Source: System.Windows
Stack Trace: at MS.Internal.XcpImports.GetDownloaderBytes(IntPtr element, IntPtr& outBytes, Int32& outSize)
at MS.Internal.InternalWebRequest.GetResponseStream()
at System.Net.BrowserHttpWebRequest.Completed(Object sender, EventArgs e)
at System.Windows.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, String eventName)


This doesn't look to be "your" fault. However, any suggestions?
Do you see the same behavior?


Second: a much simpler observation.
It seems like silverlight "lost" the ability to set a timeout or a user agent.


The obvious request.UserAgent and request.Timeout properties don't exist and
request.Headers[HttpRequestHeader.UserAgent] = USERAGENT throws an System.ArgumentException


Both these questions really aren't specific to your code, however, I hoped you might be able to shed some light.

Regardless, thanks for posting the helper. It been a great "inspiration" for my own.

M

mike briseno us

3/27/2008 6:17:23 PM

calbert

I wrote the helper to mainly handle posting to a web page or custom IHttpHandler, I didn't really set it up to use with web services. If you want to post some code displaying how your app is using the Helper I can probably troubleshoot the issues you are having.
Web Service calls are pretty straight forward in SL 2 but the WebRequest feature involved more code, that is why I created the helper class.

calbert us

6/12/2008 10:51:31 PM

eric

Thanks so much for posting this code... it's exactly what I was wanting to do myself for use as a wrapper for accessing a web service I have on Apache; except your code is much better than I could have done. Smile

I'm using Silverlight 2 Beta 2 and I'm running into problems using your code, although I'm not sure if it's your code that is actually causing the problem or not.

The behavior seems really strange:

Everything seems fine up until "BeginResponse()". Inside "BeginResponse()", if I look at "response" right after the call to "helper.Request.EndGetResponse(ar);" in the debugger, "response.StatusCode" equals "NotFound"; so, naturally, when "stream = response.GetResponseStream()" executes it returns null.

The thing that's strange is I never see the actual POST request come into Apache, i.e. it's never logged in the "access.log" file. However, what I do see, is Silverlight fetching "clientaccesspolicy.xml" from the root of my web server, which is successful (I've given full access in the policy file, i.e. "*"). I've also added "http://localhost/" as a trusted site in the security tab in IE (not sure if this matters).

So, if I have a "wide-open" client access policy file and it's being fetched successfully, why don't I ever see my request come from Silverlight? How can I turn on additional debugging to trace the error further, like the output Mike Briseno left in his post?

I get no exceptions or any errors in the VS output window.

Any ideas or direction would be greatly appreciated!

Thanks again, -e

eric us

6/18/2008 2:49:25 PM

eric

The lesson here is to read the Microsoft change docs between releases (especially for Beta releases)! And make sure you update all copies of your client access policy file! Smile

MS changed some attributes/behavior of the "clientaccesspolicy.xml" between Silverlight Beta 1 and Beta 2. I had updated the policy file on my home machine but not at work; so I was hit by my cross-domain calls not working.

Check the MS documentation... they have an updated example (allows all access) policy file for Beta 2.

Hope this helps someone, -e

eric us

6/21/2008 7:53:58 AM

Stefan

Calbert,

Fantastic class, very helpful and a big thank you!

Here's a small contribution - don't forget to url encode your values:

....
using System.Windows.Browser;
....
foreach (var item in helper.PostValues)
{
writer.Write("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value));
}

if you don't then you might lose special characters (like "+,=,&", etc)!


Regards,
Stefan

Stefan jp

6/23/2008 12:33:09 PM

calbert

Thanks Stefan, I added it to the class!

calbert us

7/24/2008 7:45:43 AM

pingback

Pingback from edsilverton.wordpress.com

Klaklakgroup.com Silverlight portfolio goes live (including content management, deep linking and Google content indexing) « Ed Silverton

edsilverton.wordpress.com

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

8/28/2008 5:52:53 PM

Powered by BlogEngine.NET 1.3.1.0
Theme by Mads Kristensen

About the author

I am Senior Software Engineer specializing in the Microsoft .NET Framework and PBBG development.

E-mail me Send mail

Calendar

<<  August 2008  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar

Recent posts

Recent comments