Python in Your Browser with Silverlight

Introduction to IronPython and Silverlight

Silverlight Python

Introduction

I have been developing with Python for nearly four years, and have been working with IronPython for around eighteen months now.

I am the author of the following Python projects:

I'm particularly grateful to IronPython. Because of it I'm now able to earn a living programming with Python, for Resolver Systems.

I've written several articles and tutorials on Python, and I'm currently writing a book, IronPython in Action, for Manning publications.

Resolver Systems is a small company (6 developers but looking to hire) based in London. Resolver Systems was created to develop a new business application, aimed at the financial services market. It is currently in use with our first few customers and a small number of people in our private beta test program. We hope to move to a wider beta test phase soon.

Resolver the company started in late 2005, and I joined in April 2006. At this stage IronPython was still in alpha.

Resolver is a Rapid Application Development tool with a spreadsheet interface. There is both a desktop version and a fledgling Resolver Web Server.

.NET was initially chosen as the development platform, but a scripting language was needed as an integral part of the application. After discovering and trying IronPython (whilst it was still Beta) the two developers, as they then were, decided to write the whole application with IronPython.

The Resolver codebase currently stands at around thirty thousand lines of production plus eighty thousand lines of test code. About 1% of the production code is in C# and the rest is IronPython.

IronPython in Action is a book that I and a colleague are writing on IronPython. It was started earlier in the year, and just as I finished the first part I had to rewrite it because Microsoft released Silverlight and IronPython 2!

The first five chapters are now available via Manning's 'early access program', where you get to access the book as it is written. The first five chapters are an introduction to IronPython, Python and the .NET framework. It goes through creating an example structured IronPython application. The rest of the book will be more 'recipe focussed' covering a range of topics like testing, ASP, databases and web services, embedding and extending IronPython and of course a lot more. Smile

This talk is inspired by the talk on dynamic languages by John Lam and Jim Hugunin from Mix 07. You can watch the video.

Aim of the Talk

To inspire you as to what is possible with Silverlight and give you enough details to start experimenting.

IronPython

Why use IronPython?

Microsoft are serious about IronPython and dynamic languages for the .NET framework. Microsoft have built IronPython support into the following projects:

IronPython is very well integrated with the .NET framework. Strings in Python are .NET string objects, and they have the Python methods that you would expect. The same is true for the other types.

We can also take .NET assemblies and import classes / objects from them, and use them with no wrapping needed.

Silverlight

Silverlight is a sandboxed browser plugin for creating rich client-side web applications. It runs on Windows and Mac OS, supporting Firefox, IE and Safari (with support for more browsers on the way).

There is a Mono version called Moonlight, which runs on Linux (supporting Firefox). This is now officially backed by Microsoft who are making the media codecs, the test suite and the full specification available to the Mono team. Initially it will target Firefox on Linux, but eventually will run on multiple browsers everywhere that Mono runs.

Version 1.0 is just out and focuses on the streaming media capabilities.

Version 1.1 is still in alpha and ships with a cut down version of the CLR (the core of the .NET framework). It can be programmed with C# or any of the DLR languages which includes IronPython.

All the examples I show here require Silverlight 1.1 Alpha Refresh

What Does Silverlight Offer

Visit the Showcase for some of the funky things that Silverlight can do. There is also a set of examples using IronPython at codeplex.com/dynamicsilverlight.

Silverlight separates presentation from code (design from development), through XAML. This is an XML markup for creating user interfaces, including hooking up events and defining animations. This is intended to be created by tools like Expression Blend (currently in free beta). There is also a version for Mono called 'Lunar Eclipse' under development.

What Silverlight doesn't come with yet is very many 'controls' for building user interfaces. You have to create everything yourself. There is already quite a range of 'third party' interface components being created (including someone implementing the Windows Forms API for both Silverlight and Flash!) and there will be controls included in the final version of Silverlight 1.1. Because of this (and because it is a shame to throw away everything we already know about clientside browser programming, and all the great libraries that are available), part of what we will be looking at is how to use Silverlight with the browser DOM and (normal) Javascript.

