Silverlight ASP.NET control: AsyncFileUpload

Posted at Sat, 05 May 2007 00:57:04 GMT by Wilco Bauwer

One of the things people have been asking for, particularly since AJAX became popular, is being able to asynchronously upload files. Silverlight 1.1 contains the building blocks to make this possible. I've written an ASP.NET control to demonstrate this feature as an end-to-end scenario. Please note that this feature is just a prototype. As you will see in the source code comments, there is plenty of room for improvement and polishing.

To see the results, go to the AsyncFileUpload demo. (As you can see in its codebehind, there's currently a limit of a bit less than 1mb for the total file upload size.) You can also check out the source-code of this control.

OpenFileDialog
The first building block that I use in this control is the new OpenFileDialog (OFD). This dialog will return read-only streams to one or more files on the client. You can read from those streams on the client. Additionally you can also define filters, set a title, etc.

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 

OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "Select a file...";
ofd.Filter = "Word Documents|*.doc;*.txt|All Files|*.*";
ofd.EnableMultipleSelection = true;
if (ofd.ShowDialog() == DialogResult.OK) {
    foreach (FileDialogFileInfo file in ofd.SelectedFiles) {
        using (StreamReader reader = file.OpenRead()) {
            string fileName = file.Name;
            string contents = reader.ReadToEnd();
            // ...
        }
    }
}

BrowserHttpRequest
The next block is BrowserHttpWebRequest. This is an abstraction of the browser's XMLHttpRequest object and uses the exact same semantics. In my case I use this object to upload chunks of the file to the server. (Specifically, to a server-side ASP.NET handler which will save/append the chunk.) Since binary data is currently not supported in all browsers, we base64 encode the data before sending it to the server.

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

// NOTE: In the future we'd like to hide BrowserHttpRequest, so you only need to 
// use (and know about) HttpWebRequest. That should make it easier to port code 
// between different environments (Silverlight/desktop).
HttpWebRequest request = new BrowserHttpRequest();
request.Method = "POST";
StreamWriter writer = new StreamWriter(request.GetRequestStream());
writer.Write(...);

request.BeginGetResponse(OnResponse, request);

...

void OnResponse(IAsyncResult asyncResult) {
    HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
    HttpWebResponse response = request.EndGetResponse(asyncResult);
    using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
        // response.StatusCode, ...
        string responseText = reader.ReadToEnd();
    }
}

Scriptability
Another feature we're using is [Scriptable]. This attribute can be applied on types/members that can be accessed from JavaScript. This is useful in case you want to extend the browser programming framework for JS developers. In my case I use this to expose the ProgressChanged event. You can handle this event from JavaScript to do something more complicated than a simple progress bar and/or message.

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 

[Scriptable]
public class AsyncFileUpload : Canvas {

    void OnCanvasLoaded(object sender, EventArgs e) {
        // We need to explicitly expose object instances to JavaScript.
        WebApplication.Current.RegisterScriptableObject("uploader", this);
    }

    [Scriptable]
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    ....
}

JavaScript:
1 
2 
3 
4 
5 
6 

var control = document.getElementById('AgControl1');
var uploader = control.Content.uploader;
uploader.ProgressChanged = function(sender, args) {
    // Sender is currently always null.
    // Use args.Progress here...
}

Currently our scripting support is limited to primitive types and HTML/JS objects only. This means you can't (yet) return arbitrary managed objects to JS.

In closing...
This is just a quick write-up on some of the features you will find in Silverlight. I hope with this quick and dirty post I have gathered some interest in the programming API portion of Silverlight. I hope you'll realize that Silverlight can be used to do some visually very compelling things, but is not at all limited to that. Thoughts?

Great work!
Does it have 200K limit?
No. My example has a ~1mb limit (for all files in total). See the codebehind of the example where I have set this limit. Nothing prevents you from removing this limit or changing it.
Good!

Silverlight 1.1 is supported .net, It is powerful than flash!!
sorry, Silverlight alpha 1.1
Hi Wilco,
Nice article. Do you realize that your Async Demo link actually goes to Apple site http://www.silverlight.com/AsyncFileUpload
because your link is a relative link. Can you please fix it.

Thanks,
Rupak Ganguly
http://developershelf.blogspot.com
Thanks!!!
Your demo application doesn't work. I click browse, nothing happens.
What browser are you using? And did you make sure you have Silverlight 1.1 alpha installed?
Hi

It is a great example. Thanks. What I want to ask is what if client will want to catch OnUploadComplete for example and execute self Default.aspx.cs code?
On IE7 under Vista Premium with Silverlight 1.1 Alpha installed pressing the "AsyncFileUpload demo" Browse button does not do anything. Silverlight runs properly at silverlight.live.com.

Want to provide some guidance?
Are you using the 1.1 refresh version that was just released a few days ago? If so, that's your reason. I haven't updated the demos yet on my site to use the latest version. I hope to do so later today or tomorrow.
Updated... Should work again now if you have 1.1 refresh installed.
Ummmm....

Have any idea why any folder view from explorer of the file being downloaded causes this exception in the browser? (XP Pro/IE7/Silverlight 1.1 Alpha Refresh)

Silverlight error message
ErrorCode: 1001
ErrorType: RuntimeError
message: System.Net.WebException: Operation is not valid due to the current state of the object
at
Wilco.Windows.Browser.AsyncFileUpload.FileUploader.OnResponse(IAsyncResult asyncResult)
at
System.Windows.Browser.Net.BrowserHttpRequest.OnWebRequestComplete(Object context)
at System.Windows.Hosting.CallbackCookie.Invoke()
at System.Windows.Hosting.DelegateWrapper.OnInvoke()
at
System.Windows.Hosting.HostAppDomainManager.InvokeDelegate(IntPtr pHandle)
MethodName:
That's strange. Do you get this everytime you try to upload a file? Have you tried uploading different type of files to see if perhaps there's a bug in my code related to that?
Thank you for your response.

Problem appears to be independent of file type.

The problem occurs intermittantly, but one way to obtain the error message is to do the following:

Download your code

uncomment out the 2nd line below:
C#:
1 
2 

// TODO: Use the following line of code instead once a bug in Silverlight's assembly url parser is fixed.
                _host.AssemblyPath = Page.ClientScript.GetWebResourceUrl(typeof(ClientServices), "Wilco.Web.Silverlight.Resources.Wilco.Windows.Browser.dll");



Change the maxupload size so that you can make the test (you can't with a 1MB file - it's too quick), i.e. in Web.config change maxuploadsize to 100MB (i.e. the following):

MaxUploadSize="100000000"

Compile and publish to your local machine, and add a virtual directory in IIS. Set write permissions on the "Uploads" subdirectory.

Browse to the localhost virtual directory in IE7.

Choose any file with some size. One file you can chose is ASPNETFutures.msi, which is 51.2 MB. The type or extension does not appear to matter.

While the file is uploading, task over to an explorer window that shows the Uploads directory. If necessary, hit F5 in explorer a few times. The Silverlight dialog with error message will pop up.

The problem appears at other times also, this is just a way to duplicate it.

Test performed on XP Pro SP2/IIS 5.1/IE7.
Sorry for the late response.

Anyway, it sounds like this doesn't happen consistently, at least not with smaller files. This means that it's possible there's a connection failure, which I don't believe I handle. One of the improvements of this demo would be to make it much more reliable, so chunks can fail and be re-sent.

I will check out your repro this weekend if I can find some time for it. Thanks!
Thank you for your response.

The test described above is all done through localhost and on a single machine, so the problem described isn't likely related to a connection issue.

It does happen on small files also, the procedure described above is just a simple way to duplicate the problem.
I too have a similar problem with the error;

"Operation is not valid due to the current state of the object", which occurs every time I try and use the following line of code, and the Browser URI points to "testfile.xml", however, I get "Cross domain calls are not supported by BrowserHttpWebRequest.", when I point to the path of the file using "http://testfile.xml"....

Dim Response = req2.GetResponse
(where req2 is a BrowserHttpWebRequest object).

I'm really getting cheesed off with Silverlight and VS2008....I hope for Microsoft's sake, these problems are only confined to the Beta version, otherwise I can see VS2008 putting a lot of people off....



Any updates?
A working cancel and resume function would be cool.
The Silverlight examples do not seem to work. I have the latest version of Silverlight installed.
Your message will be encoded/formatted when it is displayed. If you want to post code, please put the code inside [code=X][/code] tags, where X is the language of your code (C#, ASPX, SQL, etc).
Name:
Email:
(will be encoded using JavaScript to keep it functional and prevent it from being picked up by spammers)
Url:
 
Message:
3 + 3 =