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

Practical Silverlight and SharePoint Integration: Part Four

Main Image

In Part Three of this series we continued to expand our sample Silverlight/SharePoint list application by adding an additional call to retrieve list detail.  In this article we will use the results of our call for list detail to determine which columns should be displayed when we retrieve SharePoint list items.  To simplify list item data retrieve we will also explorer a SharePointList helper class that is useful for building CAML queries and encapsulating list items results.

So far the calls we have made from our Silverlight application to the SharePoint lists web services have require only simple parameters.  Our first call to GetListCollectionAsync required no parameters. Our call to GetListAsync required only the name of the list.  Now we will up the anti and make calls to GetListItemsAsync.

GetListItemsAsync requires the caller to specify the name of the list, the view name, the fields to return, a CAML query, and other options.

The method looks like this:

ListsSoapClient.GetListitemsAsync( string listName, string viewName, XElement query, XElement viewFields, string rowLimit, XElement queryOptions, string webID, object userState )

 

ParameterDescription
string listName The name of the list from which to retrieve list items.  This can be either the list title or a GUID surrounded by curly braces e.g. “UserInfo” or “{D5098B8F-1C04-4dc7-AB86-65AD591A008B}”.  Though the MSDN documents recommend using a GUID here instead of the actual list name, you should note that SharePoint list GUIDs are unique for each site implementation.  If you are writing Silverlight components to be used on multiple SharePoint sites,  the use of list names is a little more portable.  If you insist on using a list GUID, you should retrieve the list GUID by first calling the GetList method.
string viewName The GUID, surrounded by curly braces, of the view used to retrieve list items.  Setting the viewName to string.empty or “” causes the default view for the specified list to be used.
XElement query

A SharePoint CAML query.  Microsoft’s reference for the CAML query language can be found here: CAML Overview.  The CAML query used here starts with the <Query> element.  For example:
<Query>
    <OrderBy>
        <FieldRef Name=”Name”/>
    </OrderBy>
    <Where>
         <Or>
             <Eq>
                  <FieldRef Name=”Status”/>
                  <Value DataType=”Text”>Completed</Value>
             <Eq>
             <IsNull>
                 <FieldRef Name=”Status”/>
             </IsNull>
         <Or>
    </Where>
</Query>

XElement viewFields

This fields to be returned for each list item found.  If null, all fields of the view will be returned.
This xml fragment is of the form:
<ViewFields>
    <FieldRef Name=”field1-name”/>
    <FieldRef Name=”field2-name”/>
    …
    <FieldRef Name=”fieldN-name”/>
</ViewFields>

string rowLimit A string representation of the maximum number of list items to be returned, or an empty string, if unspecified.
XElement queryOptions

A null if not specified, or an xml fragment containing a list of parameter setting based on the parameter of the SharePoint SPQuery object.  SPQuery parameter documentation can be found here: SPQuery.
For example:
<QueryOptions>
    <ExpandUserField>True</ExpandUserField>
</QueryOptions>

string webID The GUID it of the SharePoint parent web site that contains the list, or a null if the site specified by the url of the web service should be used.
object userState Any object that you wish to be passed to the asynchronous event handler that wil be called when this async method completes.  userState be null.

 The Microsoft MSDN documentation for this method can be found here: Lists.GetListitems method

 Now that we know what method to use to enumerate list items from a SharePoint list, the next step is to modify the user interface of the application from Part Three  of this series to allow us to select a list then view the list items.  You will note that I have also added some additional radio buttons and check boxes toggle between list detail and list items or show the data as raw xml or in list format.  I’ve added the “All” check box to toggle between passing a null value for the viewFields parameter (returns all fields) or a list of standard fields.

