Client-side HTML controls for Silverlight 2

5 October 2008

What if you could use and create ASP.NET-like web controls that are 100% client-side, powered by the Silverlight 2 runtime? What if ViewState was a thing of the past? Would you like to cut your web application's load on your servers from 50 pageviews down to 0? Wouldn't your users love it if your application was always instantly responsive?

A week ago, I posted "If you can do it in JavaScript, you can do it in managed Silverlight .NET code." Here's proof that the HTML interoperability capabilities in Silverlight are not only world-class, but also amazingly powerful.

This "managed HTML controls" concept is a neat idea: something that might whet your appetite to explore the interoperability features and consider using the feature to build sophisticated applications. However, key hurdles leave this as a conversation piece more than a new application platform. At the end of the day, the Silverlight presentation framework as it stands if going to be your best bet for a majority of your applications.

Exploring and thinking about changing the boundaries and status quo of web apps is something that's fun and challenging. A client-side implementation of ASP.NET, enabling rich browser apps written in C#, was totally my dream a few years ago. Nikhil's Script# actually can get you some of the way by letting you write client apps in .NET.

Today, a client-side ASP.NET HtmlControl is interesting, but not a game-changer, given the maturity of JavaScript platform and the richer app platforms out there like Silverlight (that is based on WPF) and Flex.

What's the HtmlControls concept look like?

Here's a look at a few of the types.

By exposing strongly typed properties, methods, and events, your .NET HTML application code will be cleaner, easier to maintain, enable you to use static analysis tools, and even perform unit testing.

Although the HtmlControlBase exposes a lot of the sample string-based properties as the standard Silverlight HtmlElement, it adds overloads with enumeration-based parameters (cut down on spelling errors), and stop having to duplicate strings through your app:

The CssAttribute enum.

You can set Font properties like you would in ASP.NET, without worrying so much about CSS at the end of the day:

The FontInfo object.

The same goes for standard HTML properties defined by the W3C - helpful HtmlProperty enum:

The HtmlProperty enum.

Thinking about the pros and cons

Some of what you can do with managed HTML controls:

  • Create strongly typed custom controls: be more efficient, create simpler code, less spaghetti code
  • Develop rich web applications using Visual Studio, including built-in FxCop, code refactoring, the object browser, XML documentation support, and more
  • Create sophisticated AJAX-like applications with IntelliSense support
  • No need for your controls to POST-back to the server or worry about ViewState ~ these are stateful, since they are in memory
  • Integrate with Silverlight 2 applications, and all the richness it provides in its framework and class libraries
  • You could move an application like 37signals' Backpack out of a server farm, store the .Xap on a CDN, and use occassional cross-domain web service calls to store and retrieve data... imagine, some types of apps could serve a billion application users with only 1 billion .Xap downloads from your CDN, instead of generating tens of billions of web pages on an expensive web farm

What's so great about it?

  • Replace loosely typed string property setters (spaghetti code) with strongly typed code
  • Like Script#... but with the live .NET Silverlight 2 CLR!
  • Kind of like a cheap ASP.NET implementation, on the client side, that works on Windows and Mac (IE, Firefox, Safari)
  • Proves how powerful Silverlight can be, even for out-of-the-box projects
  • Fun!

What are the drawbacks?

  • Silverlight has a much richer presentation story: really rich controls, animations, design tools like Blend, and WPF goodness. Videos. Audio. Full-screen.
  • Why build effectively an HTML-based AJAX app using Silverlight, and yet require your users to have the Silverlight 2 runtime on their machines?
  • The memory performance of this concept is probably quite bad: each element is already in the browser; there's a pointer to that from the native platform in Silverlight; there's the Silverlight HtmlElement object instance; and then there's the HtmlControl type. That's a lot of duplicated stuff!
  • The performance of the concept in general isn't going to be too awesome: there's a lot of interop here between the web browser, JavaScript, the browser's plugin APIs (NPAPI or ActiveX), the managed CLR, and back again
  • All the points made here to NOT use the HTML DOM bridge

What does it give you that you don't get with JavaScript?

  • C#, VB, or DLR languages like Ruby running in the browser, client-side, interacting with the HTML page
  • The ability to use Visual Studio for client web development

And, at the end of the day, Script# might get you these things, without the Silverlight dependency.

What does it give you that you don't get with Silverlight today?

  • Add managed code to existing AJAX applications and HTML elements by hooking up events and altering properties
  • Easier way to work with HTML pages than using the functional but basic HTML interop
  • Create rich HTML text editing controls and web forms; create elements for the browser's built-in printing system

Using the HTML control framework

Example: Setting the width and height

Without this framework, to set the width of an HtmlElement in Silverlight, you'd need to write this code (assumes you have a regular Silverlight HtmlElement instance called "element"):

// Setting the height and width - notice the typo you wouldn't know 
// about until runtime:
element.SetStyleAttribute("width", "50px");
element.SetStyleAttribute("height", "450ppx");

