DevExpertise

Practical tips and tricks for all things .NET, SharePoint, Silverlight, InfoPath, and general application development.

Retrieving SharePoint List Data from Silverlight Without a Custom WCF or ASP.NET Web Service

Posted by DevExpert on 7th February 2009

I’ve done a little work in the recent months with Silverlight, specifically with leveraging Silverlight inside of SharePoint.  My first project involved a pulling in data from SharePoint via a WCF service, and while it worked, it took a long time to configure correctly and to add the necessary components to SharePoint in order for it to work.  Sahil Malik describes a method beginning here, and as you can see it’s definitely doable but not the simplest approach.

While creating a WCF will offer you the most power and flexibility when it comes to the data it can expose, as you can manipulate SharePoint data (or any data for that matter) any way you like.  In this post, I’m going to show you how to retrieve SharePoint list data in Silverlight the “old-fashioned” way – by using the out-of-the-box SharePoint web services, and executing an HttpWebRequest to get our data.

Because there are a ton of different concepts I could elaborate on related to Silverlight and SharePoint, such as creating a web part host, configuring SharePoint for Silverlight, etc., for the sake of this post I’m going to keep it focused on the data retrieval only.  In future posts, I’ll dive into my approach for hosting a Silverlight app in SharePoint.  Also, take a look at this post from Karine Bosch – she has a very similar approach and hers is a lot prettier than mine!

First, I defined the “Widgets” list I want to retrieve data from. In addition to the Title field, it contains a Description field and an Image field:image

Next, I created a new Silverlight application in Visual Studio, and defined a Widget class, which will serve as the representation of a Widget list item:

public class Widget {
    public string Title { get; set; }
    public string Description { get; set; }
    public string ImageUrl { get; set; }
}

I also added a few class-level variables in my Page.xaml.cs file, which I’ll use in my data methods:

private List<Widget> _widgets = null;
private Stream _body = null;
private string _responseString;

For the data retrieval, I’m going to be leveraging the out-of-the-box Lists.asmx web service to retrieve the list items, and build an HttpWebRequest object.  The BeginGetRequestStream method executes the request, and will asynchronously call the RequestCallback method when it finishes:

private void GetData() {
    try {
        Uri listUri = new Uri("http://server/_vti_bin/Lists.asmx", UriKind.Absolute);
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(listUri);

        request.Method = "POST";
        request.ContentType = "application/soap+xml; charset=utf-8";
        request.Headers["ClientType"] = "Silverlight";
        request.BeginGetRequestStream(new AsyncCallback(RequestCallback), request);
    }
    catch (Exception ex) {
        HandleException(ex);
    }
}

Here’s where the real work needs to be done.  In our callback method, we need to build a SOAP envelope that will be passed to the web service in order to get a valid response.  If you’re uncertain where to find that envelope or are unsure if how it should be formatted, simply browse to the web service itself and click the method that you will be executing:

image

Once the SOAP envelope format is determined, simply add in the listName, viewName (if necessary), query, viewFields, and queryOptions, and you have your envelope.  The next step is to get the response by asynchronously executing the BeginGetResponse method and passing in the ResponseCallback callback method:

