Silverlight 2 brings support for threading to the browser. You can either directly start new threads using System.Threading.Thread and System.Threading.ThreadPool, or you can use the higher-level (and recommended) System.ComponentModel.BackgroundWorker type. The latter encapsulates the concept of executing work in the background (using a thread from the thread-pool) and updating the UI based on progress and/or completion of that work, which means that you can safely update the UI from the related events.

A lesser-known type that we introduced in beta 1 is System.Windows.Threading.Dispatcher. This type lets you execute work on the UI thread - something that's useful when you directly want to update the UI from a background thread. Since Silverlight always has a single UI-thread, there is only a single disatcher instance per Silverlight application. This instance is accessible via any DependencyObject or ScriptObject instances' Dispatcher property. Once you have a reference to a dispatcher, you can use its BeginInvoke method to dispatch your work. In Silverlight we added an overload which takes an Action, which means you don't need to add a cast or anything to help the compiler infer what type of delegate you want to pass:

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

var myThread = new Thread(() => {
    // Using a lambda...
    myTextBlock.Dispatcher.BeginInvoke(() => myTextBlock.Text = "Updated from a non-UI thread.");

    // Using an anonymous delegate...
    myTextBlock.Dispatcher.BeginInvoke(delegate {
        myTextBlock.Text = "Updated from a non-UI thread.";
    });
});
myThread.Start();

Please note that you may not be able to find the dispatcher property via intellisense. It's marked as an advanced property, so you either need to update your VS settings to display advanced members, or you just need to ignore intellisense and assume your code will in fact compile regardless of what intellisense implies. The same goes for CheckAccess, which is actually marked as a member that should never be displayed. The main reason these members aren't always visible is because they shouldn't be as common as the other members on a DependencyObject. As I mentioned before, you'll probably want to use a BackgroundWorker most of the time instead.

Gotchas

There are a couple of things to be aware of. The first is that we try to guard against cross-thread invocations when this would potentially be unsafe. For example, we don't allow you to call into the HTML DOM or a JavaScript function from a background thread. The reason for this is that both assume to be invoked on the UI thread. Breaking this assumption can lead to unexpected behavior, including browser crashes.

