Last year I saw Nikhil Kothari do a session called - Enhancing Ajax Applications with Silverlight. One of his demos was using the OpenFileDialog and a webservice to transfer chunks of data up to the webserver which I downloaded and worked out how it all fit together. Now that beta 2 is released I thought I'd update the demo to call the webservice using a Service Reference and the Async event model.
So lets see how it works.

- The web page has a single button and a div that works as the progress bar.
- The user clicks the button which opens the OpenFileDialog
- User selects as many files as they want
- The files are sent one at a time by breaking them down into chunks
- A chunk is sent through the webservice and then the system waits for the Async Completed event
- The async event will then send the next chunk until the file is sent and moves on to the next file.
Lets see what the code does now:
The aspx page hosts the Silverlight control with a couple of extra parameters than standard.
<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/FileUploader.xap" OnPluginLoaded="onUploaderAvailable"
MinimumVersion="2.0.30523"
Width="0" Height="0" HtmlAccess="Enabled" InitParameters="UploadServiceUrl=http://localhost:7345/ClientBin/Uploader.asmx" />
I want the Silverlight control to be able to interact with the HTML so HtmlAccess is set to true. I also want to set the location of the service so the InitParameters is set and lastly I want to run some javascript when the Silverlight control has loaded so the OnPluginLoaded event is wired up.
Now have a look at the js for the page.
<script type="text/javascript">
var _uploader;
function onUploaderAvailable(sender, e) {
slCtl = sender.get_element();
_uploader = slCtl.Content.uploader;
_uploader.uploadStarting = onUploaderStarting;
_uploader.uploadCompleted = onUploaderCompleted;
_uploader.uploadProgress = onUploaderProgress;
}
function onUploaderCompleted(sender, e) {
document.getElementById('progressContainer').style.visibility = 'hidden';
}
function onUploaderProgress(sender, e) {
document.getElementById('progressLabel').innerHTML = e.fileName;
var total = e.totalLength;
var sent = e.sentLength;
var width = Math.ceil(200 * sent / total) + 'px';
document.getElementById('progressFill').style.width = width;
}
function onUploaderStarting(sender, e) {
document.getElementById('progressCountLabel').innerHTML = _uploader.uploadCount;
document.getElementById('progressContainer').style.visibility = 'visible';
}
function uploadFiles() {
_uploader.uploadFiles();
}
script>
When the Silverlight control has loaded the first method will fire which gets a reference to a custom object called "uploader". This object is a [ScritableMember] that gets registered with the Page (you'll see that in a minute) that can now be accessed and used by the js on the page. Now that we have a reference the events on the "Uploader" are wired up to the js methods so that when an event such as UploadStarting fires the UI can be updated. As you can see there will be no visible XAML component.
[ScriptableMember]
public event UploadEventHandler uploadProgress;
In the Uploader class you can see the [ScriptableMember] marked on the public events and methods that the js needs access to. In the onUploaderAvailable js event that is fired when the Silverlight plugin is loaded a reference to the Uploader instance is gained and the public events are wired up to the js methods. the Uploader instance is registered in the load event of the Page.xaml class like so:
_uploader = new Uploader();
HtmlPage.RegisterScriptableObject("uploader", _uploader);
Now that the Html page knows about the uploader and the events have been wired up we can now click the Upload Button which will fire the uploadFiles js method which fires the uploadFiles method on the uploader object. If you debug the method in the cs file you will see that the js is calling directly into the cs via the scripting bridge that [ScritableMember] creates.
The uploadFiles method spins up an OpenFileDialog which has MultiSelect set to true and the filter set to XML, once the user selects the files they want a list of those files is created to be processed. You can also see that a new webservice client is created and its Completed event is wired up.
// Create the Service
client = new UploaderSoapClient(binding, addr);
// Register for the Completed Event
client.UploadFileChunkCompleted += new EventHandler<UploadFileChunkCompletedEventArgs>(client_UploadFileChunkCompleted);
Since any call to a webservice is Asynchronous a completed event must be wired up to handle the call back, here we define client_UploadFileChunkCompleted to fire when the web service call to UploadFileChunk is done. To do the call to the web service a ServiceReference was added to the Silverlight project and the binding and end point set up as shown here:
EndpointAddress addr = new EndpointAddress(App.uploaderEndPoint);
BasicHttpBinding binding = new BasicHttpBinding();
UploaderSoapClient client;
The uploaderEndPoint was retrieved from the InitParams in App.xaml.cs like so:
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
uploaderEndPoint = e.InitParams["UploadServiceUrl"].ToString();
}
The InitParams are set on the Silverlightncontrol like this
<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/FileUploader.xap"
OnPluginLoaded="onUploaderAvailable" MinimumVersion="2.0.30523" Width="0"
Height="0" HtmlAccess="Enabled" InitParameters="UploadServiceUrl=http://localhost:7345/ClientBin/Uploader.asmx" />
So now everything is wired up we are ready to upload the files. Enjoy!
You can download this solution here...