Life, the Universe, and Software EngineeringLife, the Universe, and Software Engineering

Practical Silverlight and SharePoint Integration: Part Three

Main Image

In Part Two of this series we expanded on the out-of-the-box connectivity to the SharePoint Lists web service and added the ability to dynamically connect to any SharePoint server URL.  In this article we will make a more complex call to retrieve list detail and show how data retrieved from the SharePoint lists web service can be bound to Silverlight controls.

The first step we are going to need to do is enhance our existing sample code base and update the user interface to give us a little more flexability to select a list and get some details.   So lets start with our previous code base which can be found in Part Two or you can get it directly here: PraticalSiverlightAndSharePointIntegration.Part2.zip.

First lets split our single output text box into two sections, on the left a list of list, and on the right a original output text box for additional detail about the list.

Here’s what the new user interface looks like:

Page.xaml

 

The XAML for the above page should now look like:

<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"  x:Class="SharePointListExample.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:SharePointListExample"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition MaxHeight="25"/>
        </Grid.RowDefinitions>
        <Grid Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <ScrollViewer Grid.Column="0">
                <ListBox x:Name="lbxListOfLists"></ListBox>
            </ScrollViewer><basics:GridSplitter Grid.Column="1"></basics:GridSplitter>
            <ScrollViewer Grid.Column="2">
                <TextBox x:Name="txtOutput" IsReadOnly="True"></TextBox>
            </ScrollViewer>
        </Grid>
            <StackPanel Grid.Row="1" Orientation="Horizontal" Background="LightGray">
            <TextBlock Margin="5,0,5,0" VerticalAlignment="Center">Sharepoint Url:</TextBlock>
            <TextBox MinWidth="100" x:Name="txtSharePointUrl" Text="http://mysharepointserver.com/sites/mysite"></TextBox>
            <Button x:Name="btnGo" Margin="5,0,0,0" Content="Go" Click="btnGo_Click"/>
        </StackPanel>
    </Grid>
</UserControl>

Now lets update the list data returned from SharePoint so that our “list of lists” will be populated.

The old code to catch and display our results in just the text box looked like this:

         /// <summary>
        /// Handle the completed web service request to retrieve the list collection
        /// </summary>
        /// <param name="sender">The soap client</param>
        /// <param name="e">The completed event args</param>
        void listSoapClient_GetListCollectionCompleted(object sender, GetListCollectionCompletedEventArgs e)
        {
            try
            {
                txtOutput.Text = e.Result.ToString();
            }
            catch (Exception exception)
            {
               handleException("Failed to get list collection", exception);
            }
        }

Since we want to let Silverlight to most of the work in displaying our “list of lists” all we need to do here is to bind the ItemsSource property of the Silverlight Listbox to the collection of list elements returned by SharePoint.  In this case we are only interested in the individual List elements so we used the Linq Descendants function to get the collection of List elements as follows:

        public const string SHAREPOINT_SOAP_NAMESPACE = "http://schemas.microsoft.com/sharepoint/soap/";
        /// <summary>
        /// Handle the completed web service request to retrieve the list collection
        /// </summary>
        /// <param name="sender">The soap client</param>
        /// <param name="e">The completed event args</param>
        void listSoapClient_GetListCollectionCompleted(object sender, GetListCollectionCompletedEventArgs e)
        {
            try
            {
                txtOutput.Text = e.Result.ToString();
                lbxListOfLists.ItemsSource = e.Result.Descendants(XName.Get("List", SHAREPOINT_SOAP_NAMESPACE));
            }
            catch (Exception exception)
            {
               handleException("Failed to get list collection", exception);
            }
        }



Now compiling and running the application we should see something like the following when “Go” is pressed:

FirstRun

Ok so the output still isn’t very appealling… We will need to add a little more xaml code to our page to take advantage of Silverlight’s data templating.  In addition to xaml code, the out-of-the-box data binding doesn’t understand how to read directly from XML nodes,  so to start with we will use an IValueConverter class that will assist the Silverlight binding in accessing data directly from the XElement classes that are returned by the call to Descendants.  Below is the code for this new helper class:

public class XAttributeConverter : IValueConverter 
    {
        [Flags]
        public enum ConvertionOptions
        {
           None = 0x00,
           Uri = 0x01,
           Html = 0x02
        }
        public const string PROPERTY_BASEURI = "baseuri";
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
        {
            if (value is XElement &amp;&amp; parameter is string)
            {
                XElement element = value as XElement;
                string parameters = parameter as string;
                if (parameters != null)
                {
                    string[] parameterItems = parameters.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    if (parameterItems.Length &gt; 0)
                    {
                        Dictionary<string, string> properties = new Dictionary<string, string>();
                        ConvertionOptions options = ConvertionOptions.None;
                        string attributeName = null;
                        int i = 0;
                        foreach (string item in parameterItems)
                        {
                            string trimmedItem = item.Trim();
                            if (i == 0)
                            {
                                attributeName = trimmedItem;
                            }
                            else if ( item.Contains("="))
                            {
                                string[] itemElements = item.Split('=');
                                properties[itemElements[0].Trim().ToLower()] = itemElements[1].Trim();
                            }
                            else
                            {
                                try
                                {
                                    ConvertionOptions option = (ConvertionOptions)Enum.Parse(typeof(ConvertionOptions), item, true );
                                    options |= option;
                                }
                                catch
                                {
                                    Debug.WriteLine( "Unrecongnized XAttributeConverter parameter \"{0}\"", item);
                                }
                            }
                            i++;
                        }
                        XAttribute attribute = element.Attribute(attributeName);
                        if (attribute == null)
                        {
                            Debug.WriteLine("Attribute \"{0}\" not found in XElement \"{1}\".", attributeName, element.Name.ToString());
                            return null;
                        }
                        else
                        {
                            string parameterValue = attribute.Value;
                            if ( (options &amp; ConvertionOptions.Uri ) == ConvertionOptions.Uri )
                            {
                                Uri baseUri = new Uri(properties[PROPERTY_BASEURI]);
                                return new Uri(baseUri, parameterValue);
                            }
                            if ((options &amp; ConvertionOptions.Html) == ConvertionOptions.Html)
                            {
                                return parameterValue.Replace("&lt;","&lt;").Replace("&gt;","&gt;");
                            }
                            if (targetType != typeof(string) &amp;&amp; typeof(IConvertible).IsAssignableFrom(targetType))
                            {
                                return System.Convert.ChangeType(parameterValue, targetType, null);
                            }
                            else
                            {
                                return parameterValue;
                            }
                        }
                    }
                }
            }
            Debug.WriteLine("Invalid parameters from XAttributeConverter");
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

We will need to modify the xaml code to use the XAttributeConverter as follows:

<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"  x:Class="SharePointListExample.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:SharePointListExample"
    Width="400" Height="300">
    <UserControl.Resources>
        <local:XAttributeConverter x:Key="FromXAttribute"/>
        <DataTemplate x:Key="ListDataTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=Title}"/>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition MaxHeight="25"/>
        </Grid.RowDefinitions>
        <Grid Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <ScrollViewer Grid.Column="0">
                <ListBox x:Name="lbxListOfLists" ItemTemplate="{StaticResource ListDataTemplate}" SelectionChanged="lbxListOfLists_SelectionChanged" ></ListBox>
            </ScrollViewer><basics:GridSplitter Grid.Column="1"></basics:GridSplitter>
            <ScrollViewer Grid.Column="2">
                <TextBox x:Name="txtOutput" IsReadOnly="True"></TextBox>
            </ScrollViewer>
        </Grid>
            <StackPanel Grid.Row="1" Orientation="Horizontal" Background="LightGray">
            <TextBlock Margin="5,0,5,0" VerticalAlignment="Center">Sharepoint Url:</TextBlock>
            <TextBox MinWidth="100" x:Name="txtSharePointUrl" Text="http://mysharepointserver.com/sites/mysite"></TextBox>
            <Button x:Name="btnGo" Margin="5,0,0,0" Content="Go" Click="btnGo_Click"/>
        </StackPanel>
    </Grid>
</UserControl>

Now compiling and running the application, should produce results simliar to this:

SecondRun

So far so good.  Now lets get into make our “list of lists” clickable so that we can see more detail in our output text box when a list item is selected.  First we modify the xaml code to include a SelectionChanged event handler then we add the following code to handle the event in the code behind:

       private void lbxListOfLists_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                if (lbxListOfLists.SelectedItem is XElement)
                {
                    XElement listItem = lbxListOfLists.SelectedItem as XElement;
                    string listName = listItem.Attribute("Title").Value;
                    ListsSoapClient listsSoapClient = createListsSoapClient();
                    listsSoapClient.GetListCompleted += new EventHandler<getlistcompletedeventargs>(listsSoapClient_GetListCompleted);
                    listsSoapClient.GetListAsync(listName);
                }


            }
            catch (Exception exception)
            {
                handleException("getList Failed", exception);
            }
        }

        void listsSoapClient_GetListCompleted(object sender, GetListCompletedEventArgs e)
        {
            try
            {
                txtOutput.Text = e.Result.ToString();
            }
            catch (Exception exception)
            {
                handleException("listsSoapClient_GetListCompleted Failed", exception);
            }
        }

Note in the code above:  Now that we are requiring a bound ListsSoapClient in more than one place in our application, it makes sense to place the code for doing this in it’s own method called CreateListsSoapClient.

Running our application now will give us the same results when we press “Go”, but when we select a list item, the right handle textbox panel should now show the results of a call to GetListAsync.  GetListAsync returns details about each list selected.

Selecting a list item should result in something like the following image:

ThirdRun

 The complete source code for this article can be found here: PracticalSilverlightAndSharePointIntegration.Part3.zip (1.39 mb)

Stay tuned, part four in this series will exand on this code to allow us to retrieve and display list items.

Part One| Part Two| Part Three | Part Four

(c) Copyright 2009 – Aaron G. Daisley-Harrison – All Rights Reserved.

Authors

All Categories

Archive