Tuesday, November 09, 2010

Silverlight 4.0 with SharePoint 2010 (2 of 2)

In the previous post we set the stage for how can we start building Silverlight applications for SharePoint 2010

You have three options to actually program against the SharePoint from Silverlight: First there’s the SharePoint web services that have been there for a long time, Second there’s the new REST APIs that is suitable for working with strongly typed lists and the final option is the Client Object Model.

The Client Object Model is a simple to use API that can be called from .NET CLR, JavaScript or Silverlight, we will stick with the Client Object Model cause this is the most suitable one to use from Silverlight, If you are familiar with developing for SharePoint on the Server Side (using SPWeb, SPList, etc.) then you will find that the COM (Client Object Model) mirrors a subset of the objects that are on the server.

For Silverlight applications you need to reference the COM APIs there are two DLLs that you need to reference in your Silverlight project Microsoft.SharePoint.Client.Silverlight.dll and Microsoft.SharePoint.Client.Silverlight.Runtime.dll, you can find these DLLs in the following path

“C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\ClientBin”

So let’s start creating the Silverlight application, we will create two projects a SharePoint project and a Silverlight project and configure the SharePoint project to to deploy the Silverlight xap file (refer to the first post for more details).

in the Silverlight project, add reference to the two COM assemblies

Adding reference to the Silverlight COM assemblies

we will build a simple Guest Book Silverlight Application, it will display a list of guests and allow the user to add new guests, here’s how the application looks like

a Simple Silverlight GuestBook Application

The code is very straightforward, we have a view model that represents the Guests, the view model contains a collection of Guests that is bound to a ListBox in the xaml file, the add button is bound to a Command on the view model that adds a new Guest with the information entered by the user, the view model class is shown below

  1. public class GuestsViewModel : INotifyPropertyChanged
  2.     {
  3.         public GuestsViewModel(){}
  4.         GuestsStore _guestStore = new GuestsStore();
  5.         public ObservableCollection<Guest> Guests
  6.         {
  7.             get
  8.             {
  9.                 var res = new ObservableCollection<Guest>();
  10.                 var lst = _guestStore.RetrieveGuests();
  11.                 foreach (var gst in lst)
  12.                 {
  13.                     res.Add(gst);
  14.                 }
  15.                 return res;
  16.             }
  17.         }
  18.  
  19.         Guest _newGuest = new Guest();
  20.         public Guest NewGuest
  21.         {
  22.             get { return _newGuest; }
  23.         }
  24.  
  25.         public event PropertyChangedEventHandler PropertyChanged;
  26.         protected void NotifyPropertyChanged(string propertyName)
  27.         {
  28.             if (PropertyChanged != null)
  29.             {
  30.                 PropertyChanged(this,
  31.                 new PropertyChangedEventArgs(propertyName));
  32.             }
  33.         }
  34.         bool AddNewGuest()
  35.         {
  36.             _guestStore.AddGuest(_newGuest);
  37.             _newGuest = new Guest();
  38.             NotifyPropertyChanged("Guests");
  39.             NotifyPropertyChanged("NewGuest");
  40.             return true;
  41.         }
  42.  
  43.         private AddGuestCommand _addGuestCommand = null;
  44.         public AddGuestCommand AddGuestCommand
  45.         {
  46.             get
  47.             {
  48.                 if (_addGuestCommand == null)
  49.                 {
  50.                     _addGuestCommand = new AddGuestCommand
  51.                     (
  52.                     p => AddNewGuest(),
  53.                     p => true
  54.                     );
  55.                 }
  56.                 return _addGuestCommand;
  57.             }
  58.         }
  59.     }
  60.     public class AddGuestCommand : ICommand
  61.     {
  62.         public AddGuestCommand(Action<object> executeAction,
  63.                         Predicate<object> canExecute)
  64.         {
  65.             if (executeAction == null)
  66.                 throw new ArgumentNullException("executeAction");
  67.             _executeAction = executeAction;
  68.             _canExecute = canExecute;
  69.  
  70.         }
  71.  
  72.         private readonly Predicate<object> _canExecute;
  73.         public bool CanExecute(object parameter)
  74.         {
  75.             if (_canExecute == null) return true;
  76.             return _canExecute(parameter);
  77.         }
  78.  
  79.         public event EventHandler CanExecuteChanged;
  80.         public void OnCanExecuteChanged()
  81.         {
  82.             if (CanExecuteChanged != null)
  83.                 CanExecuteChanged(this, EventArgs.Empty);
  84.         }
  85.  
  86.         private readonly Action<object> _executeAction;
  87.         public void Execute(object parameter)
  88.         {
  89.             _executeAction(parameter);
  90.         }
  91.  
  92.     }