There's no type checking: you won't know about spelling errors or other problems until runtime, and if you do any CSS manipulation, your code will get very difficult to maintain. Here's the same code, with a managed HTML control (using a few different ways of setting the value):

// Set the width and height of the control
control.Width = 50;
control.Height = new Unit(450, UnitType.Pixel);

Same # of lines of code, but a little more "attractive" API.

You can also wrap existing HtmlElements with a managed control in some cases: either standard parts of your web page, existing AJAX applications. This is possible when the constructor exposes a HtmlElement parameter.

Creating a composite control

Here's a simple "user control" that has a link and a button inside of it:

using System;
using WebPage = System.Windows.Browser.HtmlPage;
using Microsoft.Silverlight.Testing.Html;

namespace HtmlControlSample
{
    /// <summary>
    /// A simple managed HTML control.
    /// </summary>
    public class MyUserControl : HtmlDiv
    {
        /// <summary>
        /// A simple link that is hooked up to the app.
        /// </summary>
        private HtmlAnchor _appLink;

        /// <summary>
        /// A simple button.
        /// </summary>
        private HtmlButton _button;

        /// <summary>
        /// Initializes a new instance of the MyUserControl control.
        /// </summary>
        public MyUserControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initialize composite controls and add them as children.
        /// </summary>
        private void InitializeComponent()
        {
            // A link that will display the current time
            _appLink = new HtmlAnchor(
                "Click me for the time!",
                (sender, e) => Alert(DateTime.Now.ToLongTimeString()));

            _button = new HtmlButton();
            _button.InnerText = "Toggle my boldness";
            _button.Click += (sender, e) => _button.Font.Bold = !_button.Font.Bold;

            Controls.Add(_appLink);
            Controls.Add(_button);
        }

        /// <summary>
        /// Uses the Alert method of the HTML interoperability feature to 
        /// display text.
        /// </summary>
        /// <param name="text">The text to display.</param>
        private static void Alert(string text)
        {
            WebPage.Window.Alert(text);
        }
    }
}

To hook it up to the application, here is a simple class that shrinks the Silverlight plugin to not occupy any space on the page, that creates a new instance of the MyUserControl type and appends it to the web page body.

The HideSilverlightPlugin function is important because it lets you press F5 while working in a standard Silverlight application project.

using System;
using Microsoft.Silverlight.Testing.Html;
using WebPage = System.Windows.Browser.HtmlPage;

namespace HtmlControlSample
{
    /// <summary>
    /// A sample HTML application.
    /// </summary>
    public class SampleHtmlApp
    {
        /// <summary>
        /// The application's user interface control.
        /// </summary>
        private MyUserControl _myUserInterface;

        /// <summary>
        /// Initialize and run the application.
        /// </summary>
        public static void Run()
        {
            new SampleHtmlApp();
        }
        
        /// <summary>
        /// Initializes a new instance of the SampleHtmlApp class.
        /// </summary>
        private SampleHtmlApp()
        {
            HideSilverlightPlugin();
            
            // Creates a new managed HTML control
            _myUserInterface = new MyUserControl();

            // Appends the control to the web page body
            WebPage.Document.Body.AppendChild(_myUserInterface);
        }

        /// <summary>
        /// Takes the current Silverlight plugin instance and changes its size 
        /// to be 0x0 pixels. Makes the same change to the hosting div element. 
        /// This enables F5 testing of a managed HTML control app inside Visual 
        /// Studio when using the standard TestPage.html in a Silverlight app.
        /// </summary>
        private void HideSilverlightPlugin()
        {
            HtmlControl plugin = new HtmlControl(WebPage.Plugin);
            HtmlControl host = new HtmlControl(WebPage.Plugin.Parent);
            plugin.Width = 0;
            plugin.Height = 0;
            host.Width = 0;
            host.Height = 0;
        }
    }
}

You can call the Run() method from anywhere in your Silverlight application - both can co-exist without issue. However, I've just replaced the RootVisual call in the Application_Startup method to demonstrate this example:

        public App()
        {
            this.Startup += this.Application_Startup;
            this.UnhandledException += this.Application_UnhandledException;

            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            SampleHtmlApp.Run();
        }

Here is this simple HTML application (powered by Silverlight) running on OS X 10.5 in the Safari web browser:

The app running in Safari.

Appending a managed control tree to HtmlElements

Thanks to an extension method, you can append a managed HTML control (that inherits from HtmlControlBase) to the standard HtmlElement inside Silverlight as if the control is an HtmlElement:

The extension method on HtmlElement::AppendChild.

The managed control wrapper actually tracks any changes (styles, properties, child controls) to the control and waits until it is inserted into the live web page tree to apply the properties and create the child elements.

At the end of the day there are a lot of types involved here, bridging several layers of abstraction. This is definitely not a "lightweight" solution.

Looking at the abstractions.

These bits are in use today

One of the key features of the in-browser Silverlight unit test framework is that it exposes an interactive log inside the browser, using the HTML interoperability feature.

If you've used the Silverlight unit test framework, then you've used this managed HTML controls implementation without knowing it: to move away from "spaghetti-code", we setup the log to use simple custom controls built on top of this framework.

Here's a few screenshots of components, including a ProgressBar control:

The unit test log, powered by managed HTML controls.

Here's the implementation of the ProgressBar:

using System;
using Microsoft.Silverlight.Testing.Html;

namespace Microsoft.Silverlight.Testing.UnitTesting.UI
{
    /// <summary>
    /// A control to visualize a test run's progress.
    /// </summary>
    public partial class TestRunProgress : HtmlDiv
    {
        /// <summary>
        /// The percent of the run that is complete.
        /// </summary>
        private double _percent;

        /// <summary>
        /// The progress bar.
        /// </summary>
        private HtmlDiv _progress;

        /// <summary>
        /// Initializes a new progress bar control.
        /// </summary>
        public TestRunProgress() : base()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Gets or sets the percent complete on a scale from 0 to 100.
        /// </summary>
        public double PercentComplete
        {
            set
            {
                _percent = value;
                UpdateProgressBar();
            }

            get
            {
                return _percent;
            }
        }

        /// <summary>
        /// Sets the color of the progress bar.
        /// </summary>
        /// <param name="value">The color value.</param>
        public void SetProgressColor(string value)
        {
            _progress.BackgroundColor = value;
        }

        /// <summary>
        /// Updates the progress bar.
        /// </summary>
        private void UpdateProgressBar()
        {
            double width = (double)GetProperty(HtmlProperty.ClientWidth);
            if (width <= 0)
            {
                return;
            }
            int round = (int) Math.Round(_percent * (width / 100));
            _progress.Width = round;
        }

        /// <summary>
        /// Initializes the web control.
        /// </summary>
        private void InitializeComponent()
        {
            SetStyleAttribute(CssAttribute.Position, "relative");
            _progress = new HtmlDiv();
            _progress.SetStyleAttribute(CssAttribute.Position, "absolute");
            _progress.Position.Left = _progress.Position.Top = _progress.Position.Bottom = 0;
            SetProgressColor(Color.Black);
            MakeTransparent(_progress, 25);
            Controls.Add(_progress);
        }

        /// <summary>
        /// Makes a control transparent.  Works with several browsers.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <param name="opacity">The opacity amount.</param>
        private static void MakeTransparent(HtmlControl control, int opacity)
        {
            control.SetStyleAttribute("filter", "alpha(opacity=" + opacity + ")");
            control.SetStyleAttribute("-moz-opacity", "." + opacity);
            control.SetStyleAttribute("opacity", "." + opacity);
        }
    }
}

Clicking on a test method name expands a details drill-down full of stats and information, including links that can be clicked to retry tests and display easy-to-copy details:

The unit test log with details expanded.

Download the bits

Since this is only a concept, and not something you would want to use in a production application, the bits are currently only available as part of the Silverlight unit test framework download for the Silverlight 2 Release Candidate 0.

The implementation is not a complete framework, but rather a minimal set that enables basic functionality: links, buttons, and displaying and creating HTML elements. It does not contain a rich web forms implementation, cool validation controls, or anything like that.

In the future I may make the concept available as a download separate from the test framework, but today I want to make it clear that it isn't something that is ready to really be used in everyday web applications... so hopefully the total size of the test framework assembly will discourage that [81kb]!

It is totally unsupported, to be considered prototype code.

Here's the set of types and enums in the namespace today. You'll see that it is a minimal set, and isn't including a lot of the typical HTML controls like check boxes, forms, or web form controls. There's room to innovate, but the value may not be there:

List of types in the Microsoft.Silverlight.Testing.Html namespace.

Do let me know if you like it, or build anything noteworthy, in the comments, and I will post a follow up in the future.

Other ideas

There is potential for the concept in some apps, and I'm sure some really brilliant folks out there could do any of the following things that've come across my mind:

  • Writing an ASP.NET-style XML parser using System.Xml.Linq, enabling you to embed .aspx-like pages inside your application to enable generating managed HTML control trees at runtime
  • Integrating with XAML, so a XAML page could contain a managed HTML control tree
  • Writing a compatibility layer that would allow custom ASP.NET controls to run inside Silverlight
  • Integrating or porting popular AJAX functionality (animations, rich control behaviors)
  • Writing applications that live as a .Xap file at your CDN or Amazon S3, and access cross-domain web services for any data needs, combined with isolated storage

I've built a number of fun applications using this framework and think it's a neat concept, I will share those sometime...

Special thanks to Hao Kung, Huangli Wu, Stefan Schackow, and Wilco Bauwer for their excellent work in shipping the amazing HTML DOM feature inside Silverlight 2!

Hope you find this interesting,
-Jeff

Jeff Wilcox is a Software Engineer at Microsoft in the Open Source Programs Office (OSPO), helping Microsoft engineers use, contribute to and release open source at scale.

comments powered by Disqus