HttpWebRequest Helper for Silverlight 2

by Cameron Albert 7. March 2008 17:12

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


 

Tags:

Game Development | General | Silverlight Games

Comments

3/9/2008 1:37:13 PM #

pingback

Pingback from games.rayncity.com

HttpWebRequest Helper for Silverlight 2

games.rayncity.com

3/10/2008 3:56:50 AM #

Laumann

Wow, you are really getting things done fast Smile

And beautiful code!

Laumann Denmark

3/10/2008 2:56:18 PM #

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 Denmark

3/14/2008 9: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 India

3/15/2008 7:00:46 AM #

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 United States

3/15/2008 5:09:48 PM #

Meghana

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

Meghana India

3/16/2008 7:35:04 AM #

calbert

Glad I could help Smile

calbert United States

3/28/2008 1:18:35 PM #

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 United States

3/29/2008 3:17:23 AM #

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 United States

6/14/2008 7:51:31 AM #

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 United States

6/19/2008 11: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 United States

6/22/2008 4:53:58 PM #

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 Japan

6/24/2008 9:33:09 PM #

calbert

Thanks Stefan, I added it to the class!

calbert United States

7/25/2008 4:45:43 PM #

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

1/15/2009 2:40:32 PM #

DAF555

Hey, it is exactly what I was searching for. Fully encapsulating the Request and the possibility for giving over a dynamical response-handler via delegate.
Very thanks for this solution!

DAF555 Germany

1/15/2009 6:42:28 PM #

DAF555

I still have a problem with this solution. I always get an error when I was trying to do the request what with thread-across access. What can I do?

DAF555 Germany

1/15/2009 7:21:39 PM #

DAF555

This line in BeginResponse-method throws a thread-access-exception:

helper.OnResponseComplete(new HttpResponseCompleteEventArgs(reader.ReadToEnd()));

DAF555 Germany

1/15/2009 8:26:08 PM #

calbert

DAF555,

You are probably trying to write the response into a control within the Silverlight application. Since the HttpHelper executes code on background threads the best way is to provide a delegate that will process on the main UI thread. The example below instantiates the helper class and excutes a command, handling the response on the main UI thread via a delegate.


        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)
        {
            // This will cause the WriteText method to be executed on the main UI thread.
            this.Dispatcher.BeginInvoke(() => this.WriteText(e.Response));
        }
        private void WriteText(string response)
        {
            txtAlert.Text = response;
        }

calbert United States

1/16/2009 10:55:22 AM #

DAF555

Fine, it works. Thank you very much!

I put the response handler methods together:

private void CommandComplete(HttpResponseCompleteEventArgs e) {
  this.Dispatcher.BeginInvoke(() => {
    txtAlert.Text = e.Response;
  });
}

DAF555 Germany

1/28/2009 10:49:46 AM #

DAF555

Is it possible to integrate HTTPS-support and error-handling?

DAF555 Germany

1/28/2009 11:14:23 PM #

Adrian

Hi,
Nice class. This works well for me. Thank you. But in trying to understand it, there are a couple of places where it seems to me you could have coded things more concisely. This is probably just because I'm overlooking something, so if you could explain my ignorance I'd be very grateful:
1) Why do you need to use a Dictionary for PostValues? Why not just have it as a KeyValuePair<string, string>[], and just have PostValues = postValues in the constructor instead of an iteration loop?
2) Why do you have BeginRequest and BeginResponse as statics? If they're not static you don't need to obtain helper from a cast of ar. You automatically have helper=this.

When I make these changes, everything still seems to work fine, and you save several lines of code (and to my eye make things clearer.) But I realize my app might not be stressing the class heavily enough to uncover any shortcomings of my approach. So please could you tell me why you used your approach? Was there a reason, or is it just a different coding style?
Thanks.

Adrian Canada

1/30/2009 7:35:10 PM #

calbert

Hi Adrian,

1) I believe I made it a dictionary so as to be able to add/modify keys and a Dictionary would provide faster access than enumerating.