PageA

 The XAML code to display this page is a 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="525" 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>
        <DataTemplate x:Key="ListItemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Title}"/>
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_ID}"/>
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Last_x0020_Modified}"/>
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Editor}"/>
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Created}"/>
                <TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Author}"/>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition MaxHeight="30"/>
        </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>
            <Grid Grid.Column="2">
                <Grid.RowDefinitions>
                    <RowDefinition Height="32"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5">
                    <RadioButton x:Name="rbListDetail" Content="List Detail" IsChecked="True" Checked="rbListDetail_Checked"/>
                    <RadioButton x:Name="rbListItems" Content="List Items"  Margin="5,0,0,0" Checked="rbListItems_Checked"/>
                    <CheckBox x:Name="cbAll" Content="All" Margin="5,0,0,0" IsChecked="False" Click="cbAll_Click"/>
                    <CheckBox x:Name="cbRaw" Content="Raw" Margin="5,0,0,0" IsChecked="True" Click="cbRaw_Click"/>
                </StackPanel>
                <ScrollViewer x:Name="viewOutput" Grid.Row="1" HorizontalScrollBarVisibility="Visible">
                    <TextBox x:Name="txtOutput" IsReadOnly="True"></TextBox>
                </ScrollViewer>
                <ScrollViewer  x:Name="viewListItems" Grid.Row="1" HorizontalScrollBarVisibility="Visible"  Visibility="Collapsed">
                    <ListBox x:Name="lbxListItems" ItemTemplate="{StaticResource ListItemTemplate}" ></ListBox>
                </ScrollViewer>
            </Grid>
        </Grid>
            <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5,5,5,5">
            <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>

 Next we need to modify the list selection changed event handler, from Part Three, to now call GetListItemsAsync when a list is selected. The original code looked like this:

        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);
            }
        }

}

In order to support the new radio buttons and check boxes I extracted the call to the listsSoapClient and created a new updateListDetailPanel() function to make the call based on the option selected by the user. The orginally call to listsSoapClient is now in it's own method, called getListDetail. The code to make the new call to GetListItemsAsync is in the getListItems method. Note that when the "All" check box is checked a null is passed to the viewFields parameter which causes all fields specified in the view to be returned. When the "All" check box is unchecked a only a select subset of the standard SharePoint built-in fields are returned.
The new code looks like this:

        /// <summary>
        /// Get the list details by calling GetListAsync
        /// </summary>
        /// <param name="listName">The name of the list</param>
        private void getListDetail(string listName)
        {
            ListsSoapClient listsSoapClient = createListsSoapClient();
            listsSoapClient.GetListCompleted += new EventHandler<GetListCompletedEventArgs>(listsSoapClient_GetListCompleted);
            listsSoapClient.GetListAsync(listName);
        }
        /// <summary>
        /// Get the list items by calling GetListeItemsAsync
        /// </summary>
        /// <param name="listName">The name of the list</param>
        private void getListItems(string listName)
        {
            ListsSoapClient listsSoapClient = createListsSoapClient();
            listsSoapClient.GetListItemsCompleted += new EventHandler<GetListItemsCompletedEventArgs>(listsSoapClient_GetListItemsCompleted);
            XElement query = new XElement("Query",
                                    new XElement("OrderBy",
                                            new XElement("FieldRef", new XAttribute("Name", "ID"))));
            XElement viewFields;
            if (cbAll.IsChecked.HasValue && cbAll.IsChecked.Value)
            {
                //get all fields

                viewFields = null;
            }
            else
            {
                //get some of the standard fields

                viewFields = new XElement("ViewFields",
                        new XElement("FieldRef", new XAttribute("Name", "Title")),
                        new XElement("FieldRef", new XAttribute("Name", "ID")),
                        new XElement("FieldRef", new XAttribute("Name", "Modified")),
                        new XElement("FieldRef", new XAttribute("Name", "Editor")),
                        new XElement("FieldRef", new XAttribute("Name", "Created")),
                        new XElement("FieldRef", new XAttribute("Name", "Author")));
            }
            listsSoapClient.GetListItemsAsync(listName, string.Empty, query, viewFields, string.Empty, null, null);
        }
        /// <summary>
        /// Called to display the details for a selected list
        /// </summary>
        /// <param name="listData">The XElement code holding the currently selected list data</param>
        private void updateListDetailPanel(XElement listData)
        {
            this.currentListData = listData;

            string listName = listData.Attribute("Title").Value;

            if (rbListDetail.IsChecked.HasValue && rbListDetail.IsChecked.Value)
            {
                getListDetail(listName);
            }
            else if (rbListItems.IsChecked.HasValue && rbListItems.IsChecked.Value)
            {
                getListItems(listName);
            }
        }
        /// <summary>
        /// Called when the user selects a specific list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbxListOfLists_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                if (lbxListOfLists.SelectedItem is XElement)
                {
                    XElement listItem = lbxListOfLists.SelectedItem as XElement;
                    updateListDetailPanel(listItem);
                }


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

The next piece of code to write is the listsSoapClient_GetListItemsCompleted handler which is called when GetListItemsAysnc completes. Remember that all methods of the ListsSoapClient complete asynchronously. When the listsSoapClient is called, the Result property of the GetListItemsCompletedEventArgs will contain the root XElement node of the result set. An example of an xml tree returned by a call to GetListItemsAysnc can be seen here:

<listitemsxmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"xmlns:rs="urn:schemas-microsoft-com:rowset"xmlns:z="#RowsetSchema"xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<rs:dataItemCount="3">
<z:rowows_Attachments="0"ows_LinkTitle="A"ows_Alpha="A"ows_Gamma="Choice 1"... />

<z:rowows_Attachments="0"ows_LinkTitle="B"ows_Alpha="B"ows_Gamma="Choice 2"... /><

z:rowows_Attachments="0"ows_LinkTitle="C"ows_Alpha="C"ows_Gamma="Choice 3"... /></
rs:data>

</listitems>

To set the itemsSource of our ListView control we will need to extract an enumeration of the <z:row> element from the returned xml document. To do this we will use the Descendants() method of the XElement node. The Descendents() method returns an enumeration of all matching child elements. It is important to note here though that the namespace prefix "z" is defined in the root element as the namespace "#rowset-schema" so you will need to specify both the name of the elmeent "row" and the namespace "#rowset-schema" using XName, it order for the call to Decendents() to return any matches.

        /// <summary>
        /// Called when GetListItemsAysnc completes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void listsSoapClient_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)
        {
            try
            {
                txtOutput.Text = e.Result.ToString();
                this.lbxListItems.ItemsSource = e.Result.Descendants(XName.Get("row", SHAREPOINT_ROWSET_NAMESPACE));
            }
            catch (Exception exception)
            {
                handleException("GetListItems Failed", exception);
            }
        }

I've cheated a little here by using the XAttributeConverter helper class that was introduced in Part Three. This helper class allows us to pass the XElement node enumeration directly to the ListView control as it's ItemsSource. Normaly Silverlight controls and data templates to not know how to bind to an XElement node nor how to extract XAttribute values. The XAttributeConverter helper class allows us to directly bind to attributes of an XMl node. To see how this is used take a look at the XAML code again, you should notice the data templates defined in the resources section of the XAML definition as follows:

<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>

<DataTemplate x:Key="ListItemTemplate">

<StackPanel Orientation="Horizontal">

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Title}"/>

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_ID}"/>

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Last_x0020_Modified}"/>

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Editor}"/>

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Created}"/>

<TextBlock Margin="5" Text="{Binding Converter={StaticResource FromXAttribute}, ConverterParameter=ows_Author}"/>

</StackPanel>

</DataTemplate>

</UserControl.Resources>

 If you wish to display other fields in the ListView, first look at the list with the "Raw" checkbox checked. Note that all user field names are prefixed by SharePoint with "ows_". Just add or remove bindings in the ListItemTemplate.

 

Compiling and running the code should produce something similar to the following images:

 

 

 

RunA

 

RunB

 

RunC

 

RunD

 

Note if you wish for the Silverlight control to take up the entire browser page just delete the Width and Height specified in the UserControl element of the XAML definition.

Complete source for Part 4 of Practical Silverlight and SharePoint Integration can be found here: PracticalSilverlightAndSharePointIntegration.Part4.zip (1.76 mb)

Part One| Part Two| Part Three | Part Four (To be done)

Authors

All Categories

Archive