In the code behind file MainPage.xaml.cs of the main page, the view model is set as the DataContext of the entire page

  1. public partial class MainPage : UserControl
  2.     {
  3.         public MainPage()
  4.         {
  5.             InitializeComponent();
  6.             this.DataContext = new GuestsViewModel();
  7.         }
  8.     }

The xaml is shown below

  1. <UserControl x:Class="SilverlightSP.MainPage"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.     mc:Ignorable="d"
  7.     d:DesignHeight="300" d:DesignWidth="400">
  8.     <UserControl.Resources>
  9.         <Style TargetType="TextBlock">
  10.             <Setter Property="Foreground" Value="White" />
  11.         </Style>
  12.         
  13.         <DataTemplate x:Key="personDataTemplate">
  14.             <Grid Margin="10">
  15.                 <Border Width="300" Padding="20" Grid.Row="0" Grid.ColumnSpan="2" Height="100"
  16.             Background="#FFED2D59" CornerRadius="20" >
  17.                     <Grid Margin="10">
  18.                         <Grid.RowDefinitions>
  19.                             <RowDefinition/>
  20.                             <RowDefinition/>
  21.                             <RowDefinition/>
  22.                         </Grid.RowDefinitions>
  23.                         <Grid.ColumnDefinitions>
  24.                             <ColumnDefinition Width="0.3*" />
  25.                             <ColumnDefinition Width="0.7*"/>
  26.                         </Grid.ColumnDefinitions>
  27.                         <TextBlock Grid.Row="0" Grid.Column="0">First Name:</TextBlock>
  28.                         <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding FirstName}" />
  29.                         <TextBlock Grid.Row="1" Grid.Column="0">Last Name:</TextBlock>
  30.                         <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LastName}" />
  31.                         <TextBlock Grid.Row="2" Grid.Column="0">Email:</TextBlock>
  32.                         <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Email}" />
  33.                     </Grid>
  34.                 </Border>
  35.             </Grid>
  36.         </DataTemplate>
  37.     </UserControl.Resources>
  38.     <Grid x:Name="LayoutRoot">
  39.         <Grid.RowDefinitions>
  40.             <RowDefinition Height="0.677*"/>
  41.             <RowDefinition Height="0.323*"/>
  42.         </Grid.RowDefinitions>
  43.         <Grid.Background>
  44.             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  45.                 <GradientStop Color="#FF5E48CC" Offset="0"/>
  46.                 <GradientStop Color="#FF281297" Offset="1"/>
  47.                 <GradientStop Color="#FF644FCE" Offset="0.493"/>
  48.             </LinearGradientBrush>
  49.         </Grid.Background>
  50.         <ListBox Width="400" ItemsSource="{Binding Guests}" Margin="10"
  51.                           ItemTemplate="{StaticResource personDataTemplate}" Background="{x:Null}" BorderBrush="{x:Null}"
  52.                           />
  53.         <Grid Grid.Row="1" Width="400" Height="100" >
  54.             <Grid.ColumnDefinitions>
  55.                 <ColumnDefinition Width="0.2*"/>
  56.                 <ColumnDefinition Width="0.3*"/>
  57.                 <ColumnDefinition Width="0.2*"/>
  58.                 <ColumnDefinition Width="0.3*"/>
  59.             </Grid.ColumnDefinitions>
  60.             <Grid.RowDefinitions>
  61.                 <RowDefinition Height="0.5*"/>
  62.                 <RowDefinition Height="0.5*"/>
  63.             </Grid.RowDefinitions>
  64.             <TextBlock Grid.Row="0" Grid.Column="0" Margin="10,0,0,0">First Name:</TextBlock>
  65.             <TextBox Grid.Row="0" Text="{Binding NewGuest.FirstName,Mode=TwoWay}" Grid.Column="1" x:Name="txtFirstName" Height="25" VerticalAlignment="Top" />
  66.             <TextBlock Grid.Row="0" Grid.Column="2" Margin="10,0,0,0">Last Name:</TextBlock>
  67.             <TextBox Grid.Row="0" Text="{Binding NewGuest.LastName,Mode=TwoWay}" Grid.Column="3" x:Name="txtLastName" Height="25" VerticalAlignment="Top" />
  68.             <TextBlock Grid.Row="1" Grid.Column="0" Margin="10,0,0,0">Email:</TextBlock>
  69.             <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding NewGuest.Email,Mode=TwoWay}" x:Name="txtEmail" Height="25" VerticalAlignment="Top" />
  70.             <Button Grid.Row="1" Grid.Column="2" x:Name="btnAdd"
  71.                     Content="Add" VerticalAlignment="Top" Margin="10,0,0,0"
  72.                     Command="{Binding AddGuestCommand}"/>
  73.         </Grid>
  74.     </Grid>
  75. </UserControl>

