Silverlight 4 Rough Notes: Trusted Applications

Note – these posts are put together after a short time with Silverlight 4 as a way of providing pointers to some of the new features that Silverlight 4 has to offer. I’m posting these from the PDC as Silverlight 4 is announced for the first time so please bear that in mind when working through these posts.

Silverlight 4 is the first version of Silverlight that has facilities whereby an application can go beyond the browser sandbox that Silverlight code has lived in since V1.

The full .NET Framework has always had a “get out of jail” card in that if there was fuctionality in the native operating system or in your own native DLLs that wasn’t present in the .NET Framework then you could always (subject to code access security restrictions) call out to that native code via one of the native code invocation services – namely P/Invoke and COM Interop.

This meant that, even from the first version of the .NET Framework, there wasn’t really any code that couldn’t be worked with from .NET because you could always wrapper it up and make it available.

Silverlight hasn’t been like that up until now – the code that you had available to you was what lived in the Silverlight .NET Framework along with any network services that you could call and the sandbox that this code ran in was the same regardless of whether the application was installed as an Out-Of-Browser application or not.

With Silverlight 4, this changes as there is now the notion of a “Trusted” Silverlight application which can go beyond the Sandbox in a number of ways including calling out to native code on the machine. So, a Silverlight application is always running;

  • In the browser – in this case it’s always Sandboxed.
  • Out of the browser and still Sandboxed – this is when the user has been through the consent UI to install the application onto their machine.
  • Out of the browser and Trusted (or elevated) – this is when the user has been through a modified consent UI.

Requesting that an application is elevated is something that’s added to an application’s OutOfBrowserSettings.xml file which Visual Studio generates for you when you switch on the “Out-Of-Browser” option and which ultimately contributes to the application’s manifest embedded into its xap.

Here’s an example of an OutOfBrowserSettings.xml file with the elevated setting switched on;

<OutOfBrowserSettings ShortName="SilverlightApplication3 Application" EnableGPUAcceleration="False" ShowInstallMenuItem="True">
  <OutOfBrowserSettings.Blurb>SilverlightApplication3 Application on your desktop; at home, at work or on the go.</OutOfBrowserSettings.Blurb>
  <OutOfBrowserSettings.WindowSettings>
    <WindowSettings Title="SilverlightApplication3 Application" />
  </OutOfBrowserSettings.WindowSettings>
  <OutOfBrowserSettings.Icons />
  <OutOfBrowserSettings.SecuritySettings>
    <SecuritySettings ElevatedPermissions="Required"/>      
  </OutOfBrowserSettings.SecuritySettings>
</OutOfBrowserSettings>

where ElevatedPermissions can be set to Required or NotRequired.

Whether the user can install an elevated application or not is limited by group policy but, assuming that policy permits, if they do install an application which is requiring elevated permissions then they’ll see a new consent dialog warning them that they’re effectively leaving the sandbox;

image

followed by;

image

Once the user has elected to take this path, the Silverlight application that they’re running is now elevated and that can be queried from the object model via the property Application.Current.HasElevatedPermissions which reports True/False depending upon whether the application is elevated or not.

So, what does it mean to be a elevated app? For me, it falls into a couple of different areas;

  1. The relaxing of the sandbox to allow things that it wouldn’t usually allow.
  2. The ability of Silverlight code to call out to code that it couldn’t previously call out to.

 

Relaxing the Sandbox

For an elevated application, the Silverlight runtime relaxes some of the restrictions that it would typically enforce.

Some (not necessarily definitive) examples;

  • An elevated application can make a network call ( HTTP ) to a machine that does not serve up a cross domain policy. Similarly, it can avoid cross-scheme restrictions that would apply to standard applications. Note that in the preview this does not apply to socket operations but only to HTTP interactions.

 

  • An elevated application can gather additional file information from the OpenFileDialog and SaveFileDialog dialog boxes and can raise those dialogs without explicitly needing a user-interaction (e.g. without a button press) which is different from their behaviour in Silverlight today.

 

  • An elevated application can go full-screen without requiring explicit user-interaction to drive that full-screen mode. So, an application could go full-screen as soon as it loads with something like;
 this.Loaded += (s, e) =>
        {
          if (Application.Current.HasElevatedPermissions)
          {
            Application.Current.Host.Content.IsFullScreen = true;
          }
        };

also, some of the restrictions on full-screen applications are gone. For example, you don’t see the standard;