Dynamic Languages on Silverlight

The DLR: a dynamic language runtime that can run dynamic languages on .NET and in Silverlight.

This means lots of dynamic languages - IronPython, Ruby, Visual Basic, Javascript.

As well as IronPython Microsoft are developing IronRuby. Dynamic languages DLR languages already available, and under development, built on top of the DLR currently include:

Dynamic languages are particularly compelling on the web because it is just text. The XAML is text, the HTML is just text and the code is just text. This makes it much easier for the community to share examples. This is how the web was developed, it is fundamentally a text based technology.

With Silverlight, you can still have the rich experience - but it can all remain text, even when deployed.

The dynamic languages can also interoperate with the traditional .NET languages. They can use classes created from C# /VB code without wrappers. To the dynamic languages they are natural objects just as much as ones they have created themselves.

What you get 'out of the box' with Silverlight 1.1 is IronPython and Managed JScript.

Why managed JScript? Managed Javascript is ECMAScript compliant (ECMAScript 3.0) - so code written AJAX style with traditional Javascript can be ported over to run in Silverlight, and run much faster. Really well integrated with the rest of the platform.

In their Mix 07 talk, John Lam and Jim Hugunin showed a VB example, using LINQ over objects deserialized from JSON fetched from a web-service. The VB implementation isn't public yet, and the LINQ support in IronPython isn't done - but hopefully you'll also see some cool examples today.

The DLR itself consists of three main parts:

  1. Dynamic type system, shared type system. Shared with existing .NET languages.
  2. Language hosting system.
  3. Compiler system (with parser and AST).

Structure of a Silverlight App

The Silverlight Airline Demo

The XAML defines a tree of objects that represent the user interface. You can use this to create elements of the interface include animations and hook-up events.

The XAML is actually compiled. Almost anything that can be done with XAML can be done from code (not yet animations). For example, TextBlock XAML elements have a corresponding TextBlock class. Although I am generally no fan of visual design tools, nor of writing XML by hand, it can be more verbose to do things in code than to use XAML. Dynamic languages are particularly good at manipulating test, and XAML is just text, so you can dynamically generate and consume XAML.

Let's look at the five files you need for a minimal IronPython Silverlight application.

HTML File

    <script type="text/javascript"
        src="Silverlight.js"></script>
    <script type="text/javascript"
        src="CreateSilverlight.js"></script>
</head>
<body>

    <div id="SilverlightControlHost">
            <script type="text/javascript">
                    CreateSilverlight();
            </script>
    </div>

The webpage that embeds the Silverlight control.

It references the two Javascript files and calls the CreateSilverlight function.

The Two Javascript Files

parent_element = document.getElementById('SilverlightControlHost');
control_id = "SilverlightControl";
function CreateSilverlight()
{
    Silverlight.createObject(
        "minimal.xaml",
        parent_element,
        control_id,
        {width: '640', height: '480',
         version: '1.1'},
        {onError: null, onLoad: null},
        null
    );
}

Silverlight.js is provided by Microsoft is the code that actually sets up the control, and presents a download button if Silverlight isn't installed.

CreateSilverlight.js is where you initialize the control.

Silverlight.createObject initializes the control (you can alternatively use createObjectEx which takes a single object with named members). You can configure the size of the Silverlight control, specify the XAML file to use, and provide Javascript functions to call once the control has been created or in case of an error (onError and onLoad).

XAML File

<Canvas x:Name="root"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <x:Code Source="minimal.py"
        Type="text/python" />
    <Canvas x:Name="LoaderHelper"
        Loaded="OnLoad" />

    <TextBlock x:Name="textBlock">
        Hello World from XAML
    </TextBlock>
</Canvas>

The XAML file defines a root canvas that will be loaded when the Silverlight control is created. The Python file is loaded using <x:Code Source="minimal.py" Type="text/python" />. In order to hook up the Python code to our XAML we use a 'helper loader' canvas that calls the Python function OnLoad when it is loaded. This slightly different from how you create C# projects (where the Canvas is associated with a class in an assembly that extends the Canvas object).

This XAML also has a TextBlock with a name and some text. We will manipulate this from the IronPython file.

Python File

# root is the name of the top level canvas
# and is available in this namespace

def OnLoad(sender, event):
    textBlock = root.FindName("textBlock")
    textBlock.Text = "Hello World from IronPython"

It gets a reference to the TextBlock created from XAML and changes the text on it.

As with the normal IronPython we can use the clr module to add references to assemblies and we can import Python files which will be fetched from the server.

The IronPython Web IDE

www.voidspace.org.uk/ironpython/webide/webide.html

The IronPython Web IDE

To experiment with IronPython we'll use a simple tool I've created - the IronPython Web IDE.

This is available online and you can also download it as a project.

It comes with several pieces of example code, but also lets you explore the Silverlight API and load and save Python code.

It uses the Javscript EditArea syntax highlighting Python editor.

Standard out is diverted, so that print statements are sent to the 'debugging pane' at the bottom of the screen. Tracebacks are also printed there, to aid debugging.

The Video Player

Using the MediaElement class from IronPython.

from System.Windows.Controls import MediaElement
from System import TimeSpan, Uri, UriKind

m = MediaElement()
u = Uri('HelloWorld.wmv', UriKind.Relative)
t = TimeSpan(0)
m.Source = u
m.Position = t

Accessing the Browser DOM

Changing HTML elements and setting style attributes.

from System.Windows.Browser import HtmlPage

html = '<strong>Set from IronPython</strong>'
HtmlPage.Document.experimental.innerHTML = html
e = HtmlPage.Document.GetElementByID('experimental')
e.SetStyleAttribute('border', 'solid black 2px')

Open File Dialog

from System.Windows.Controls import (
    OpenFileDialog, DialogResult
)

dialog = OpenFileDialog()
dialog.Title = "Load Python File"
dialog.Filter = "Python files (*.py)|*.py|All files (*.*)|*.*"
if dialog.ShowDialog() == DialogResult.OK:
    print dialog.SelectedFile.OpenText().ReadToEnd()

IsolatedFileStorage

from System.IO.IsolatedStorage import (
    IsolatedStorageFile, IsolatedStorageFileStream)
from System.IO import (
    FileMode, StreamReader, StreamWriter)

s = IsolatedStorageFile.GetUserStoreForApplication()
i = IsolatedStorageFileStream(name,
        FileMode.OpenOrCreate, s)

writer = StreamWriter(i)
writer.Write(data)
writer.Close();i.Close();s.Close()

Silverlight provides 1 megabyte of local storage ('in the browser') per application. You access it through the IsolatedStorageFile class.

Whilst I could create subdirectories in the file store, and save/load files from them, listing files in subdirectories didn't seem to work. Listing files in the root directory worked fine though.

Downloader

Accessing server resources (but no cross-domain calls).

from System import Uri
from System.Windows import Downloader

uri = Uri('http://www.voidspace.org.uk/')
dl = Downloader()

dl.Completed += someFunction
dl.Open('GET', uri)
dl.Send()

You can make synchronous calls with a class called the BrowserHttpWebRequest which has a more sophisticated API for doing POSTs and accessing headers etc.

Button

Subclassing the Control class (and consuming XAML).

class Button(Control):
    def __init__(self):
        root = self.InitializeFromXaml(xaml)
        self.button = root.FindName("button")

        self.button.MouseEnter += self.onMouseEnter
        ...
        self.Click = EventHook()

The XAML isn't shown here, but is in the Web IDE example.

Creating a Silverlight control. This inherits from the Control class provided in System.Windows.Controls. It is initialized from XAML. The events are then hooked up in the constructor. I've used my own Python EventHook class to create the Click event.

Resolver in the Browser

www.voidspace.org.uk/ironpython/resolver/resolver.html

Resolver the spreadsheet, development platform and number cruncher

To illustrate using Silverlight with Javascript libraries to create a web application I've created an example 'Resolver in the Browser'. It is based on an early prototype of Resolver that was used to evaluate grids for suitability.

