Sunday, May 16, 2010

Distributing Silverlight application written in IronPython

When you have Silverlight application written in IronPython, it is a good idea to split it to several files so browser can cache them separately. Later, when you change something in your application, users will download only a small part. During my attemts with IronPython and Silverligt, I have found several catches. That's why I describe here my way how to distribute IronPython Silverlight application.

I distribute my application as one .html file, one .xap file, and several .zip files. I use .zip because IIS already knows what to do with .zip files. The files are:

  1. index.html
  2. app.xap
  3. IronPython.zip - contains files from IronPython-2.6.1\Silverlight\bin:
    IronPython.dll
    IronPython.Modules.dll
    
  4. Microsoft.Scripting.zip - contains files from IronPython-2.6.1\Silverlight\bin:
    Microsoft.Dynamic.dll
    Microsoft.Scripting.dll
    Microsoft.Scripting.Core.dll
    Microsoft.Scripting.ExtensionAttribute.dll
    Microsoft.Scripting.Silverlight.dll
    
  5. SLToolkit.zip - contains files form Silverlight toolkit or SDK; in our case just
  6. System.Windows.Controls.dll
    

Let's create a small application, that uses ChildWindow control from Silverlight toolkit:

C:\IronPython-2.6.1\Silverlight\script\sl.bat python childwindow
Change the app.py and app.xml:

app.py

from System.Windows import Application
from System.Windows.Controls import UserControl

class App:
    def __init__(self):
        self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")

a = App()

app.xaml

<UserControl x:Class="System.Windows.Controls.UserControl"
  xmlns="http://schemas.microsoft.com/client/2007"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
  <controls:ChildWindow >
    <StackPanel>
      <TextBlock Text="Text in ChildWindow"/>
      <Button x:Name="btnNewWindow" Content="New window"/>
    </StackPanel>
  </controls:ChildWindow>
</UserControl>

We don't want to Chiron automatically add necesary .dll files into .xap so we have to add our own AppManifest.xaml and languages.config into childwindow\app folder:

AppManifest.xaml

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  RuntimeVersion="2.0.31005.0"
  EntryPointAssembly="Microsoft.Scripting.Silverlight"
  EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication"
  ExternalCallersFromCrossDomain="ScriptableOnly">
  <Deployment.Parts>
  </Deployment.Parts>
  <Deployment.ExternalParts>
    <ExtensionPart Source="Microsoft.Scripting.zip" />
    <ExtensionPart Source="SLToolkit.zip" />
  </Deployment.ExternalParts>
</Deployment>

languages.config

<Languages>
  <Language names="IronPython,Python,py"
    languageContext="IronPython.Runtime.PythonContext"
    extensions=".py"
    assemblies="IronPython.dll;IronPython.Modules.dll"
    external="IronPython.zip"/>
</Languages>

Now create all three .zip files and add them into childwindow folder.

To test the application with Chiron, run

C:\IronPython-2.6.1\Silverlight\bin\Chiron.exe /e: /d:childwindow

The /e: switch is important - it tells Chiron to not put any assembly into generated .xap file. Check the application on http://localhost:2060/index.html.

To generate .xap file for distribution, run:

C:\IronPython-2.6.1\Silverlight\bin\Chiron.exe /e: /d:childwindow\app /z:app.zap

If you want to use anything from external assemblies in the code, you have to add manually reference to those assemblies. For example, if you want to add a button that creates a new ChildWindow, you have to change you code like this:

app.py

from System.Windows import Application
from System.Windows.Controls import UserControl

class App:
    def __init__(self):
        self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")
        self.root.btnNewWindow.Click += self.OnClick

    def OnClick(self, sender, event):
        import clr
        clr.AddReference('System.Windows.Controls')
        from System.Windows.Controls import ChildWindow
        self.root.panel.Children.Add(ChildWindow(Content='new window'))

a = App()

app.xaml

<UserControl x:Class="System.Windows.Controls.UserControl"
  xmlns="http://schemas.microsoft.com/client/2007"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
  <controls:ChildWindow >
    <StackPanel x:Name="panel">
      <TextBlock Text="Text in ChildWindow"/>
      <Button x:Name="btnNewWindow" Content="New window"/>
    </StackPanel>
  </controls:ChildWindow>
</UserControl>

If you comment out the clr.AddReference line, ImportError appears. See the explanation in Jimmy's email.

You can download the example here but note the .zip files do not contain and .dlls.