The other thing to be aware of is creating deadlocks. Silverlight comes with primitives such as Monitor (encapsulated via the lock construct in C#) and ManualResetEvent which make it trivial to create a deadlock. A deadlock will cause most browsers to hang completely. While technically this isn't very different from some JavaScript that infinitely, it's often easier to accidentally create a deadlock than an infinite loop of code. For example, I've seen several people try to create a synchronous version of HttpWebRequest by letting the current thread wait for a ManualResetEvent to be notified by the response callback. HttpWebRequests however execute their callbacks on the UI thread, which means you have a deadlock right there. While ideally you avoid blocking the UI thread entirely, you should at least consider specifying timeouts when you use a synchronization object. For example, instead of the lock construct in C# (Monitor.Enter/Exit), consider using Monitor.TryEnter/Exit passing in a reasonable timeout, and instead of using ManualResetEvent's parameterless WaitOne, consider using one of the overloads.

There has been a fair amount of feedback regarding the HttpWebRequest type in Silverlight. People have asked or commented about:

  1. The lack of support for synchronous requests;
  2. Being limited to GET/POST requests;
  3. No support for headers in GET requests;
  4. Uploading large files;
  5. Complexity of asynchronous requests.

The response to most of this feedback has been that we are limited by what functionality the browser provides to plugins. For example, the API implemented by all browsers but IE (called the NPAPI, short for Netscape Plugin API) basically has the following support for networking:

  1. NPN_GetURL(Notify). Used to initiate a GET request. This API does not have support for passing in headers. There is also no synchronous version of this API.
  2. NPN_PostURL(Notify). Used to initiate a POST request. This API optionally takes a buffer of in-memory POST data. There is no synchronous version of this API either.

You may be wondering why we were able to support synchronous requests in Silverlight 1.1 alpha, why it did support headers in GET requests, etc. In Silverlight 1.1 alpha we didn't use the aforementioned APIs. Instead, we essentially wrapped the browser's XMLHttpRequest object that you can use in JavaScript today via our HTML/JS bridge. This had a few disadvantages:

  1. No support for cross-domain requests.
  2. Lack of support for dealing with binary data. We were able to get this working in FF, but Safari seemed to have problems handling responses beyond a certain size (couple of KB) wih binary data in them.
  3. Since it depends on interoperability with JavaScript, it's not quite as efficient as directly using native APIs.

There were a few things we could've done (and could still do) to deal with this. We could've used the operating system's networking stack and address most of the feedback we've had so far. The main disadvantage would be that all the requests would execute in their own security context. This means that if a user is authenticated using Windows authentication, any request sent by the Silverlight application would still be unauthenticated. Alternatively we could've created a hybrid, and choose what request type we would use based on the functionality you need. For example, if you don't care about binary data but you do care about headers in GET requests, we could use the browser's XMLHttpRequest object. But if you care about binary data and need authentication, we could use the browser's plugin API. Needless to say this would allow for a rather awkward/inconsistent programming experience.

Until we find a better way to deal with all of these problems in a future release, you will unfortunately have to deal with these shortcomings. To help you out a little though, I've put together some code that basically does create some sort of hybrid between XMLHttpRequest and the current version of HttpWebRequest in Silverlight. In a nutshell, you can write code like this:

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

// Based on the request uri, this will either return the Silverlight HttpWebRequest 
// type (which supports binary data and cross-domain requests), or a HttpWebRequestEx 
// type (which supports PUT/HEAD/etc, headers and synchronous requests).
var request = HttpWebRequestEx.Create(requestUri);

// Assume we are requesting a resource on our server. Cast to HttpWebRequestEx 
// such that we can call GetResponse for a synchronous request.
var sameDomainRequest = (HttpWebRequestEx)request;
sameDomainRequest.Headers["foo"] = "bar";
var response = sameDomainRequest.GetResponse();
// ...

I've also put together a type which might make it a little easier to work with HTTP requests. It's basically a wrapper around HttpWebRequestEx and HttpWebRequest and it is implemented as a fluent interface which should make it a little easier to construct a request:

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

// Synchronous request.
var response = Request.To("foo.txt").Send().ReadAllText();
HtmlPage.Window.Alert(response);

// Asynchronous request with a header and POST data.
Request.To("foo.txt")
    .WithMethod("POST")
    .WithHeader("foo", "bar")
    .WithBody(writer => writer.Write("hello"))
    .SendAsync(response => HtmlPage.Window.Alert(response.ReadAllText()));

// Asynchronous, cross-domain request.
Request.To("http://services.digg.com/stories/topic/microsoft?count=3&appkey=http%3A%2F%2Fexample.com%2fapplication")
    .SendAsync(response => HtmlPage.Window.Alert(response.ReadAllText()));

You can download the sources and binaries to try this out yourself. Let me know what you think.

During MIX this year Stefan Schackow did a presentation on the HTML/JS bridge in Silverlight 2. (It’s the second part of the presentation; the first part is Chung Webster talking about how you can use the ASP.NET controls to integrate Silveright in your web application.) If you’re interested, you can download the code for this session and see how things were done.

Almost all of the Silverlight demos you see out there are completely client-side, and invoke Web services to post/get data. This approach makes sense, but when you want to use Silverlight to functionally enhance your existing website, you may want to have some kind of integration between Silverlight objects and your server-side controls.

You may also want to enable some kind of Silverlight object composition. For example, my AsyncFileUpload component has two visualizers. It should be easy to add a new visualizer without modifying any existing code.

To address these problems, I’ve put together some experimental code.

Object composition

Each Silverlight instance has its own AppDomain. You can pass managed objects from one Silverlight instance to another through the HTML/JS bridge, but you would only be able to program against it like any other JavaScript object. The ScriptObject.ManagedObject property for such an object would be null if the underlying managed object isn’t part of the current AppDomain. As a result, we ideally compose objects inside a single Silverlight application, such that we can directly reference other objects. For example, the visualizers for our AsyncFileUploader can each reference the actual uploader, handle its events, get a stream to files being uploaded, etc.

By composing the objects inside a single Silverlight instance, we have to solve a new problem, which is visual composition. We are suddenly limited visually to a single 'canvas' if you will. I’ve addressed this to a certain extent by introducing a concept called viewports. Per Silverlight instance there can be one viewport, which will be set as the root visual. All visual objects will be added to this viewport. It’s up to the implementation of the viewport to handle how it adds the visual objects. For example, a StackViewPort may add all visuals sequentially. A GridViewPort on the other hand may add visuals to the grid based on information that was set server-side:

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 

<wilco:AsyncFileUploadVisualizer ID="v1" runat="server" UploaderID="AsyncFileUpload1" />
<wilco:AsyncFileUploadProgressVisualizer ID="v2" runat="server" UploaderID="AsyncFileUpload1" />
<wilco:GridViewPort ID="GridViewPort1" runat="server" Height="200px" Width="100%">
    <ColumnDefinitions>
        <wilco:ColumnDefinition Width="2*" />
        <wilco:ColumnDefinition Width="1*" />
    </ColumnDefinitions>
    <Locations>
        <wilco:GridViewPortItem ControlID="v1" Column="0" />
        <wilco:GridViewPortItem ControlID="v2" Column="1" />
    </Locations>
</wilco:GridViewPort>

To do the actual composition on the client, we need a few things. We need is a loader which downloads all the right things. As I mentioned before, we want to be able to implement new components. Those components will be added to their own XAP. Our loader needs to download the XAPs for all the objects before those objects can be loaded.

Once the loader has downloaded the XAPs, we have to load the main assembly from this XAP. We can do this by parsing the AppManifest’s XAML as a deployment object and then find the main assembly. In code this looks like:

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

private static Assembly LoadAssemblyFromStream(Stream xapStream) {
    string appManifestString;
    using (var reader = new StreamReader(Application.GetResourceStream(new StreamResourceInfo(xapStream, null), new Uri("AppManifest.xaml", UriKind.Relative)).Stream)) {
        appManifestString = reader.ReadToEnd();
    }

    Assembly entryPointAssembly = null;
    Deployment deployment = (Deployment)XamlReader.Load(appManifestString);
    foreach (var part in deployment.Parts) {
        var streamInfo = Application.GetResourceStream(new StreamResourceInfo(xapStream, null), new Uri(part.Source, UriKind.Relative));
        Assembly assembly = part.Load(streamInfo.Stream);

        if (assembly.FullName.Split(',')[0].Equals(deployment.EntryPointAssembly)) {
            entryPointAssembly = assembly;
        }
    }

    xapStream.Close();
    return entryPointAssembly;
}

(This technique can be useful for any kind of loader. Be careful though about the consequences. For example, if someone can use your loader to load their own XAP, that XAP would essentially impersonate your XAP, and thus be able to access its isolated store, etc. Certain things may also not work, such as resources defined in the delay-loaded XAP.)

After we’ve loaded all the right assemblies into the current AppDomain, we can instantiate every object. The easiest way to do this is if we would simply render XAML directly from our server-side controls. We can then just go through each piece of XAML and load it using the XamlReader.

To allow references between objects, we need to first instantiate all the objects and register them with a unique (user-defined) name. We then need to notify the objects that all objects have been registered such that they can actually get a reference to other objects. The following interface captures this:

C#:
1 
2 
3 
4 
5 
6 
7 
8 

public interface ISilverlightObject {

    string Name {
        get;
    }

    void Initialize();
}

For visual composition we also need a way to add visuals to a viewport if there is one. We can add another interface to take care of this:

C#:
1 
2 
3 
4 

public interface IViewPort : ISilverlightObject {

    void AddVisual(ISilverlightObject visual);
}

All visuals can add themselves to the viewport if there is one by using this interface. The viewport should take care of the rest. Viewports can set themselves as the root visual if there isn’t one already. The non-visual objects don’t need to do anything special. They can just do their thing based on their property values and/or a call to their Initialize method. Now that we have most of the client-side plumbing in place, let’s see what we can do to add some kind of integration with ASP.NET.

ASP.NET integration

So far we have a Silverlight application which is essentially a host for composite objects. To host this application, and also to group composite objects, I introduced a SilverlightDomain ASP.NET control. I also introduced a SilverlightObject base class which you implement for every Silverlight object. This base class will register its instances with the SilverlightDomain that its part of. During rendering, the SilverlightDomain will ask every registered SilverlightObject to render XAML.

The SilverlightObject base type automatically renders a few things. Based on the value of its ClientClrNamespace and ClientTypeName properties it will render the tag-name. All properties marked with a SilverlightPropertyAttribute will be added to the XAML. It will also register callbacks for all of its events marked with the SilverlightEventAttribute. On the client those events do need to be marked with ScriptableMemberAttribute, such that our plumbing code can properly attach a handler to it which initiates the postback. When the client-side event is raised and a postback is initiated, our SilverlightObject’s RaisePostBackEvent method is invoked. By default it will try to invoke a public On<EventName>(EventArgs) method, if one exists. If you don’t want to have such a public method, or you want to raise a more complex event, you can override RaisePostBackEvent and manually raise an event.

As I just mentioned, you need to implement a few abstract properties such as ClientClrNamespace and ClientTypeName. You also need to implement PackageUrl. The SilverlightObject type will automatically render these as part of the XAML it renders. On the client, our package loader will use these attributes to find out what packages need to be downloaded. It then removes those attributes such that it ends up with 'clean XAML' that the XamlReader can load the remaining XAML directly.

A Silverlight powered ASP.NET button

Although there’s usually not much point in using Silverlight just to render primitive controls such as a button, this does concisely illustrate what it takes to put together a simple ASP.NET component that uses Silverlight for rendering. Here’s the client-side code:

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

public class Button : System.Windows.Controls.Button, ISilverlightObject {

    [ScriptableMember(ScriptAlias = "click")]
    public new event EventHandler Click;

    public Button() {
        base.Click += SimpleButton_Click;
    }

    void SimpleButton_Click(object sender, RoutedEventArgs e) {
        if (Click != null) {
            Click(sender, e);
        }
    }

    void ISilverlightObject.Initialize() {
        App.Current.LoadVisual(this);
    }
}

And the server-side code looks like this:

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 

public class Button : SilverlightObject {

    protected internal override string ClientClrNamespace {
        get {
            return "clr-namespace:Wilco.Windows.Browser;assembly=Wilco.Windows.Browser";
        }
    }

    protected internal override string ClientTypeName {
        get {
            return "Button";
        }
    }

    protected internal override string PackageUrl {
        get {
            return Page.ClientScript.GetWebResourceUrl(typeof(Button), "Wilco.Web.Silverlight.Resources.Wilco.Windows.Browser.xap");
        }
    }

    [SilverlightProperty(ClientPropertyName="Background", Converter=typeof(SilverlightColorConverter))]
    public Color BackgroundColor {
        get;
        set;
    }

    [SilverlightProperty(ClientPropertyName="Content")]
    public string Text {
        get;
        set;
    }

    [DefaultValue(0), SilverlightProperty]
    public int Height {
        get;
        set;
    }

    [DefaultValue(0), SilverlightProperty]
    public int Width {
        get;
        set;
    }

    [SilverlightEvent(ClientEventName = "click")]
    public event EventHandler Click;

    public Button() {
        //
    }

   protected override void RaisePostBackEvent(string eventArgument) {
        if (Click != null) {
            Click(this, EventArgs.Empty);
        }
    }
}

To use this on our page, we can write the following code in ASP.NET:

ASP.NET:
1 
2 
3 
4 

<wilco:SilverlightDomain runat="server">
    <wilco:Button ID="button1" runat="server" BackgroundColor="Red" OnClick="button_Click" Text="Button 1" />
    <wilco:GridViewPort runat="server" Height="300" Width="100%" />
</wilco:SilverlightDomain>

To see a demo of this, go to this page with a few Silverlight buttons on it. For the source code, you can download Wilco.Web.Silverlight.zip.

A little less than a year ago I wrote about an AsyncFileUpload component for Silverlight/ASP.NET. Several things have happened since then, so I decided to rewrite it and address some of the limitations it had along the way. To make sure we’re on the same page, let me outline the goals of this proof-of-concept:

  1. Demonstrate how OpenFileDialog and HttpWebRequest can be used to upload (large) files.
  2. Show how isolated storage can be used to enable file resumes (even across browsers on the same machine).
  3. Share some experimental object model to simplify ASP.NET/Silverlight component development.

The only real non-goal is building a file upload component of production-quality.

The result

There are a few different demos you can take a look at:

  1. AsyncFileUpload with the default template (pure HTML visualization);
  2. AsyncFileUpload with a custom template (includes two Silverlight visualizers);
  3. AsyncFileUpload used from JavaScript. Although this demo still uses ASP.NET, you could easily do the same thing from other languages/frameworks/web-servers as well.

You can also download the source code for the actual library behind all of this.

Uploading large files

Silverlight 2’s HttpWebRequest buffers the request data in memory entirely, because the underlying API it uses (provided by the browser) doesn’t support streaming. This means that – at least at the time of writing – you can’t just upload large files.

We can work around this by uploading files in chunks. It does mean we need some special handling on the server, but a basic implementation requires little effort. We just need to make sure we send some meta-data with each chunk, such that the server knows where a chunk is supposed to go. There is some overhead involved here, but we can make up for this by showing accurate and visual feedback.

By sending chunks to the server, we get support for resumes almost for free. All we need to do is keep track of some information, such as something that identifies a file and the location of the next chunk we need to send. When we store this information in isolated storage, we can use it from every browser on the user’s machine. This means you can resume uploading a file in any of the installed browsers, even if you uploaded part of the file in a different browser.

While we’re uploading chunks to the server, we know exactly how much we’ve progressed and how much there is left. We can also easily find out how fast we’re uploading. This data can be used to give feedback to the user about the progress, speed, anticipated time left, and so forth.

On the server we need to know a few things per chunk:

  1. Something that identifies the file;
  2. Something that identifies the user;
  3. The chunk’s location;
  4. The actual data.

When we have this information, we can process each chunk fairly easily. We could additionally return a checksum of the file and verify on the client that the server processed the right data. If it didn’t, the client could try resending the chunk. After the client is done uploading, it could submit a form to the server. On the server you would again need something that identifies the user (such as a session ID) to find out what exactly has been uploaded.

ASP.NET/Silverlight component development

I will go over this in a separate post, probably tomorrow.

Increasing quotas

Isolated storage is a facility that lets you read/write (transient) data without worrying about other applications accessing this data. We first introduced this in Silverlight about a year ago with the alpha release. Its support was fairly basic though, and you couldn't store more than 1mb of data per application. We have given this feature an upgrade in Silverlight 2 beta 1. Although the default limit is less than it was before (100kb instead of 1mb), you can now ask the user for a larger quota. You can do this by calling IsolatedStorageFile.TryIncreaseQuotaTo(long):

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

void OnMouseLeftButtonDown(…) {
    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) {
        if (store.TryIncreaseQuotaTo(/* 2mb */ 2 * 1024 * 1024)) {
            // The user accepted our request.
        }
        else {
            // Quota not increased… Deal with it.
        }
    }
}

This method can only be called from a Silverlight initiated action, such as a mouse-down event on a WPF object. There's no cross-browser way to tell if a user clicked on a HTML object, so unfortunately this isn't supported. We decided to only support TryIncreaseQuotaTo in response to user-initiated actions because without this restriction an app could ask for more data at arbitrary, unexpected moments. This can be both annoying and unsafe – a user could accidentally accept a request for a larger quota if the dialog suddenly takes away focus.

Silverlight will only ask the user for one of the pre-defined quotas: 100kb, 1mb, 5mb, 10mb or unlimited. This means that if you ask for 2mb, the user will actually be asked for 5mb. We do this to avoid asking the user every other second if it's ok to increase the quota by another few bytes - something the average user probably wouldn't understand.

Level of isolation

Each Silverlight application gets its own isolated store. This means that if you have two applications in the same directory, they will each get their own isolated store. This store will be accessible from every browser that's supported by Silverlight, which means you can write data in FF and read back this data in IE.

We considered using isolation per page that hosts a Silverlight application. Unfortunately this doesn't work well in shared hosting scenarios. A page at www.contoso.com/joe/foo.html can have an iframe with www.contoso.com/john/bar.html in it, and access its DOM. This means it can inject its own Silverlight application into that DOM, and thus get access to the isolated store of the other website on the same domain.