2) The request and response are asynchronous so you will want to cast ar.AsyncState as HttpHelper rather than rely on an instance, the methods do not have to be static, although it is a cleaner separation and forces the cast of ar.AsyncState. The main reason for this would be to prevent cross-thread access.

calbert

1/30/2009 7:44:19 PM #

calbert

DAF555,

I have not tried it with HTTPS but that is just a URI so it should work fine, is your Silverlight app accessible via the HTTPS link?

Also, for error handling, change the BeginResponse to this:
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)
        {
          if (response.StatusCode == HttpStatusCode.OK)
          {
            Stream stream = response.GetResponseStream();
            if (stream != null)
            {
              helper.OnResponseComplete(new HttpStreamEventArgs(helper, stream));
              stream.Close();
            }
          }
          else
          {
            helper.OnResponseFailed(new HttpResponseFailedEventArgs(helper, response.StatusDescription));
          }
        }
      }
    }

Add this class to the same namespace where the HttpHelper lives:

public delegate void HttpResponseFailedEventHandler(HttpResponseFailedEventArgs e);
  public class HttpResponseFailedEventArgs : EventArgs
  {
    public string Message { get; set; }

    public HttpResponseFailedEventArgs( string message)
    {
      this.Message = message;
    }
  }

And then add the event declaration to the HttpHelper class:

public event HttpResponseFailedEventHandler ResponseFailed = delegate { };
    private void OnResponseFailed(HttpResponseFailedEventArgs e)
    {
      this.ResponseFailed(e);
    }

calbert United States

2/28/2009 7:01:11 PM #

Jamie

If I may offer a couple suggestions...
I wanted to be able to POST files with this class, so I made some modifications (my additions are in VB so if somebody wanted to convert this back to C#, they're more than welcome).





First, I took the PostValues out of the New method and moved them to a separate method I called SetPostValues() - I like this because it lets you add values if you want but doesn't require them... i.e. you're only uploaded a file and no other variables.
-----------------------
Public Sub SetPostValues(ByVal ParamArray postValues As KeyValuePair(Of String, String)())
        Me.PostValues = New Dictionary(Of String, String)()
        For Each item In postValues
            Me.PostValues.Add(item.Key, item.Value)
        Next
End Sub
-----------------------

Next, we need to add a property PostFiles that will contain the File(s) - we're going to save this as filename, byte data.
-----------------------
    Public Property PostFiles() As Dictionary(Of String, Byte())
        Get
            Return _PostFiles
        End Get
        Private Set(ByVal value As Dictionary(Of String, Byte()))
            _PostFiles = value
        End Set
    End Property
    Private _PostFiles As Dictionary(Of String, Byte())
-----------------------

Next, I added a method SetPostFiles() so that you can populate PostFiles.
-----------------------
    Public Sub SetPostFiles(ByVal ParamArray postFiles As KeyValuePair(Of String, Byte())())
        Me.PostFiles = New Dictionary(Of String, Byte())()
        For Each item In postFiles
            Me.PostFiles.Add(item.Key, item.Value)
        Next
    End Sub
-----------------------


Ok, so now we need a new Public READ-ONLY property that will contain the unique "Boundary" for the post.
-----------------------
    Private _Boundary As String
    Public ReadOnly Property Boundary() As String
        Get
            Return _Boundary
        End Get
    End Property
-----------------------

Once we've got all that, we need to make some changes to the New method... Like I said earlier, remove setting PostValues (because that's a new method), let's make sure that PostValues and PostFiles are NOTHING... Also, we're going to set the random Boundary and set the Content-Type (which is now multipart/form-data and not x-www-form-urlencoded - so we can send files)
-----------------------
        Me.Request = DirectCast(WebRequest.Create(requestUri), HttpWebRequest)
        Me._Boundary = System.Guid.NewGuid.ToString
        Me.Request.ContentType = "multipart/form-data; boundary=" & Boundary
        Me.Request.Method = method
        Me.PostValues = Nothing
        Me.PostFiles = Nothing
-----------------------

The last changes to make are in the down and dirty processing...
First, we send the string that says "We're gonna send some stuff now"
Next, we check to see if there are any POST values, and if there are, we don't send those as key=value anymore, we send those as form data elements (and now you don't need to URL Encode the value)
Next, we check to see if there are any POST files, and if there are, we send those as form data elements sending the "key" as the file name. - notice we have to switch from sending String data to raw Byte data so I'm flushing the buffer each time we switch... I'm not entirely sure if that's necessary but it couldn't hurt too much (I wouldn't think that it would add that many processing steps to really matter if we're sending files).
Lastly, we send the "I'm all done sending you data" string and close the writer.
-----------------------
        If helper IsNot Nothing Then
            Using writer As New StreamWriter(helper.Request.EndGetRequestStream(ar))
                ' Send the "open tag" for the POST values
                writer.Write("--" & helper.Boundary & vbCrLf)
                ' If we have any POST values, send them first
                If helper.PostValues IsNot Nothing Then
                    Dim FormatString As String = "Content-Disposition: form-data; name=""{0}"";" & vbCrLf & vbCrLf & _
                                                "{1}" & _
                                                vbCrLf & "--" & helper.Boundary & vbCrLf
                    For Each item In helper.PostValues
                        writer.Write(FormatString, item.Key, item.Value)
                    Next
                End If
                ' If we have any POST files, send them last
                If helper.PostFiles IsNot Nothing Then
                    Dim HeaderString As String = "Content-Disposition: form-data; name=""upfile""; filename=""{0}"";" & vbCrLf & vbCrLf
                    '                             Then the file would go here... raw bytes...
                    Dim SeperatorString As String = vbCrLf & "--" & helper.Boundary & vbCrLf
                    For Each item In helper.PostFiles
                        writer.Write(HeaderString, item.Key)
                        writer.Flush()
                        writer.BaseStream.Write(item.Value, 0, item.Value.Length)
                        writer.BaseStream.Flush()
                        writer.Write(SeperatorString)
                    Next
                End If
                ' Send the "close tag" for the POST values
                writer.Write(vbCrLf & "--" & helper.Boundary & "--")
                writer.Close()
            End Using
            helper.Request.BeginGetResponse(New AsyncCallback(AddressOf HttpHelper.BeginResponse), helper)
        End If
-----------------------


With all these changes, the call to the class and methods would now be
-----------------------
'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")
'    helper.SetPostValues(New KeyValuePair(Of String, String)("authKey", app.AuthKey), _
'                         New KeyValuePair(Of String, String)("cmd", cmd.ToString()), _
'                         New KeyValuePair(Of String, String)("msg", msg))
'    helper.SetFileValues(New KeyValuePare(Of String, Byte())("filename",{File data as Byte()})
'    AddHandler helper.ResponseComplete, AddressOf CommandComplete
'    helper.Execute()

'End Sub

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

-----------------------


Hope this helps somebody... it sure helped me!!!

p.s. I think this class is probably going to need some sort of Dispose method or something won't it? We don't want to have IE eating up all the system memory when we run this over and over again for large files.

Jamie United States

Powered by BlogEngine.NET 1.5.0.7
Modified Theme by Mads Kristensen

About the Author

CameronAlbert.com I am Senior Software Development Consultant specializing in Silverlight, WPF and the Microsoft .NET Framework. 

My current project Perenthia is a Silverlight multi-player game based in a fantasy world that combines text adventure games with some moderate graphics

View Cameron Albert's profile on LinkedIn
See how we're connected

Follow cameronalbert on Twitter

 

Recommended Books

Silverlight 4 Business Application Development - Beginner's Guide:

http://www.packtpub.com/microsoft-silverlight-4-business-application-development-beginners-guide/book

Microsoft Silverlight 4 Business Application Development: Beginner’s Guide