Silverlight 4 has now been out and about in beta for a couple of months and I still haven’t knuckled down and tried it out to any great extent. I have just been too busy at work with version 3. But I guess there is a time for everything, and now is the time for me to get started on version 4…
Silverlight introduces a heap of new and cool features. Everything from webcam support to more features when data binding and support for printing. This time however, I have decided to write about COM-interop.
If you had asked me about features that were likely to be in the next release when they released v3, I am pretty sure that COM-interop would never have been mentioned. I’m probably not the only one who didn’t think about that, but it opens up a host of new possibilities. But it comes with a big “but” (with one T…). It will for pretty obvious reasons, such as COM being a windows technology, not work on any other platform than Windows. And to quote Pearl - “Wow, that's a pretty big butt”. This is something we need to keep in mind when using this feature.
When deciding to use COM-interop we actually need to consider a few more things. This feature, as well as a few other, requires the application to run out-of-browser, or “OOB”. Why? Well, when running OOB, the Silverlight application is hosted by a custom application instead of the normal browser. This makes it possible to remove the sandbox that the browser normally places us in. This OOB restriction is, as I mentioned before, not limited to COM-interop. We will face the same requirement if we whish to for example use the keyboard in full screen mode.
COM-interop adds to the list of requirements by also requiring the application to be running with elevated permissions. Running with elevated permissions is a new features in SL4 and gives the Silverlight applications more power than they had before. Elevated permissions give us a couple of “power” features such as COM-interop and access to the “My *”-folders. But, as I said, it requires the user to give the application elevated permissions during installation…
Ok, so COM-interop requires the user to run OOB, with elevated permissions on Windows…that is sort of limiting. The OOB and elevated permissions thing is kind of ok I guess if you considering the amount of power it gives the application. Windows only however is not good. My general advice would be to use COM-interop only in applications where you know the user will run Windows, such as intranets, or as an extra feature for those of use who run windows but that can be turned off for others. Say for example that you are writing an HR application and the user needs to be able to send off e-mails from the application. With COM-interop we could do so using Outlook. This is a good “value-add” feature, but it is also a feature that could be handled differently if the user is running Mac. For example by giving the user textboxes with the information that should be in the e-mail and a hyperlink with a “mailto:” url. That way, the user could click the link and then copy paste the information. Not as cool, but still functional for those who don’t run Windows. Another alternative would be to use a “mailto:” url with the subject and recipient e-mail address and use Silverlight 4’s support for adding stuff to the clipboard. That way the user only has to press “Weird Mac key”+V…
Anyhow…with these restrictions in mind, let’s go ahead with a sample application. A sample application that uses…wait for it…wait for it…Outlook!…tadaaa… I know…everyone that demos COM-interop uses Outlook. Why? Well, because a lot of people have it, and it is easy to do… I guess I could have gone with any Office program, but I decided to stick with the good old Outlook…
So…let’s fire up VS 2010 and get started. And yes…you have to use VS 2010 as Silverlight 4 is not supported in VS 2008. And besides, this demo actually uses a new C# 4.0 feature, the new keyword “dynamic”. When creating the project, select a new Silverlight Application. Set the version to 4 and don’t use a web site to host the application. Using a dynamically created page works fine…
Next we need to configure it to allow OOB and to request elevated permissions. This is easy. Just open the project properties, tick the “Enable running out of the browser” checkbox and then click the “Out-of-Browser Settings…” button. On the screen that pops open, tick the “Require elevated permissions when running outside the browser�� checkbox. That’s it. This will generate a OutOfBrowserSettings.xml file for us in the Properties folder.
Next, we need to enable the new keyword “dynamic” in out code. We do this by adding a reference to an assembly called Microsoft.CSharp. This assembly comes with the Silverlight SDK and is available at “C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\Microsoft.CSharp.dll”. After having done this, we have to start programming.
In this application, I will be using 2 views. One view that is used when running in the browser and one outside it. The “in browser” view is responsible for telling the user that he or she has to install it or to use the installed version if it has already been installed previously, while the other view will talk to Outlook and get the users contacts.
The first view to tackle is the “in browser” view, or the InstallationView as I have called it. It is a simple view. It has a single StackPanel with a TextBlock and a Button. The visibility of the control is bound to the view model that is placed in the DataContext. The Xaml looks like this
<UserControl x:Class="OutlookIntegration.Client.Views.InstallationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:OutlookIntegration.Client.Converters"
xmlns:vm="clr-namespace:OutlookIntegration.Client.ViewModels"
Visibility="{Binding IsVisible}">
<UserControl.DataContext>
<vm:InstallationViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<converters:BoolConverter x:Key="BoolConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding InstallationMessage}" />
<Button Content="Install" Command="{Binding Path=InstallCommand}" Margin="0,10,0,0"
Visibility="{Binding CanInstall, Converter={StaticResource BoolConverter}}"/>
</StackPanel>
</Grid>
</UserControl>
As you can see, the TextBlock’s Text property as well as the Button’s visibility is also bound to the view model. I have also used the new Command property on the button to bind its “functionality” to the view model. The new Command property makes it possible to handle the “commanding” for at least a few controls without the need for the CommandManager.
The view model used for this view deals with the installation state of the application. There are three interesting properties that gives us information about this. All of them are available on the Application object. One is called IsRunningOutOfBrowser and reports about OOB state. Another is called InstallState and deals has information about whether or not the application runs OOB. And final one is called HasElevatedPermissions.
The InstallState will tell us if the current application has been installed. Not if the application is running OOB. It is actually an enumeration with a couple of different states, but the most important ones are Installed and NotInstalled. So even if it returns Installed, it can still be running in the browser.
So for this view model, we need to look at the IsRunningOutOfBrowser property to know if the InstallationView should be visible or not. It also needs to look at the InstallState to know if the application needs to be installed or already has been installed and just needs to be run out of browser. Finally, the view model needs an ICommand that the button can use to install the application. The actual installation of the application is automated and only requires calling a single method called Install(). The view model for the InstallationView therefore looks like this
public class InstallationViewModel : ViewModelBase
{
public InstallationViewModel()
{
Application.Current.InstallStateChanged += (s, e) =>
{
OnPropertyChanged("CanInstall");
OnPropertyChanged("InstallationMessage");
};
InstallCommand = new DelegateCommand(o =>
{
Application.Current.Install();
});
}
public Visibility IsVisible
{
get
{
return Application.Current.IsRunningOutOfBrowser ? Visibility.Collapsed : Visibility.Visible;
}
}
public string InstallationMessage
{
get
{
string message = "This application needs to be installed before running";
if (Application.Current.InstallState == InstallState.Installed)
{
message = "This application is already installed. Run your installed version...";
}
return message;
}
}
public bool CanInstall
{
get
{
return Application.Current.InstallState != InstallState.Installed;
}
}
public ICommand InstallCommand
{
get;
private set;
}
}
Oh yeah…as you can see the code also handles the InstallStateChanged event. This will be raised when the state changes. In this case I just raise the PropertyChanged event for the CanInstall and InstallationMessage, causing the view to rebind these and therefore hide the installation button and change the message. That’s all there is to it. That’s all that is needed to handle the logic for an application that needs to be installed…
The second view, which is used to show the Outlook contacts, is very simple as well. It contains a single ListBox that will display the contacts that are read from Outlook. It also binds its Visibility to the view model so that we can hide it if it is running in the browser…
<UserControl x:Class="OutlookIntegration.Client.Views.OutlookView"
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"
xmlns:vm="clr-namespace:OutlookIntegration.Client.ViewModels"
Visibility="{Binding IsVisible}">
<UserControl.DataContext>
<vm:OutlookViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ListBox ItemsSource="{Binding Contacts}" />
</Grid>
</UserControl>
The view model “behind” this view is also quite simple. It contains a bool property named IsVisible that will, as mentioned before, control the views visibility. It also contains an ObservableCollection<string> that will contain the name of the contacts.
When working with COM-interop, a class named ComAutomationFactory is responsible for managing the COM-objects. This object also contains a bool property called IsAvailable. This will tell us whether COM-interop is available or not…duh… So the IsVisible property will use this to decide if we should show the Outlook view or not.
public class OutlookViewModel : ViewModelBase
{
public OutlookViewModel()
{
Contacts = new ObservableCollection<string>();
LoadContacts();
}
private void LoadContacts()
{
...
}
public Visibility IsVisible
{
get
{
return ComAutomationFactory.IsAvailable ? Visibility.Visible : Visibility.Collapsed;
}
}
public ObservableCollection<string> Contacts
{
get;
private set;
}
}
As you can see, the view model is very simple. But as you might have noticed, I have left out the implementation of the LoadContacts() method. This method is, as you might have guessed, responsible for getting the contacts from Outlook.
When instantiating COM-objects in Silverlight, we can’t just add a reference like we do in regular .NET programs and we do not automatically get a strongly typed proxy object. Instead we have to use a static method called CreateObject() on an object called ComAutomationFactory. This method takes a progID in the form of string, not the GUID that is sometimes used in other situations. But what does it return? Well…it returns a “magic” object of type System.Windows.Interop.ComAutomationMetaObjectProvider. This is however not something we care about. Instead, we define the return object using a new C# 4.0 keyword named dynamic. This means that the variable we are working on is so called late bound. Late bound means that the compiler has no idea what the variable will contain, as the type is not set until runtime. This gives us a annoying lack of IntelliSense. We have to code “blind” and read documentation. Or we can cheat, which is what I did. Since my knowledge of the COM Outlook API is limited, I solved it by creating another WinForms project and use it to create the code needed. In a WinForms project, we can just add a reference to a COM-component and get a strongly types proxy and thus IntelliSense. After I verified that the code worked, I copied it to my Silverlight project and change all variable declarations to the keyword dynamic. This also made it possible for me to see the integer value of an enumeration value that is needed in the code.
Since the COM-interop code is fairly uninteresting in this case, I will just show it in a snippet and you can have a look at it if you are interested in that specific part of the demo…
private void LoadContacts()
{
if (ComAutomationFactory.IsAvailable)
{
dynamic outlookObj = ComAutomationFactory.CreateObject("Outlook.Application"); ;
dynamic fldContacts = outlookObj.GetNamespace("Mapi").GetDefaultFolder(10);
int itemCount = fldContacts.Items.Count;
for (int i = 1; i <= itemCount; i++)
{
dynamic contactItem = fldContacts.Items[i];
Contacts.Add(string.Format("{0} {1}", contactItem.FirstName, contactItem.LastName));
}
}
}
That’s it! Now you can try running the code. It should start up in the browser and tell you to install. And when installed, it should show you another message in the browser and open the installed version showing your Outlook contacts. I have one warning however. If you have a lot of contacts, the application might lock up for a while. The COM-interop is running on the UI-thread and will therefore lock up the UI when running. So a good recommendation would be to shuffle this off to a separate thread…but that is sort of out of scope for this post…
But what happens if it doesn’t just work? Well, if you press F5 in VS, it will start the application in debug mode. It will however start the application in the browser and thus never reach the COM-interop code, or any other code that only runs OOB. And if we modify the code to run the COM code in the browser it will obviously fail since it doesn’t run OOB and with elevated permissions. So how do we solve that? Well…if you look at the project properties under the Debug tab, you will find some debugging options. One of them is “Installed out-of-browser application”. This will start the application in OOB mode when debugging. But…there is a but…a big but…no not really…but…you do need to run the application in browser first at least once so that you can install it. The OOB debugging requires the application to be installed before it works. It will however use the latest version at all times. SO you don’t have to re-install every time that you make a change…
So…that was my intro to COM-interop in Silverlight. As you see, there are some caveats when doing it, and it does slow us down a bit by not supporting IntelliSense for the dynamic objects. But the powerful features it offers definitely makes up for it. Just remember to offer a fallback for people running non Windows operating systems (read Mac OS)…
Source code available here: OutlookIntegration.zip (19.46 kb)
Cheers!