The HTML 5 spec runs into exactly this problem as well. For this type of scenario it recommends you simply do not use offline storage. Given the fact that by default you can't just execute your own code within a different Silverlight application's application domain, Silverlight should not be prone to this kind of cross-site attack.

There is a downside to Silverlight's implementation though. When hosting a Silverlight application on a server and letting third-party websites add this to their page, they would all share the same isolated store. This might not be desirable. You could work around this by using URL rewriting on the server, but you need to be careful about the potential security implications this can have.

Finally, while isolated stores are shared per application, quotas are shared per domain. This is done in anticipation of a configuration dialog we will likely be adding in a future build. Having quotas per application rather than per domain means it would make configuration a lot harder for users.

Introduction

By now you’ve probably read a thing or two about Silverlight 2 already. About the layout system, the new set of built-in controls, data-binding, styles, templates, yada yada. You may either already be cranking out code utilizing all of these visually-oriented features, or you may be left wondering:

"How is any of this useful to me? I’m already using HTML/CSS/JS on the client, and I don’t have any intentions to visually enhance or replace my website."

My goal is to demonstrate how you can take advantage of Silverlight by taking you through some of the features in Silverlight that you can use to enhance your existing website in a non-obtrusive way. Think:

  1. Writing new code in a managed language (C#, Ruby, JScript.NET, whatever) instead of native (interpreted) JavaScript.
  2. Using OpenFileDialog to read files on the client, without round-tripping to the server.
  3. Storing transient data securely on the client in isolated storage.
  4. Improving responsiveness and performance by executing work in the background through a BackgroundWorker or by using ordinary threads.
  5. Accessing cross-domain data via the networking APIs.
  6. Retrieving real-time data from the server via sockets.
  7. Binding data by re-using WPF's data-binding engine.

If you’re wondering what the point of all this is, ask yourself (or the users of your website) when the last time is you (or they):

  1. had no idea how much time there was left before an upload would finish;
  2. couldn’t resume from a failed upload;
  3. lost data (article/comment/etc.) after trying to save it;
  4. wasted lots of server-side bandwidth (and decreased client-side responsiveness) because the server had to act as a proxy between the client and a third-party website.

This can be addressed with Silverlight. The key to integrating all of this is the two-way interoperability layer in Silverlight (sometimes referred to as 'the HTML/JS bridge'), which is what I will be going through in this article. I will show you how you can access the HTML DOM and use your existing JavaScript code. You will see how you can pass managed objects back and forth, and program against them in JavaScript like ordinary JS objects. We will dive deep and take a look at what really happens under the hood to understand how far exactly our interoperability goes.

Getting started

Accessing the HTML DOM

Silverlight’s gateway to the browser is System.Windows.Browser.HtmlPage. It’s a static type with properties – such as Window, Document and Plugin – which should all be self-explanatory. The only thing to note is the IsEnabled property. Access to the hosting page may be disabled in a few situations:

  1. The hosting page set the enableHtmlAccess hosting parameter to false. This is a common thing to do when hosting advertisements or other Silverlight content that you don’t trust.
  2. Your code executes within a custom host, such as the Blend designer.
  3. The plugin unloaded itself, either by removing itself from the HTML DOM, or by changing its source property.

Generally you’ll check for this flag before initializing code that uses HtmlPage, and bail out quickly if it returns false. There’s usually no need to check for this flag throughout the rest of your code.

With this information it should be straightforward to access the HTML DOM. To insert a new element into the DOM, you may previously have written:

JavaScript:
1 
2 
3 

var element = document.createElement("div");
element.innerHTML = "Hello, World!";
document.body.appendChild(element);

This translates to the following in C#:

C#:
1 
2 
3 

var element = HtmlPage.Document.CreateElement("div");
element.SetAttribute("innerHTML", "Hello, World!");
HtmlPage.Document.Body.AppendChild(element);

You get the idea.

A common thing you may run into is that when the plugin is initializing (the application's constructor executes), the HTML DOM may not be ready yet. This means you may not be able to find an element, or you may run into 'Operation aborted' errors in IE. To deal with this, you are encouraged to check for HtmlPage.Document.IsReady and handle HtmlPage.Document.DocumentReady if the document isn't already ready.

Invoking JavaScript

Calling into JavaScript isn't much more complicated. To continue getting right to the point, let's take the following JavaScript:

JavaScript:
1 
2 
3 

var calculator = new Calculator(); // Assume this is a JS library of ours.
var sum = calculator.add(5, 1);
alert(sum);

And turn it into C#:

C#:
1 
2 
3 

var calculator = HtmlPage.Window.CreateInstance("Calculator");
var sum = Convert.ToInt32(calculator.Invoke("add", 5, 1));
HtmlPage.Window.Alert(sum.ToString());

There are a couple of things going on here.

The first thing to notice is that you use HtmlWindow.CreateInstance to instantiate JavaScript objects. Under the hood this will evaluate some JavaScript to create the instance, due to lack of native support for instantiating objects through the browser's plugin API.

Next, notice that Window, an HtmlWindow type, has an Invoke method. It inherits this method from one of its base types, ScriptObject. A few other fundamental methods it inherited are InvokeSelf, GetProperty and SetProperty. These few methods let you do just about anything. I will cover them in-depth later in this article. For now all you need to remember is that these members let you get and set values, and invoke functions.

The other thing to notice is the call to Alert. We could've actually called into alert like this:

C#:
1 

HtmlPage.Window.Invoke("alert", sum);

In fact, that's exactly what HtmlWindow.Alert does under the hood. In general we decided to make commonly-used functions first-class for discoverability and sometimes performance reasons. Whenever there is a built-in way to do something, you'll want to use that instead of using late-bound calls.

Lastly, Invoke and GetProperty both have an Object return type. They may return a bool, double, string or ScriptObject. We always return a double for numbers because this is what Safari gives us back under the hood. Also, for now assume that ScriptObject is an ordinary JavaScript object.

With this knowledge you should be able to do just about anything from managed code already. You can instantiate objects, invoke functions that return objects, set properties on those objects, and so forth.

At this point you may be wondering what happens when you pass a managed object to JavaScript. Before we go into this, let's quickly go over the opposite of what we've done so far.

Invoking managed code

Before you can invoke any managed code from JavaScript, you first need to register the objects you want to expose to the browser. You also need to mark which members are callable with the ScriptableMemberAttribute, or mark all declared members at once with ScriptableTypeAttribute:

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

public class Calculator {

    [ScriptableMember]
    public int Add(int a, int b) {
        return a + b;
    }

    // Not callable from script.
    public int Subtract(int a, int b) {
        return a – b;
    }
}

public class App : Application {

    public App() {
        HtmlPage.RegisterScriptableObject("calc", new Calculator());
    }
}

After you have a scriptable object, you can use it from JavaScript:

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 

void onPluginLoaded(plugin) {
    var calc = plugin.content.calc;
    // If you are using ASP.NET AJAX to create a Silverlight instance, use this instead:
    // var calc = plugin.get_element().content.calc;
    var sum = calc.Add(5, 1); // Case sensitive.
    alert(sum);
    try {
        calc.Subtract(5, 1); // Failure. There's no such member.
    } catch (e) {
        alert(e.message ? e.message : e);
    }
}

As with calling into JavaScript from managed code, you can pass JavaScript objects and DOM elements to managed code, assuming the signature of the invoked member permits this.

You should make sure you don't access any managed objects before they're initialized. If you need to access them when the page is loaded, you should handle the plugin's onload event and access your managed objects from there.

Inside the marshalling layer

So far the examples we've seen are very basic. They involve calling methods with primitive arguments and return values. In Silverlight our support goes much further however. As I briefly mentioned before, you can pass arbitrary managed objects back and forth between Silverlight and the browser. We also have specialized support for DateTimes, Guids, structs in general, Delegates, lists and dictionaries.

JavaScript dates are ordinary objects. This is why we treat them as such by marshaling them to ScriptObjects, just like we do with any other type of JavaScript object. The only difference is that we check what type of value the target site expects. If it expects a DateTime, we will convert it to that. Similarly, when you pass a DateTime to JavaScript, we will convert it to a real JavaScript date object.

Our support for Guids is trivial. When one is passed to JavaScript, the marshalling layer converts it to a string. We do this because it's fairly common to use Guids as identifiers for data, and the easiest way to represent them in JavaScript is via an ordinary string. If you round-trip such a string back to managed code, we will only attempt to convert it back to a Guid if the target site expects a value of this type.

Struct support is relatively basic too. We try to enforce the right semantics by making sure we create copies of values before marshalling them, which involves boxing the value. Without explicitly copying the values, there are cases where a value type would very much act like a reference type. For example, passing a value type to JavaScript where one of the fields gets mutated would in fact mutate the field on the value on the caller's site.

Anything else that is not a ScriptObject or primitive will be marshaled by reference as a managed object. To understand how managed objects are marshaled, we should first take a look at how browser objects are marshaled.

Browser object marshalling

All browser objects are represented by ScriptObjects. This includes ordinary JavaScript objects, but also windows (HtmlWindow), documents (HtmlDocument) elements (HtmlElement), element collections (HtmlElementCollection) and even managed objects (ManagedObject, an internal type) that have gotten a life inside the browser. We do a lot of things when the browser returns an object reference to us as after invoking a function or property. This is all done transparently, so you don't need to worry about any of this, but it gives you an idea of what's really happening in part of the marshalling layer.

Type inference

In Silverlight 1.1 alpha we forced you to tell us what the type of an object was going to be via a generic type parameter. While the glorified cast may seem nicer than the casts you need now, you would quickly find that you don't always know the type of an object. In one place you may assume the elements of an array are of type ScriptObject, while in reality one of the elements is in fact an HTML DOM element. This made it difficult for us to guarantee correctness, impacting the reliability of any code that used our bridge.

This has changed in Silverlight 2. We now automatically determine the type of an object. In Internet Explorer we can do this very efficiently through a few native calls to QueryInterface. In the other browsers we are forced to determine the type via some 'JavaScript reflection.' In both Firefox and Safari this boils down to inspecting nodeType to see if an object is a document or element. If the object doesn't have this property, we will check if the object is an instance of HTMLWindow, NodeList or HTMLCollection in Firefox. In Safari 2 we'll look at a few other properties that let us determine whether it's a window or element collection.

We only need to do this when the value that the browser returned to us is an object. For every value the browser returns to us, it lets us know whether it's a bool, int, double, or an object, so we don't always need to do additional work to find out the type of a return value.

Object identity

When the browser returns a value, we convert or marshal this to a managed value. For primitives this is a straightforward process. Objects however are simply a pointer, so we need to marshal it to something that is usable from managed code. We need to marshal it to a ScriptObject or one of its derivates, based on the type we previously inferred.

The simplest thing we could've done is marshal a pointer to a new object every time. This would have several consequences though. In scenarios involving lots of round-tripping of objects, memory usage would grow and the pressure on the GC would increase. More importantly, we would have a correctness problem. Imagine the following code:

C#:
1 
2 
3 
4 

var f1 = HtmlPage.Document.GetElementById("foo");
var f2 = Htmlpage.Document.GetElementById("foo");
assert(f1.Id == f2.Id); // True.
assert(f1 == f2); // False.

This code queries for the same element twice. The variables f1 and f2 both refer to a different managed object, which in turn both wrap the same native browser object. This object identity issue might seem unimportant, but it hits you whenever you need to tell whether two objects are the same.

We could deal with this by overriding Equals/op_Equals and letting it compare the underlying browser object pointers, and let GetHashCode return the pointer's hash code. Although this would probably address the majority of the situations, it would simply be a work around. There still wouldn't be true object identity. You would still get the wrong result when comparing two variables typed as Object, as the CLR would end up comparing the references, rather than calling into the overloaded equals operator. Furthermore the GetHashCode implementation would be rather inefficient on Safari – which I'll explain further in a second.

To correctly implement object identity, we need to know whether we already marshaled a browser object before. If we did, we simply return that object. Otherwise, we marshal it and make sure we keep track of the managed object we create. We introduced an object cache in ScriptObject for exactly this.

The object cache maps pointers to WeakReferences of ScriptObjects. By using weak references we avoid keeping alive objects forever. Additionally, ScriptObject implements a finalizer to remove its entry in the object cache, such that we're not leaking at least sizeof(IntPtr) bytes per object that no longer exists.

At this point there's only one remaining problem. Safari doesn't preserve object identity in its plugin API, which means that we get back different pointers every time for the same native browser object. It is important to note that the pointers we get back are pointers to objects which wrap the real browser object – even so in FF. Both browsers support the NPAPI, a plugin API supported by most browsers, which defines the contract of an object (NPObject). To adhere to this contract, it usually needs to marshal its browser objects itself. Firefox seemingly does this by caching NPObjects to preserve object identity and reduce memory overhead. Safari apparently creates a new NPObject every time.

We addressed this problem by introducing a second object cache, which maps a custom identifier (an int) to an IntPtr of the browser object that we marshaled before, which is also the key in the primary object cache. We generate custom identifiers ourselves, and try to store this in a private field ($__slid) on the object, such that we can read this back later when we need to marshal another pointer. Whenever we are trying to marshal a pointer, we first try to read this private field and do a lookup in our secondary object cache to get back a different pointer (to this object) which we marshaled before.

This secondary cache is purely used as a heuristic to improve performance. We don't rely on any of the information we gather in this process. We use it to do a lookup in the real object cache, and we then call into script to verify the two pointers we have (a pointer which we want to marshal, and a pointer we marshaled before and believe points to the same browser object) do indeed point to the same browser object. We do this by calling into an anonymous JavaScript helper function, which looks like:

JavaScript:
1 
2 
3 

function(obj1, obj2) {
    return (obj1 == obj2);
}

When we pass two pointers to native browser objects, NPObjects, the browser will marshal these back to real browser objects. In every browser, including Safari, it will get back the original browser objects. Because of this, we can correctly tell whether two objects are the same in JavaScript.

Back in managed code we can use this to verify that two pointers are indeed the same. If they are, we go ahead and return the managed object we created before for this object. If they aren't, we will walk through our primary object cache and check if this pointer really wasn't marshaled before, by comparing each pointer to the current pointer via a call to our JavaScript helper function. If it turns out we never marshaled this browser object before, we will generate a new identifier and associate try to associate it with this browser object. We will update both object caches accordingly.

This process does involve a slight performance hit on Safari, in particular when you often marshal objects that you never marshaled before. Unfortunately there's not much we can do about this given the way Safari decided to implement the NPAPI, without sacrificing the usability of our managed API.

Managed object marshalling

Marshalling to JavaScript objects involves creating a browser object which wraps the managed object and acts as a proxy. Internally it's a ManagedObject which derives from Scriptobject. When a managed object is round-tripped, we will return its ManagedObject, rather than the real managed object it wraps. You can access the underlying managed object via the ScriptObject.ManagedObject property.

We decided to return ManagedObjects rather than their underlying objects to make the programming model more consistent and increase the reliability of user code: every object can be treated in the same way, just like you can in JavaScript. This means you don't have to constantly check if an object is either a browser object or a managed object.

ManagedObjects reference a ManagedObjectInfo which stores a method table with entries for each scriptable member for that type. The entries are wrappers around the actual members, which makes it possible for us to add special-casing for certain type of members, such as properties and events.

For every managed type we lazily construct the (case-sensitive) method table and cache this information for performance reasons. We use weak references such that this information can be released when there's no reason to hold onto it any longer. The process for constructing the method table for most types involves walking through all public members with a ScriptableMemberAttribute. We then walk their inheritance tree. For every type in this tree with a ScriptableTypeAttribute we add all of that type's declared members. This means that if you apply this attribute to your type, it will only add the members you declared in your type; it does not include any inherited members. We also add an addEventListener and removeEventListener method to the method table to support events in the same kind of way they're supported in the HTML DOM.

Another method we add is createManagedObject. This method lets you create instances of complex types that are used as parameters for any of the scriptable members. For example, if you have a CustomerService type with a scriptable Add(Customer) method, you will be able to create an instance of such a Customer type, assuming it has a public default constructor:

C#:
1 
2 
3 
4 

var customerService = plugin.content.customerService;
var customer = customerService.createManagedObject("Customer");
customer.Name = "John Doe"; // Assume we marked Customer.Name as a scriptable member.
customerService.Add(customer);

By automatically doing this work for you, you don't need to add calls to HtmlPage.RegisterCreatableType all over the place. You also don't need to hold on to the plugin object in order to call createObject to instantiate a type registered via RegisterCreatableType. In other words, each object that is a receiver (i.e. has a scriptable method which takes a complex object) is also a factory which creates complex objects.

We have basic support for method overloading, by finding the overload which matches the number of arguments and by doing some basic parameter validation.

Delegates are a special-case scenario. We map a delegate's DynamicInvoke method and add an apply method. This method allows you to invoke delegates in a 'late-bound' fashion from JavaScript. We will take care of unrolling the array of arguments and call DynamicInvoke on the delegate.

JavaScript functions are automatically marshaled to delegates if the target site expects one. Silverlight 2 currently only supports delegates that are compatible with the void(object, EventArgs) signature, such as EventHandler and EventHandler<TEventArgs>. Arbitrary delegate types aren't supported – at least not at the time of writing.

Types that implement IList will end up storing members in the method table that correspond to the functions that JavaScript arrays support. This means managed arrays, List<T>s, and so on can be treated and used as arrays in JavaScript.

Similarly, types with support for IDictionary<string, TValue> are also made more accessible from JavaScript. The keys in such a dictionary translate to fields in JavaScript. When you set a field, we will add or set the value with the field's name as the key. Accessing a field translates to a lookup in the dictionary.

Lastly, we always map the ToString method of every type, because some browsers implicitly call toString on objects, for example when passing them to alert.

Object lifetime

Normally a managed object gets collected by the GC when it's no longer rooted. In other words, when an object can no longer be accessed, the GC is allowed to collect it. When we pass such an object to the browser, we have to prevent this behavior. We do this by pinning the ManagedObject and giving lifetime ownership to the browser object that wraps it. This browser object is always a reference counted object which will unpin the ManagedObject when the last reference is released.

We will re-create the native browser object on the fly when a ManagedObject is passed to the browser after its native counter-part no longer exists.

If the managed runtime shuts down when the browser still has a reference to a managed object, for example by removing the plugin from the DOM after getting the reference, the browser object wrapping the managed object will know about this and gracefully let all calls fail. When the last reference goes away, it won't attempt to unpin the managed object either, which isn't a problem as the managed object was already freed from memory during the managed runtime shutdown.

The lifetime of all other objects is more trivial. Either when the managed runtime shuts down, or when a ScriptObject gets GC'ed because it's no longer reachable, we will release our reference to the native object. We guarantee that this reference is released on the UI thread.

Exception handling

Silverlight returns exceptions thrown in managed code as JavaScript errors. It does this by calling ToString on the exceptions, and then it passes this exception text on to the caller. Internet Explorer stores this in an error object. When catching this error, you can get back the exception text by accessing the message field. In all other browsers the object that you catch is the exception text.

There's one exception. It appears that Safari ignores any reported errors. This means that if you invoke a method which throws, your JavaScript code won't know if anything went wrong. It'll simply not get back a value. One way to deal with this is by always returning something from managed code, such as a Boolean value. This way you can assume something went wrong when you don't get back any value.

The opposite – throwing exceptions in JavaScript back to managed code – works similarly. Unfortunately the plugin API we use for browsers other than Internet Explorer (NPAPI) doesn't have first-class support for reporting exceptions. We can only tell whether a call failed or succeeded. We considered invoking JavaScript functions via a helper function, but this requires the ability to invoke JavaScript functions in a late-bound way (via Function.apply), something that isn't supported by every version of Safari that we support. Consequently we simply throw a generic InvalidOperationException whenever a JavaScript function invocation fails. In Internet Explorer it contains the error text of the exception that was thrown, while in all other browsers you will simply see a generic error message.

Use case: Extending the browser with OpenFileDialog support

Most browsers today don't support reading files on the client. You have to use an input element of type 'file' and let the browser send the file to the server, which can then echo the contents back to the client. Besides the somewhat awkward user experience, it also complicates the programming model. You have to restore the state of the page, or you have to use an iframe and do the round-tripping there.

Silverlight comes with an OpenFileDialog feature. This dialog provides read-only streams to one or more files selected by the user. Out of the box you can only use this feature from managed code. It takes little effort to encapsulate this feature though. Let's take a look at what it takes to extend the browser with this feature.

Step 1: Adding a Silverlight island to the page

There are several ways to do this. The easiest is to use VS and let it generate a page with Silverlight for you. It will embed Silverlight via an object tag, which refers to your Silverlight application (a .xap file). To integrate this application with your website, all you need to do is copy this object tag declaration and the .xap file.

Since our feature doesn't have any UI on the page, you can make sure the object tag's width and height is 0 pixels. You shouldn't use the display or visibility styles to make Silverlight invisible, as Silverlight may not even run at all in that case.

Step 2: Making OpenFileDialog scriptable

We can make OpenFileDialog scriptable by introducing a few wrappers. For the OpenFileDialog type itself we can simply add a scriptable method to our application. We'll let this method return an array of scriptable objects which wrap the OpenFileDialog's SelectedFiles property. In code this looks like:

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

public class App : Application {

    public ScriptableFileInfo OpenFiles(string filter) {
        var dialog = new OpenFileDialog();
        dialog.Filter = filter;
        if (dialog.ShowDialog() == DialogResult.OK) {
            var fileInfos = new ScriptableFileInfo[dialog.SelectedFiles.Length];
            for (int i = 0; i &lt; dialog.SelectedFiles.Length; i++) {
                fileInfos[i] = new ScriptableFileInfo(fileInfos[i]);
            }
        }
        return new ScriptableFileInfo[0];
    }
}

And the ScriptableFileInfo type can be implemented like this:

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

public class ScriptableFileInfo {

    private FileDialogFileInfo _info;

    [ScriptableMember(ScriptAlias = "contents")]
    public string Contents {
        get {
            using (var reader = new StreamReader(_info.OpenRead()) {
                return reader.ReadToEnd();
            }
        }
    }

    [ScriptableMember(ScriptAlias = "fileName")]
    public string FileName {
        get {
            return _info.FileName;
        }
    }

    public ScriptableFileInfo(FileDialogFileInfo info) {
        _info = info;
    }
}

Now, we still need to expose this to script. There are two options we can choose from:

  1. Add a ScriptableMemberAttribute to our OpenFiles method, and register our application object as a scriptable object by calling HtmlPage.RegisterScriptableObject.
  2. Encapsulate our OpenFiles method via a delegate which is accessible from script.

We will go with the last option because it's the easiest to work with from JavaScript. It's also just as easy to implement. In our application's constructor we simply add:

C#:
1 

HtmlPage.Window.SetProperty("openFiles", new Func<string, ScriptableFileInfo[]>(OpenFiles));

Step 3: Using OpenFileDialog from JavaScript

Now that we've added an openFiles function to the window object, we can access it from JavaScript like this:

JavaScript:
1 
2 
3 
4 

var selectedFiles = window.openFiles("Text Files|*.txt;*.csv|All Files|*.*");
for (var i = 0; i < selectedFiles.length; i++) {
    alert(selectedFiles[i].fileName + ": " + selectedFiles[i].contents);
}

We do still need to be careful about when we execute this code. The openFiles function won't be there until our application has been downloaded and its constructor has executed. You can account for this by handling the onload event on the Silverlight plugin. Once this event has occurred, you should be able to safely use the openFiles function on the window object.

Conclusion

Silverlight does a lot of work under the hood to make it a first-class citizen in the browser. People writing managed code can fully integrate with the existing codebase for a website. Those who prefer to continue writing JavaScript can fully leverage the features offered by Silverlight with little effort. This should allow everyone to get at least something out of Silverlight.

Client-side scripting comes with a bunch of limitations. There's no client storage you can read from or write to. You can't read from files a user selected from your <input type='file' /> element, etc. To address this, I've encapsulated a few services that Silverlight enables. I have packaged this up as an ASP.NET control, but nothing prevents you from directly using the Silverlight assembly and host it on an arbitrary webserver.

For the results of this control, check out the ClientServices demo. Or download the control's source-code.

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 

function saveDraft() {
    /* Get the scriptable, managed services object */
    var services = $get("clientServices").Content.services;
    var messageTextBox = $get("messageTextBox");

    /* Save the contents of the textbox in a file called Draft.txt on the 
       client, using isolated storage. We can read from this using 
       services.ReadFile("Draft.txt"), even if the user restarted the browser. */
    services.SaveFile("Draft.txt", messageTextBox.value);
}

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 = contro