private void RequestCallback(IAsyncResult asyncResult) {
    try {
        string envelope =
            @"<?xml version=""1.0"" encoding=""utf-8""?>
                <soap12:Envelope
                 xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
                 xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
                 xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
                    <soap12:Body>
                        <GetListItems xmlns=""http://schemas.microsoft.com/sharepoint/soap/"">
                            <listName>Widgets</listName>
                            <query>
                                <Query xmlns="""">
                                    <OrderBy>
                                        <FieldRef Name=""Title"" />
                                    </OrderBy>
                                </Query>
                            </query>
                            <viewFields>
                                <ViewFields xmlns="""">
                                    <FieldRef Name=""Title"" />
                                    <FieldRef Name=""Description"" />
                                    <FieldRef Name=""Image"" />
                                </ViewFields>
                            </viewFields>
                            <queryOptions>
                                <QueryOptions xmlns="""">
                                    <IncludeMandatoryColumns>False</IncludeMandatoryColumns>
                                </QueryOptions>
                            </queryOptions>
                        </GetListItems>
                    </soap12:Body>
                </soap12:Envelope>";

        UTF8Encoding encoding = new UTF8Encoding();
        HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
        _body = request.EndGetRequestStream(asyncResult);
        envelope = string.Format(envelope, _listName);
        byte[] formBytes = encoding.GetBytes(envelope);

        _body.Write(formBytes, 0, formBytes.Length);
        _body.Close();

        request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
    }
    catch (WebException ex) {
        HandleException(ex);
    }
}

The ResponseCallback method is responsible for determining that the response is valid, and to get the data out of the response.  Once the data is retrieved from the response stream, the only step left is to process that data, and for that we call ProcessResponse.  We must use the BeginInvoke() method of the page’s Dispatcher object, because all of the data retrieval is done asynchronously on a different thread.  Since you can’t modify controls on a different thread than the one it was created on, we have to invoke it this way and let Silverlight handle the thread marshalling:

private void ResponseCallback(IAsyncResult asyncResult) {
    HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
    Stream content = response.GetResponseStream();

    if (request != null && response != null) {
        if (response.StatusCode == HttpStatusCode.OK) {
            _widgets = new List<Widget>();

            using (StreamReader reader = new StreamReader(content)) {
                _responseString = reader.ReadToEnd();
                reader.Close();
            }

            try {

                this.Dispatcher.BeginInvoke(ProcessResponse);

            }
            catch (WebException ex) {
                HandleException(ex);
            }
        }
    }
}

And finally, the ProcessResponse method that parses the response data and creates are Widgets collection, which is then set as the page’s DataContext:

private void ProcessResponse() {
    XDocument results = XDocument.Parse(_responseString);

    _widgets = (from item in results.Descendants(XName.Get("row", "#RowsetSchema"))
                select new Widget() {
                    Title = item.Attribute("ows_Title").Value,
                    Description = item.Attribute("ows_Description").Value
                    ImageUrl = FormatImageUrl(item.Attribute("ows_Image").Value),
                }).ToList();

    this.DataContext = _widgets;
}

private string FormatImageUrl(string value) {
    return value.Substring(0, value.IndexOf(','));
}

Now that we have our data, creating a snazzy Silverlight web part is only limited by your creativity and XAML-slinging skills.  Here’s a quick and dirty snippet to verify we’re actually getting data:

<UserControl x:Class="DevExpertise.Silverlight.App.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <StackPanel>
        <Grid x:Name="LayoutRoot" Background="White">
            <ListBox ItemsSource="{Binding}" VerticalAlignment="Stretch">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding ImageUrl}" Height="40" />
                                <StackPanel>
                                    <TextBlock Text="{Binding Title}" FontWeight="Bold" />
                                    <TextBlock Text="{Binding Description}" />
                                </StackPanel>
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </StackPanel>
</UserControl>

…and voila!  Silverlight in SharePoint consuming list data:

image

That’s all it takes to get data from SharePoint without using a custom web service or having to go through the hassle of building and deploying a WCF service.  In a future post, I’ll describe how to build a generic and reusable web part to host Silverlight applications.  Stay tuned!

Tags: , , , , ,
Posted in .NET, SharePoint, Silverlight, XAML | 9 Comments »

Deleting List Items in a SharePoint List

Posted by DevExpert on 4th February 2009

Occasionally you may encounter a need to delete list items from a SharePoint list.  This sounds like an easy enough task – just loop through the SPList’s Items collection and delete the item.  That would be the intuitive way, but then again this is the SharePoint Object Model.  Let’s take a look a bunch of ways you may try to accomplish this task, and a single way which actually works.

To run these tests, I created a simple custom list named Widgets that contains 10 items:

image 

Incorrect Method #1
The first approach that is probably taken is to loop through the list items with a For..Each loop, and call the Delete() method of each list item, like the following:

using (SPSite siteCollection = new SPSite("http://server")) {
    using (SPWeb site = siteCollection.OpenWeb()) {
        SPList list = site.Lists["Widgets"];

        foreach (SPListItem item in list.Items) {
            item.Delete();
        }
    }
}


However, whenever you run this you encounter the following error:

Collection was modified; enumeration operation may not execute.

To help explain this, it will help to first understand the IEnumerable interface, from with the SPListItemCollection ultimately implements. Unfortunately the MSDN documentation is a little scarce on modifying collections with this interface, but the IEnumerator interface contains a lot of good information that applies to IEnumerable as well. 

Basically, enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection.  An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection (such as deleting), the enumerator is irrecoverably invalidated.

Incorrect Method #2

Ok, learning from the mistake in method 1, it makes sense to eliminate the For..Each loop and use a traditional For loop. This method uses an index which increments at each loop, and we call the Delete(index) method of the list item which accepts an item index:

for (int i = 0; i < list.Items.Count; i++) {
    Console.WriteLine("Deleting {0}", list.Items[i].Title);
    list.Items.Delete(i);
}


To visualize the results, I’m outputting the item it’s deleting so we can see if everything is working as it should:

image

What the heck is happening here?  Why is it deleting every other item?  Well, every time you delete an item from the Items collection, the number of items in that collection decreases and thus the indexes no longer match up.

Incorrect Method #3

Hmm, OK…if it’s deleting every other item because the collection is adjusted when items are deleted from it, let’s just be safe and always try to delete the first item at index 0:

for (int i = 0; i < list.Items.Count; i++) {
    Console.WriteLine("Deleting {0}", list.Items[0].Title);
    list.Items.Delete(0);
}


The result is exactly like Method #2 – it deletes every other item:

image


Incorrect Method #4

For kicks and giggles, what if the code is adjusted to delete the item at index 0 inside a For..Each loop?:

foreach (SPListItem item in list.Items) {
    Console.WriteLine("Deleting {0}", list.Items[0].Title);
    list.Items.Delete(0);
}


Woah, that worked! As you can see, it deleted every item:

image

But wait… What if we need to delete an item only if a condition is met?  Let’s try to loop through the collection and only delete the item whose title is Sample Item #5:

foreach (SPListItem item in list.Items) {
    if (item.Title == "Sample Item 5") {
        Console.WriteLine("Deleting {0}", list.Items[0].Title);
        list.Items.Delete(0);
    }
}


The result is what you should expect after deleting the first item in the index – it deletes the first item, not the item that met the condition:image


Correct Method

Ok, finally here’s the correct method for deleting list items.  The trick is to use a decrementing For loop.  The For loop in the following example counts downward (i–) instead of upward (i++), because items are being deleted and the number of items decreases with each increment:

for (int i = list.Items.Count - 1; i >= 0; i--) {
    list.Items.Delete(i);
}


Which produces:

image

What about deleting an item based on a condition?:

for (int i = list.Items.Count - 1; i >= 0; i--) {
    if (list.Items[i].Title == "Sample Item 5") {
        Console.WriteLine("Deleting {0}", list.Items[i].Title);
        list.Items.Delete(i);
    }
}


It works!:

image

 

Hopefully you will always remember to delete list items (actually, the same goes for most other SharePoint collections too, such as SPFieldCollection, SPWebCollection, etc.) using a decrementing counter.  Happy coding!

Tags: , ,
Posted in .NET, Object Model, SharePoint | 8 Comments »

Adding Custom Properties to SharePoint Features

Posted by DevExpert on 3rd February 2009

Any time you deploy a custom development artifact, be it a web part, assembly, etc., you should do it via a feature and SharePoint solution – especially if we’re deploying it to a client.  Occasionally you need to run code when you activate or deactivate your feature by using a feature receiver.  Let’s say that you need add a custom configuration property that will change between deployments.  For example, one could write a generic feature that can be used for multiple projects and/or clients, and the only thing that changes is a path or value of something.  Instead of placing these in a web.config file, you can place them directly in the feature.xml file that defines your feature.  It’s a much better approach that mucking with the web.config, especially since you’d need to do it before activating the feature or installing the solution package.

Here’s an example of a feature I recently wrote for a client that ran when a new site was created.  The feature receiver executes when it’s activated, and configures custom permissions for the site.  I needed to add a farm administrator account to the site, but didn’t want to hard-code that into the actual code, so I put it in the feature definition:

image

Now, when my feature receiver code executes, I can retrieve these properties:

public override void FeatureActivated(SPFeatureReceiverProperties properties) {
    string farmAdminDomain = properties.Feature.Properties["FarmAdminDomain"].Value;
    string farmAdminLoginName = properties.Feature.Properties["FarmAdminLoginName"].Value;
    string farmAdmin = string.Format("{0}\\{1}", farmAdminDomain, farmAdminLoginName);

    // do something with values

 

Features are cool.

Tags: , ,
Posted in .NET, Object Model, SharePoint | 2 Comments »

Setting the Welcome Page in WSS 3.0

Posted by DevExpert on 2nd February 2009

Whenever someone navigates to your SharePoint site without a specific page specified in the URL, such as http://fakeserver/sites/fakesite/, SharePoint navigates you to the site’s welcome page, typically default.aspx.  MOSS publishing sites provide the ability to change this by going to Site Settings > Welcome Page, and changing the page.  What about team sites, or WSS sites?  There is no way to modify the welcome page through the UI.  One could open SharePoint Designer and do it that way, but this isn’t ideal if you don’t have a license for SPD.  I’ll be describing another approach in this post that leverages the Object Model, and I’m even providing a complete SharePoint solution (WSP) file that you can install and activate on any WSS site!

First, let’s take a look at the code snippet to make this happen.  The SPFolder object has a property called WelcomePage, and all that is needed is to set this property on the SPWeb’s RootFolder:

using (SPSite siteCollection = new SPSite(http://server/sites/sandbox)) {
    using (SPWeb site = siteCollection.RootWeb) {
        SPFolder rootFolder = site.RootFolder;
        rootFolder.WelcomePage = "Pages/home.aspx";
        rootFolder.Update();
    }
}


Now, whenever I hit http://server/sites/sandbox, I am directed to my custom Home.aspx page in my Pages document library:

image


Woo hoo!  If I click on the Sandbox title or its icon, or even in the breadcrumb, I’m directed here, because it’s to the site’s root URL, not to a specific page.  But what if I click the Home link on the top navigation bar? It takes me to my original default.aspx page, which defeats the purpose.  To fix this, we need to add the following lines of code:

SPNavigationNode homeNode = site.Navigation.TopNavigationBar.Navigation.Home;
homeNode = site.Navigation.TopNavigationBar[0];
homeNode.Url = SPUrlUtility.CombineUrl(site.Url, "Pages/home.aspx");
homeNode.Update();


Now, no matter what I click to bring me back to the root page, I’m directed to my home.aspx page.  Who said the Welcome Page was a MOSS-only feature?

 

SharePoint Solution

It doesn’t make sense to expect an end user to run a console app or write custom code to do this.  It’s possible to do this via SharePoint designer too, but what if they don’t have it?  It makes a lot more sense to present this to users in the browser, exactly like a MOSS publishing site.  To provide this functionality, it’s just as simple as creating a custom LAYOUTS page and a solution package that can be used to deploy and install it. 

First, we create the file-system structure in the Visual Studio solution:

image


Next, define the feature:

<?xml version="1.0" encoding="utf-8"?>
<Feature
  Id="2D3BDDCF-E6A4-4110-922C-70E79652C6B9"
  Title="DevExpertise Welcome Page Feature"
  Description="Contains the page and shortcuts to change a site's welcome page"
  Version="1.0.0.0"
  Scope="Site"
  Hidden="false"
  ImageUrl="DevExpertise.WelcomePage\devExpertiseFeatureLogo.png"
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="CustomActions.xml" />
  </ElementManifests>
</Feature>


The custom action should be defined to add a link to the Look & Feel category (GroupId=”Customization”):

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="UserGroupAdminLinkForSettings"
    GroupId="Customization"
    Location="Microsoft.SharePoint.SiteSettings"
    RequireSiteAdministrator="TRUE"
    Sequence="120"
    Title="Welcome Page">
    <UrlAction Url="_layouts/DevExpertise.WelcomePage/WelcomePage.aspx" />
  </CustomAction>
</Elements>


Package up your solution using WSPBuilder and deploy it.  After it’s deployed and activated on the site collection, I’m presented with a Welcome Page link on each and every sub site in the site collection, allowing me to set the welcome page for each site:

image


Clicking the link will navigate to the custom LAYOUTS page I created:

image


This is a great method for providing functionality that may be missing from the user interface.  Once you master the steps required to create a LAYOUTS page (which I’ll be covering step-by-step in a future post), and master creating custom actions, the possibilities to extend and enhance your SharePoint site are endless.

I’ve included this solution package for you to deploy and use.  As always, the code is as-is and without warranty, so if it doesn’t work – fix it!

Download Solution: DevExpertise.WelcomePage.zip

Tags: , , ,
Posted in .NET, Object Model, SharePoint | 3 Comments »