In the view model you can see that we are using a repository for the Guests implemented using the GuestStore class, this class simply stores the guests in an internal list

  1. public class Guest
  2.     {
  3.         public string FirstName { get; set; }
  4.         public string LastName { get; set; }
  5.         public string Email { get; set; }
  6.     }
  7.     public class GuestsStore
  8.     {
  9.         private List<Guest> _guests = new List<Guest>();
  10.  
  11.         public GuestsStore()
  12.         {
  13.             _guests= new List<Guest>() {
  14.             new Guest(){ FirstName="Mohamed", LastName="Mosallem",Email="mmosalem@devlifestyle.net"},
  15.             new Guest(){ FirstName="Ahmed", LastName="Ismail",Email="aismail@yahoo.com"},
  16.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  17.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  18.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  19.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  20.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  21.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  22.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"},
  23.             new Guest(){ FirstName="Ibrahim", LastName="Mahmoud",Email="imahmoud@hotmail.com"}
  24.             };
  25.         }
  26.  
  27.         public List<Guest> RetrieveGuests()
  28.         {
  29.             return _guests;
  30.         }
  31.         public bool AddGuest(Guest g)
  32.         {
  33.             _guests.Add(g);
  34.             return true;
  35.         }
  36.     }

Till now this is a normal Silverlight application no SharePoint yet, what we will do is that, we will create a custom SharePoint list to store the Guests in it, and we will use our Silverlight application to populate this list, so let’s create the custom list in SharePoint.

From the Site Actions menu choose Create, in the Create Dialog select Custom List, name it GuestBook

Create Custom List

On the ribbon go to the List menu, click the Create Column button Create Column

Fill the necessary information and repeat this step for the three columns “First Name” , “Last Name” and “Email”

Create Column Dialog

The next step is to change our Silverlight application to read/write to the GuestBook list. Most of the changes will be in the GuestsStore class, we will remove the internal list we used before.

