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.
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:
You can set Font properties like you would in ASP.NET, without worrying so much about CSS at the end of the day:
The same goes for standard HTML properties defined by the W3C - helpful HtmlProperty enum:
Some of what you can do with managed HTML controls:
What's so great about it?
What are the drawbacks?
What does it give you that you don't get with JavaScript?
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?
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:
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 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.
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:
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:
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:
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.
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:
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.