image 

and, more so, keyboard handling is much more in line with a standard application rather than being restricted down to just a few set of keys which is what happens with a standard, sandboxed application.This means that you should have more success with having a Silverlight application full-screened on a separate monitor and it not exiting full-screen whenever you use the keyboard.

  • File access. Initially, I’d suspected that an elevated application would have access to anywhere on the disk. For example, I’d expect that it might be able to do something like;

 

      using (FileStream fs = new FileStream(
        "c:\\temp\\testfile.txt", FileMode.CreateNew))
      {
        fs.Close();
      }

 

but that’s not something that’s supported and also raises interesting questions as to how that might work on OS X and whether there’d be a need for file path abstractions to support that kind of thing. Instead, what is supported is a set of standard locations – MyDocuments, MyMusic, MyPictures, MyVideos and an elevated application can ( subject to standard ACLs ) work within those folders. As an example, an elevated application can execute code like;

 

      string myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
      Directory.CreateDirectory(myDocs + "\\myFolder");
      File.Create(myDocs + "\\myFolder" + "\\myfile.txt").Close();

 

and that works fine although I must admit that I haven’t tried it on OS X ( although I believe that it’ll work fine – I just don’t have Silverlight 4 yet on my Mac ). Note that another special folder such as Desktop doesn’t work.

Calling Out to Code

The major enhancement around being able to call additional code beyond the Silverlight .NET Framework is the ability to use COM interop from Silverlight applications.

This isn’t any and every variant of COM interop – it’s specifically about allowing late-bound ( i.e. untyped, IDispatch based ) calls from Silverlight to COM code using the automation marshaller rather than any custom marshalling ( it’s been a long time since I had to think about COM and marshalling :-S ). As far as I remember it, the automation marshaller generally is capable of marshalling the types that VB6 supported rather than any arbitrary type that you could marshal yourself.

This is really about being able to call out to existing COM code on the user’s machine and, as you might expect, this is a Windows-only feature and does not today have a related feature on the OS X side. There is no functionality in the framework that supports deploying COM code as part of a Silverlight application so the assumption is that the COM code gets to the user’s machine by other means ( e.g. they have installed Office ).

There’s a new class in the framework called ComAutomationFactory and it provides key methods for interacting with COM classes;

    • CreateObject
    • GetObject
    • GetEvent

and so if Silverlight wants to spring up an instance of Excel then it can go ahead and do something like;

 

      using (IDisposable disposable = ComAutomationFactory.CreateObject("Excel.Application"))
      {
        
      }

Now, the weird thing about this code is that CreateObject returns IDisposable. That is – the only thing that we know about the object returned from CreateObject is that is IDisposable and an object. So, how to program against it?

The way in which we can code against the instance returned from CreateObject is via the new dynamic keyword in C# 4.0 ( or via VB 10’s existing support for that kind of late-bound interaction ).

If you’ve been watching the .NET languages story you’ll know that in C# 4/VB 10 the support for dynamic invocation has been widened. Using my own picture – in C# 4 and VB 10 there’s a new, common way in which dynamic invocations can be done;

image

and so both languages become equal in terms of their ability to dynamically make invocations onto Plain Old CLR Objects ( via reflection ), COM objects ( via IDispatch ) and anything that happens to implement IDynamicObject and that some/all of this has been added into Silverlight 4 with the added benefit of making the new COM interop story part of a bigger picture that matches up with what’s already happening in the full .NET Framework.

So, we can go and perform our invocation by doing something like;

      using (dynamic excel = ComAutomationFactory.CreateObject("Excel.Application"))
      {
        excel.Visible = true;
      }

and, sure enough, Excel springs up onto the screen. It’s worth noting that my entry into the COM world is via a ProgId rather than anything else.

Once we’ve got Excel (e.g.) on the screen, we can do a whole bunch of interop work with it. For instance, if I want Excel to draw a graph for me then I might write a UI like;

<UserControl x:Class="SilverlightApplication10.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Image
            x:Name="imageExcel"
            Stretch="Fill"
            Margin="5" />
        <Button
            Click="OnGraphInExcel"
            Grid.Row="1"
            Content="Graph in Excel"
            HorizontalAlignment="Center"
            Margin="5" />
    </Grid>
</UserControl>

with a little code behind it in order to execute behind that button click;

  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();
    }
    private void OnGraphInExcel(object sender, RoutedEventArgs e)
    {
      dynamic excel = ComAutomationFactory.CreateObject("Excel.Application");

      excel.Workbooks.Add();

      for (int i = 1; i <= 10; i++)
      {
        excel.ActiveSheet.Cells[i, 1].Value2 = i;
        excel.ActiveSheet.Cells[i, 2].Value2 = i * i;
      }
      excel.ActiveSheet.ChartObjects().
        Add(100, 100, 300, 300).Chart.ChartWizard(
          excel.ActiveSheet.Range("A1", "B10"));

      string path = string.Format("{0}\\picture.jpg",
        Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));

      excel.ActiveSheet.ChartObjects(1).Chart.Export(path, "JPG");
      excel.ActiveWorkbook.Close(SaveChanges: false);

      excel.Quit();

      using (FileStream stream = File.OpenRead(path))
      {
        BitmapImage bitmapImage = new BitmapImage();
        bitmapImage.SetSource(stream);
        imageExcel.Source = bitmapImage;
        stream.Close();
      }
    }
  }

and so when I run that up I get a little UI;

image

and clicking the button launches Excel for me, draws a graph, saves it to the filesystem and then my code loads that back up into the Silverlight application;

image

Once you’re into this kind of cross-runtime world ( just like the existing HTML bridge in Silverlight 2 and 3 ) you have to think about type marshalling and conversions and no doubt there will be a table on MSDN as to how Silverlight’s .NET types will marshal across into COM’s automation types and it’ll be the sort of table that  you might study one day when you’ve hit some specific conversion that you don’t understand 🙂 but most things are likely to take the “natural” route.

Similarly, you get into what happens if you pass a CLR type across to a COM object and what happens if that COM object then passes it back ( this also surfaces in the HTML bridge ) and there is support for those kind of operations.

I was surprised to find that Silverlight code can handle events from COM code – so that means that I can go ahead and sync up Silverlight code to events originating in applications like Excel so (e.g.) if I wanted to keep track of how many sheets the user has opened inside an Excel workbook then I can author a bit of UI;

<UserControl x:Class="SilverlightApplication50.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition
                Height="Auto" />
            <RowDefinition
                Height="*" />
        </Grid.RowDefinitions>
        <Button
            Content="Open Excel"
            Margin="5"
            Click="OnOpenExcel" />
        <Viewbox
            Grid.Row="1">
            <StackPanel>
                <TextBlock HorizontalAlignment="Center">Worksheets opened...</TextBlock>
                <TextBlock
                    HorizontalAlignment="Center"
                    Text="{Binding NumberWorksheets}" />
            </StackPanel>
        </Viewbox>
    </Grid>
</UserControl>

with a bit of code behind it that spins up Excel, creates a new workbook and then syncs up to the NewSheet event on the ActiveWorkbook;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.ComponentModel;

namespace SilverlightApplication50
{
  public partial class MainPage : UserControl, INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          this.DataContext = this;
        };
    }
    public int NumberWorksheets
    {
      get
      {
        return (numberWorksheets);
      }
      set
      {
        numberWorksheets = value;

        if (PropertyChanged != null)
        {
          PropertyChanged(this, new PropertyChangedEventArgs("NumberWorksheets"));
        }
      }
    }
    void OnOpenExcel(object sender, RoutedEventArgs e)
    {
      if (ComAutomationFactory.IsAvailable)
      {
        excel = ComAutomationFactory.CreateObject("Excel.Application");
        excel.Visible = true;
        excel.Workbooks.Add();

        // Should be able to do...
        // excel.ActiveWorkbook.NewSheet += new ComAutomationEventHandler(OnSheetOpened);

        // but that's not working for me right now so this is the long form
        ComAutomationEvent evt =
          ComAutomationFactory.GetEvent(excel.ActiveWorkbook, "NewSheet");
        evt.EventRaised += OnSheetOpened;
        
        NumberWorksheets = 3;
      }
    }

    void OnSheetOpened(object sender, ComAutomationEventArgs e)
    {
      NumberWorksheets++;
    }    
    int numberWorksheets;
    dynamic excel;
  }
}

here’s that running;

image

So – all in all this is a big change from previous Silverlight versions and should help an awful lot for business applications that need to integrate more with the local machine whilst ( crucially ) not doing anything to damage the sandboxing story around a regular Silverlight application running inside or outside of the browser – i.e. this is additive to the existing system rather than changing the way that system worked.