The central object to start with while programming against the SharePoint COM is the ClientContext which represents the SharePoint Context in which the client (in our case the Silverlight application) is running, one important concept in SharePoint COM is that before you access a property or object you have to ask for it, let’s walkthrough the RetrieveGuests method

  1. public void RetrieveGuests()
  2.       {
  3.          var clientContext = ClientContext.Current;
  4.          var guestBookList = clientContext.Web.Lists.GetByTitle("GuestBook");

First we grab a reference to the current ClientContext, then we define an object that will reference our list (you can see that the object model is very similar to the SharePoint server API).

after that we ask the client context to load the list items

  1. clientContext.Load(_guestsListItems,
  2.                        items => items.Include(
  3.                                item => item[FirstNameColumn],
  4.                                item => item[LastNameColumn],
  5.                                item => item[EmailColumn]
  6.                             )
  7.                            );

As you can see we explicitly asked the clientContext to Include the columns we are interested in.

The clientContext.Load doesn’t actually call to the server and retrieve the list item instead it just builds up the request that will be sent to the server, so basically we can batch multiple operations together to be executed as batch on the server, to actually send the request to the server we call the clientContext.ExecuteQuery method

  1. clientContext.ExecuteQueryAsync(
  2.                 new ClientRequestSucceededEventHandler(GuestsRetrievedSucceeded),
  3.                 null);

We used the async version of the method, this method accepts a callback function to be called when ExceuteQuery succeeds

  1. void GuestsRetrievedSucceeded(object sender, ClientRequestSucceededEventArgs args)
  2.         {
  3.             if (GuestsRetrieved != null)
  4.             {
  5.                 List<Guest> res = new List<Guest>();
  6.                 foreach (var item in _guestsListItems)
  7.                 {
  8.                     res.Add(new Guest()
  9.                     {
  10.                         FirstName = item[FirstNameColumn].ToString(),
  11.                         LastName = item[LastNameColumn].ToString(),
  12.                         Email = item[EmailColumn].ToString()
  13.                     });
  14.                 }
  15.                 GuestsRetrieved(res);
  16.             }
  17.         }

In this event handler we populate a list of Guests from the SharePoint list items and then fire a custom event GuestRetrieved, the view model subscribes to this event.

The AddGuest method is very simple, we define a new list item and set its field values, then submit this to the server, and fire an event if this succeeds

  1. public void AddGuest(Guest g)
  2.         {
  3.             var clientContext = ClientContext.Current;
  4.             _newGuest = g;
  5.             var guestBookList = clientContext.Web.Lists.GetByTitle("GuestBook");
  6.          
  7.             ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
  8.             ListItem newGuestListItem = guestBookList.AddItem(itemCreateInfo);
  9.             newGuestListItem[FirstNameColumn] = g.FirstName;
  10.             newGuestListItem[LastNameColumn] = g.LastName;
  11.             newGuestListItem[EmailColumn] = g.Email;
  12.             newGuestListItem["Title"] = g.FirstName + " " + g.LastName;
  13.             newGuestListItem.Update();
  14.  
  15.             clientContext.ExecuteQueryAsync(new ClientRequestSucceededEventHandler(GuestAddedSucceeded), null);
  16.         }
  17.         
  18.         void GuestAddedSucceeded(object sender, ClientRequestSucceededEventArgs args)
  19.         {
  20.             if (GuestAdded != null)
  21.             {
  22.                 GuestAdded(_newGuest);
  23.             }
  24.         }

Here’s the application running inside SharePoint

The Silverlight application running inside SharePoint

This is just a simple basic Silverlight application, let’s add a simple enhancement that can make our Silverlight application shine within SharePoint, to do this we will use Expression Blend, right click on the MainPage.xaml file and select Open in Expression Blend

Open in Expression Blend

Right click on the ListBox and select Edit Additional Templates-> Edit Generated Item Container-> Edit a Copy

Edit Generated Item Container

Click Ok in the Create Style Dialog

On the states tab set the transition duration for the LayoutStates group to 1 second

Set the transition duration

While the BeforeLoaded state is selected, select the grid element from the Objects and Timeline tool window, go to the properties window and change the Opacity property to 0% and change the Translate transform Y property to 100

 Changing the Opacity and translating the ListBox

The last thing is to click on the small icon to Turn on FluidLayout

Turn on FluidLayout

Now build and run the application, you notice a very nice effect when adding elements to the list.

That’s the end of this post, hope this was useful.

You can download the source code from here

P.S: you will notice that the column names in the code don’t match the names we specified in SharePoint, please refer to this blog post for more details

1 comment: