Develop a Sandboxed Silverlight 3.0 Web part for SharePoint 2010

I recently had a chance to play around with the Silverlight Bing map control  v1 SDK and thought of integrating the map functionality with SharePoint  as a web part. In this post I am going to through the steps I’ve taken to accomplish this task. Basically we are going to develop a Silverlight 3.0 web part  as a sandbox solution for SharePoint 2010. Our web part will be interacting with SharePoint 20101 new Client object model to access data stored in a custom list to render the map with locations as pin points. The end functionality will looks as below. Out custom SharePoint list stores the locations with their Latitude and Longitudes.

image

You need to have SharePoint 2010 dev box, Visual Studio 2010 and Expression Blend 3, Silverlight Bing map control library installed for this task. I have outlined the steps required to setup a development environment for SharePoint 2010 in my previous blog post.

Let’s get started.

Step1: Create a new Silverlight 3.0 application in Visual Studio 2010

image

Step2: Choose to host your SilverLight application in a ASP.NET Web Application project.

image

Step3: Add the SharePoint 2010 reference DLL’s to interact with Client object model  into the Silverlight project that you have created. The two dll’s that you need to reference are located in the folder 14\TEMPLATE\LAYOUTS\ClientBin

image

Step 4: Download and install  Bing Maps Silverlight Control SDK. The interactive live SDK can be found here. You will also need a developer key to assess Bing maps which can be obtain from here.  Once the SDK is installed reference the Bing map dll’s (2) in your silverlight project. by default the those dll’s are present in C:\Program Files (x86)\Bing Maps Silverlight Control\V1

Once all necessary dll’s references are added our Silverlight project looks as below.

image

Step 5: Now its time to design the user interface with XAML for our web part. Import the xml namespace for Bing map control in the in MainPage.xaml file.

xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

once the above name space is imported all you need is  <m:Map/> to place the map control in your user interface. I’ve used expression Blend 3 to author XAML and output looks as below. Basically I got a simple Grid layout with TextBlock and Map control with the default map location points to central Australia with “Road” mode and  with zoom level 4.3. You can set these values as per your requirements. The CredentialsProvider key is what you get when you create a developer account for Bing map control.

<UserControl x:Class="SLFactoryMap.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"
    xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
    mc:Ignorable="d">
    <UserControl.Resources>
        <Style x:Key="HeaderStyle" TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="FontStyle" Value="Normal"/>
            <Setter Property="FontSize" Value="13.333"/>
            <Setter Property="Margin" Value="0,0,0,0"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="Foreground">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0"/>
                        <GradientStop Color="#FF725E5E" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="Margin" Value="5,0,0,0"/>
        </Style>
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="#FF64D3DC" Width="600" Height="490">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.04*"/>
            <RowDefinition Height="0.92*"/>
        </Grid.RowDefinitions>
        <TextBlock Height="20" Style="{StaticResource HeaderStyle}" Grid.Row="0" Grid.Column="0"  d:LayoutOverrides="Height" Grid.ColumnSpan="2"> Production Sites </TextBlock>
        <m:Map x:Name="factorymap" Grid.Row="1" Grid.Column="0" CredentialsProvider="{your key}" Mode="Road" ZoomLevel="4.3" Center="-25.7000,133.8833"/>
    </Grid>
</UserControl>

Above XAML code will render out UI in visual studio canvas designer as shown below.

image

Step 6: Now its time to create our code behind class MainPage.xaml.cs  in C#

First import the following names spaces

using SP = Microsoft.SharePoint.Client;
using Microsoft.Maps.MapControl;

then create the following class level members

SP.ClientContext _context;
SP.Web _currentWeb;
SP.List _locationList;
SP.ListItemCollection _locations;

Then we need to create the UI loaded routed event handler and obtain the SharePoint client context reference so that we can access  sharepoint client object model. Once we get a reference to the object model (web) which corresponds to the SPWeb in server context, we are obtaining a client reference to the “factoylocations” custom list which stores our locations. We are going to access all the list items in that list. Therefore we need a CAML query with no filters. To reduce server calls we batch up all the server requests and make one single call to the server to obtain list data. In the end we make a Asynchronous call to the server by calling _context.ExecuteQueryAsync . At this point SharePoint Client object model calls WCF Services perform authentication,  calls service methods and obtain the results for us which frees us from lot of plumbing work. This is a massive developer productivity feature comes with SharePoint 2010.

void MainPage_Loaded(object sender, RoutedEventArgs e)
{    // get the current client context
    _context = SP.ClientContext.Current;
    // get a reference to the current web
    _currentWeb = _context.Web;
    //get a reference to the list that contains locations
    _locationList = _currentWeb.Lists.GetByTitle("FactoryLocations");
    //we need to obtain all items in the location list.
    SP.CamlQuery query = new SP.CamlQuery();
    query.ViewXml = "<View></View>";
    _locations = _locationList.GetItems(query);
    //batch up the work to perform
    _context.Load(_locationList);
    _context.Load(_locations);
    _context.ExecuteQueryAsync(clientRequestSucceeded, clientRequestFailed);
}

Once the sever request is completed we need a call back method which needs to run in the UI thread. Our clientRequestSucceeded callback method looks as below. Here we iterate through the listitem collection and create Pushpin for each location listitem.

Dispatcher.BeginInvoke make sure that our anonymous method get executed in the client UI thread.

/// <summary>
/// Event handler for get request succeeded
/// </summary>
private void clientRequestSucceeded(object sender, SP.ClientRequestSucceededEventArgs e)
{
    //execute the code in the UI thread as a anonymous method
    Dispatcher.BeginInvoke(() =>
    {
      try
        {
          foreach (SP.ListItem loc in _locations)
           {
             Location location = new Location((Double)loc["Latitude"], (Double)loc["Longitude"]);
             // The pushpin to add to the map.
             Pushpin pin = new Pushpin();
             pin.Location = location;
             // Adds the pushpin to the map.
             factorymap.Children.Add(pin);
           }
       }
      catch (Exception ex)
       {
            MessageBox.Show(ex.ToString());
       }
   });
}

Failed request handler looks as below.

/// <summary>
/// Event handler for get request failed
/// </summary>
private void clientRequestFailed(object sender, SP.ClientRequestFailedEventArgs e)
 {   //execute the code in the UI thread as a anonymous method
   Dispatcher.BeginInvoke(() =>
   { MessageBox.Show(string.Format("Error occured in displaying factory locations:  {0}", e.Message)); }
   );
 }

Finally we need to make sure that loaded event is wired up to the main page constructor as shown below

public MainPage()
{
    InitializeComponent();
    Loaded += new RoutedEventHandler(MainPage_Loaded);
}

Step 7: At this point we can build the project and we should be able to see the SilverLight .xap file get generated. Next Add a empty SharePoint project to the solution. Point the URL to the site collection you are working with and Select Deploy as a SandBoxed solution option. SandBoxed SharePoint Solution Package files (WSP files) that are limited in what they can do and in the server resources they can use. What they can do is limited using Process Isolation and Code Access Security limited to the SharePoint Site. The resources they can use are limited by process monitoring, logging and log aggregation. Executing Code in the Sandbox Executing Code in the Sandbox The Sandboxed Solutions Service provides for a complete isolation system that ensures code running in a sandboxed solution cannot reach out to access information beyond the scope of the deployment. Specifically, sandboxed solutions will not be able to make updates to the SharePoint object model beyond the scope of the SPSite object. Farm level and web application level changes are allowed only for read operations. This gives best option for developers as well as farm administrators.

image

Step 8: Add a module a New Item to the SharePoint Project. In this case I call it FactoryMapModule. This module item is used to include the .xap file into the SharePoint solution. Therefore we need to tell the module, the location of our .xap file. Right click the module item –> go to properties > Select Output References. In the Dialog box Click Add button. Select your SilverLight Project Name. In this case it was SLFactoryMap. Also select the deployment type as ElementFile. Finally Click OK.

Now we have our .xap file generated by the SilverLight project included as a dependency for our module.

image

Now our solution structure should look as shown below.

image

Step 9: Update element.xml file of the module item. add a File section and include the XAP file path (source path) and Url (relative URL after deployment)

image

Step 10: At this point we are ready to build the project and deploy the SharePoint solution. Right Click the SharePoint project and Select “Deploy” option. This action will build our project, Create a .wsp Solution file and deploy the solution into SharePoint solution gallery in the specified site collection and activate the feature. Now we can go to the solution galley as the Site Collection administrator and view our solution in the solution store.

Go to Site Settings –> Galleries –> Solutions

image

image

Step 11: Create a custom list in the site with the name “FactoryLocations” with two columns named “Longitude” and “Latitude” and fill some sample data init. Our web part is going to consume this data when rendering the map.

Step 12: Add the Silverlight web part to a web part page. SharePoint 2010 provides a wrapper web part (under media and content category) to host Silverlight .xap files. The concept  is similar to SmartPart approach we have in WSS3/MOSS 2007.  Therefore we need to add this wrapper  web part first into the page that you want to place the Silverlight web part as shown below.

image

Step 13: Specify the relative path of the .xap file to the Silverlight web part. In this sample the .xap file is located in the root of the site collection.

Note: Also set the Chrome type to None in the Silverlight web part properties.(This is a workaround for a bug present in Beta 2 in rendering the title of Silverlight web part)

image

Now we should be able to visualize the map control in our Silverlight Webpart with locations specified in our list marked as Pinpoints. The sandbox worker process is where your actual code runs! This is in contrast to having the code run inside of w3wp.exe. This is why you don’t have to restart the application pool every time your redeploy an sandbox solution. If you wish to debug a sandbox solution, and for any reason why F5 debugging is not working, you will need to attach your visual studio debugger to the SPUCWorkerprocess.exe process.

As we have seen the power of Silverlight 3.0 along with SharePoint 2010 client object model enhance developer productivity and open up lot of opportunities to build rich interactive applications leverage on SharePoint 2010 platform.


One Comment on “Develop a Sandboxed Silverlight 3.0 Web part for SharePoint 2010”

  1. walleye1 says:

    I am trying to implement this solution but get a few errors, can I get some help?

    Error 1 Cannot convert lambda expression to type ‘System.Delegate’ because it is not a delegate type

    “//execute the code in the UI thread as a anonymous method
    Dispatcher.BeginInvoke(() =>”

    Error 2 The name BranchMap does not exist in the current context

    BranchMap.Children.Add(pin); // Change factorymap in xaml to BranchMap
    this error was inplace prior to changing the text

    Error 3 The name ‘InitializeComponent’ does not exist in the current context


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s