This is a talk for Developers, not for Designers - so uhmm... it's probably not the best looking web application you've seen, but it all runs in the browser - with no server side activity.

This is a minimal spreadsheet engine in around 400 lines of Python code. The view layer is done in Javascript, using the EditArea Javascript syntax highlighting code editor and the ExtJS Javascript grid. The data model, spreadsheet engine and a controller layer are all written in IronPython using Silverlight.

The spreadsheet works by generating and executing Python code for the values and formulae in cells. You can also insert arbitrary Python code to be executed before the spreadsheet code (including setting cells). This is similar to the way that Resolver works.

Formulae can be any valid Python expression. There is no extra syntax, so we can't work with cell ranges like we would be able to in a full spreadsheet.

To understand what is going on we'll first need a quick lesson on spreadsheets. When we enter values in cells, corresponding Python code is generated and then executed to produce the results. This means that if we have a formula that references another cell (like =A1), then we must have already evaluated A1 before we evaluate this formula. We also need to know if there are any cyclic chains that can't be resolved sensibly (like cells that reference themselves). This is called dependency analysis, and determines the order that code for cells should be generated in.

To work out what the dependencies are (which cells are referenced in a formula) we need to parse the expression (the full Resolver has its own formula langauge, based on Python expressions, and its own parser). Fortunately the IronPython parser is exposed, so there is come code in this spreadsheet that uses the IronPython parser to parse the expression into an AST (Abstract Syntax Tree). I then subclass one of the IronPython classes called the PythonWalker, which walks the AST and pulls out all of the Name nodes that match a regular expression for cell names. We then order the cells based on the resulting dependency graph (a topological sort) and execute them in order - generating errors for any cycles we find. The algorithm that does all that is actually very simple, a few lines of Python code.

This spreadsheet does a full recalculation each time (in Resolver it is done on a background thread), this means that pre-formula code is executed every time - the spreadsheet is represented as a Python program. You can see in Excel that if you have functions in VBA, they aren't executed every time and results can get out of sync until you force a full recalculation.

So when we enter a value or a formula in a cell, we need to trigger a recalculation in IronPython. Editting one cell may change the displayed value in several cells (any cells that depend on that cell will change), so we need to push the updated values back from IronPython to the Javascript grid.

This means that we need to be able to call between Silveright and Javascript and vice versa.

One thing that is worth noting. The ExtJS grid wasn't designed as a spreadsheet grid. The grid needs to display the values, but when in edit mode be editing the underlying formula rather than editing the displayed value. The grid just stores the displayed values, the formulae (the values to edit) are stored in the data model and need to be fetched when we enter edit mode.

The details of all the hoops I needed to jump through to get this to work are on my blog.

The Scriptable Attribute

Where did he come from?

We use one technique to call into Silverlight from Javascript using one technique, and a slightly different one for calling from Silverlight into Javascript. Both of them are based on marking the class and or method that we are using with the .NET Scriptable attribute.

Unfortunately we can't set attributes from IronPython, so we have to use a bit of C#. Fortunately we can just create a stub class, and override a method in a subclass in an IronPython subclass. The C# is simple enough that, even if you have never seen C# before, you should be able to understand it.

The Scriptable attribute lives in the Ssytem.Browser.Net namespace. C# can be compiled using the Visual Studio 2008 Beta, which is currently available as a free beta. You will also need Visual Studio tools for Silverlight installed. XXX The Visual Studio Beta is a big download, and can be slow to startup. Fortunately you don't need this installed to compile assemblies for Silverlight - we'll see how in a few moments.

You can download an example Visual Studio 2008 project containing the example C#: ScriptableProject.zip.

Scriptable C# Class

using System;
using System.Windows.Browser;

[Scriptable]
public class ScriptableForString
{
    [Scriptable]
    public string method(string value)
    { return this._method(value); }

    public virtual string _method(string value)
    { return "override me"; }
}

C# for a Scriptable class with a Scriptable method that takes and returns a string look like:

We need a Scriptable class with a Scriptable method. This should call down to virtual method that we can override in an IronPython subclass. If we want to pass and return arguments they need to be strongly typed, and can only be a primitive like a string or an integer. This isn't really a problem though because we can pass or return JSON as a string. There is a JSON serializer and deserializer available in Silverlight, and using it from Javascript is easy of course!

Using from IronPython

To use this from IronPython we need to import the class from the assembly we have compiled. To add a reference from the assembly, we have to use the full name:

import clr
clr.AddReference("Scriptable, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
from Scriptable import ScriptableForString

class SomeClass(ScriptableForString):
    def _method(self, string):
        ...
        return new_string

some_class = SomeClass()

Resgitering the Scriptable Object

Registering the scriptable object with the Silverlight control:

from System.Windows import WebApplication

def OnLoad(sender, e):
    WebApplication.Current.RegisterScriptableObject(
        "someClass", someClass
    )

Calling from Javascript

One we have registered the scriptable object, it is then available on the control to be called from Javascript.

control = document.getElementById(
            'SilverlightPlugin'
          );
result = control.Content.someClass.method(
            value
         );

We need to get hold of the Silverlight control (using the name set in the CreateSilverlight function):

C# Scriptable Event

using System;
using System.Windows.Browser;

[Scriptable]
public class ScriptableEvent
{
    [Scriptable]
    public event EventHandler Event;

    public virtual void OnEvent(ScriptableEventArgs e)
    {
        Event(this, e);
    }
}

C# Scriptable EventArgs

[Scriptable]
public class ScriptableEventArgs : EventArgs
{
    private string _val;

    [Scriptable]
    public string val
    {
        get { return _val; }
        set { _val = value; }
    }
}

This is the other side of the coin. To call into Javascript from IronPython (to set values in cells from the Spreadsheet engine), we need to register a ScriptableEvent. Arguments are passed by setting scriptable attributes on a scriptable event args.

Using from IronPython

No need to subclass this time.

event = ScriptableEvent()

# This must also be registered
WebApplication.Current.RegisterScriptableObject(
    "event", event)

args = ScriptableEventArgs()
args.val = 'some string'
event.OnEvent(args)

The Javascript

In order to use this event, we have to assign a Javscript function to the event exposed on the control.

We can only assign one function to this event (no multicast). This function will receive two arguments (like normal .NET events), the sender and the event we pass in.

function some_function(sender, args) {
    value = args.val;
    ...
    args.val = new_value;
}

control = document.getElementById('SilverlightPlugin');
control.Content.event.Event = some_function;

When event.OnEvent(args) is called, the javascript function (some_function) is called and receives the arguments sender and our event args. The Javascript can modify the attributes on the event to return values. After the call returns, IronPython can look at the attributes on the event args to retrieve any return values.

Compiling C# with Notepad

set sl=C:\Program Files\Microsoft Silverlight
set csc=C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe

%csc% /out:SilverlightApp.dll /t:library
  /nostdlib+ /noconfig /r:"%sl%\system.dll"
  /r:"%sl%\agclr.dll" /r:"%sl%\mscorlib.dll"
  /r:"%sl%\System.Core.dll"
  /r:"%sl%\System.Silverlight.dll"
  /r:"%sl%\System.Xml.Core.dll"
  *.cs

pause

See blog entry by Michael Schwarz.

Actually you need two things installed, Silverlight and the .NET framework SDK. The SDK comes with a C# compiler (csc.exe), and we will create a batch file that tells csc to compile with references to the Sivlerlight DLLs instead of the standard framework ones.

This batch file assumes the standard locations for your Silverlight and framework installs. The line starting with %csc% needs the arguments that follow all on one line.

It tells csc not to use the standard .NET DLLs, but use the Silverlight ones instead. It compiles all the C# files in the directory (*.cs) into an assembly specified by the /out argument (/out:SilverlightApp.dll).

You can download an example that will compile the C# scriptable examples: CompilingCSharp.zip.

IronPython in Action

IronPython in Action

www.manning.com/foord