A Compendium of MOSS List Access and Manipulation Techniques using Services and RPC – Part 2

 

After a long and eventful couple of months I’m happy to be writing this next installment. Since my last entry I’ve compiled some great information. I’ve spent time with services in support of Rich Internet Applications (RIA) and started to explore Azure. Of course my little compendium of MOSS list access techniques now moves forward with this post.

Code for list access can leverage the well known patterns/techniques of either synchronous or asynchronous programming. Both patterns can be used for extracting list data. This is no great revelation to most programmers. Certainly one can load any source data synchronously but more often, particularly with regard to browser based applications, asynchronous patterns must be leveraged to avoid stalled page loads or other unwanted performance issues. Clearly without understanding how to load data synchronously you have nothing to work with in the first instance.                

A Quick Recap

SharePoint provides list data in a few different ways. SharePoint itself uses FrontPage RPC protocol to populate views but also provides SOAP services that wraps the SharePoint object model. One of the examples I provided in my last post is shown below – note that owssvr.dll (http://msdn.microsoft.com/en-us/library/ms478653.aspx) is embedded within the url (these are not SOAP/ASMX examples, those will come later).

To obtain list data enter a similar url.

http://currier-win03en.dev.currier.vm/_vti_bin/owssvr.dll?Cmd=Display&List={d8d532f6-e87e-4fb3-b3e1-92dbec0ea893}&Query=*&XMLDATA=TRUE

…and this is the list data returned to the browser – somewhat complex

clip_image001[1]

Take another look at an actual list view

clip_image001

If you view the html source from the loaded page you will see a <table> tag that appears something like this:

<table width="100%" class="ms-listviewtable" id="{971152F1-C8C6-42F4-8660-E4F2E053E9E7}-{07DDA3A8-C0A9-46E6-ACFA-C335BB6AA6B1}" dir="None" border="0" cellSpacing="0" cellPadding="1" summary="Rooms" o:WebQuerySourceHref="http://team.currier.com/_vti_bin/owssvr.dll?CS=65001&XMLDATA=1&RowLimit=0&List={971152F1-C8C6-42F4-8660-E4F2E053E9E7}&View={07DDA3A8-C0A9-46E6-ACFA-C335BB6AA6B1}">

Note that owssvr.dll is embedded in the “value” of the attribute named o:WebQuerySourceHref, see http://msdn.microsoft.com/en-us/library/ms474379.aspx

[as an aside, you can use class="ms-listviewtable" to dynamically style your list view with JQuery – also, if your need help locating css classes and other client side SharePoint generated HTML then download the IE developer (or similar) tool http://www.microsoft.com/downloadS/details.aspx?familyid=E59C3964-672D-4511-BB3E-2D5E1DB91038&displaylang=en also see http://msdn.microsoft.com/en-us/library/dd565628(VS.85).aspx ]

Two simple clients leveraging Front-page RPC and WebClient class

This first client example will load the above data synchronously  from the list (then uses Linq to query the list into a useful construct)

public static void SynchronousClient()
{

Uri uri = new Uri("http://team.currier.com/_vti_bin/owssvr.dll?CS=65001&XMLDATA=1&RowLimit=0&List={971152F1-C8C6-42F4-8660-E4F2E053E9E7}&View={07DDA3A8-C0A9-46E6-ACFA-C335BB6AA6B1}");
WebClient sp = new WebClient();

// add credentials
sp.Credentials = new System.Net.NetworkCredential("username", "password");
XmlReader reader = XmlReader.Create(sp.OpenRead(uri));  
XDocument loaded = XDocument.Load(reader);

// Query the data
var results = from myList in loaded.Descendants()
        where myList.Name.LocalName == "row"
        select myList;

// look at the data
foreach (XElement elem in results)
{
    string Title = elem.Attribute("ows_LinkTitle").Value;
    string Rooms = elem.Attribute("ows_Rooms").Value;
    string Building = elem.Attribute("ows_Building").Value;
}

sp.Dispose();

}

This next example uses an asynchronous pattern and RPC to obtain the same results. It’s based on a sample for Silverlight at this link

http://sharepoint.microsoft.com/blogs/GetThePoint/Lists/Posts/Post.aspx?ID=204

Note that static methods are not usable in this situation (as with the synchronous method above). Here is the code

class SampleAsyncWebClient
    {

        public void SimpleAsyncWebClient(){}

        public void AsynchronousClient()
        {

            Uri uri = new Uri("http://team.currier.com/_vti_bin/owssvr.dll?CS=65001&XMLDATA=1&RowLimit=0&List={971152F1-C8C6-42F4-8660-E4F2E053E9E7}&View={07DDA3A8-C0A9-46E6-ACFA-C335BB6AA6B1}");
            WebClient sp = new WebClient();

            // add credentials
            sp.Credentials = new System.Net.NetworkCredential("Administrator", "pass@word2");

            sp.OpenReadCompleted += new OpenReadCompletedEventHandler(sp_OpenReadCompleted);

            sp.OpenReadAsync(uri);

            // Pause for the async task to complete (not for production use of course, this is just a sample)

            Console.Read();

            sp.Dispose();

        }

        void sp_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                XmlReader reader = XmlReader.Create(e.Result);
                XDocument loaded = XDocument.Load(reader);

                // Query the data
                var results = from myList in loaded.Descendants()
                              where myList.Name.LocalName == "row"
                              select myList;

                // look at the data
                foreach (XElement elem in results)
                {
                    string Title = elem.Attribute("ows_LinkTitle").Value;
                    string Rooms = elem.Attribute("ows_Rooms").Value;
                    string Building = elem.Attribute("ows_Building").Value;
                }

            }
        }

    }

The above code now covers the basics with regard to RPC and reading list data. This next example uses the WebClient class to PUT files as attachments into a list. Although somewhat instructive this methodology is not all that useful.  Here’s what this approach looks like.

public static void SynchronousPUTListAttachmentClient()
        {
            //note: this method assumes that the item has an attachment/itemid folder already created
            string ItemID = "3";
            string DescFileName = "Sample2.stp";
            string SourceFileNamePath = @"C:UsersAdministratorDocumentsVisual Studio 2008ProjectsListServicesSamplesListServicesSamplesstatus2.stp";

            // Use this for list attachments ==> http://team.currier.com/Lists/Rooms/Attachments/"+ItemID+"/"+DescFileName
            // Use the following for doc library
            Uri uri = new Uri("http://team.currier.com/Shared%20Documents/" + DescFileName);

            WebClient sp = new WebClient();

            // add credentials
            sp.Credentials = new System.Net.NetworkCredential("Administrator", "pass@word2");
            byte[] ret = sp.UploadFile(uri, "PUT", SourceFileNamePath);
            sp.Dispose();

        }

Ok, for the moment lets put the FrontPage RPC and WebClient stuff aside. The next few samples will cover SOAP calls into SharePoint. These kinds of examples fill the internet. The idea here is to reduce the code down to the absolute minimum for clarity sake. You should also know that best practices such as OnError, Try/Catch, and other production worthy error trapping are not included here.

This first “foundation” sample covers the description of the list itself. I won’t go into the creation of the proxy object with Visual Studio or other command line utilities. The next few lines represent instantiating the object and setting authentication.

// reference the proxy and setup authentication

// List Service
ListServices.Lists lists = new ListServices.Lists();

          lists.Url = "http://team.currier.com/_vti_bin/Lists.asmx";
          lists.Credentials = new System.Net.NetworkCredential(“username", “password”);  // can also include domain here

          // alternately

// not used here –> proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

Ok, we now pass the proxy into each method and perform the requisite operation

/// <summary>
/// demo GetList service
/// </summary>
/// <param name="proxy"></param>
/// <param name="listname"></param>
 

public static void GetList(ListServices.Lists proxy, string listname)
{
// call the service  
XmlNode node = proxy.GetList(listname);

            // lets look at the list schema. Use the XML visualizer to view it. Its BIG, but one important attribute
            // we want from this service is the list guid. This can be used with other service calls
            string listschema = node.OuterXml;

            // lets look at some List attibutes
            string listguid = node.Attributes["ID"].Value;
            string listtitle = node.Attributes["Title"].Value;
            string listName = node.Attributes["Name"].Value;
            string listversion = node.Attributes["Version"].Value;
            string listdescription = node.Attributes["Description"].Value;

            // lets look at the field attributes for this list by creating navigator object
            XPathNavigator navigator = node.CreateNavigator();
            XPathNodeIterator iterator = navigator.SelectDescendants("Field", "http://schemas.microsoft.com/sharepoint/soap/",true);

// iterate over the fields
            while (iterator.MoveNext())
            {
                string fieldname_internal = iterator.Current.GetAttribute("Name", string.Empty);
                string fieldname_display = iterator.Current.GetAttribute("DisplayName", string.Empty);
                string fieldguid = iterator.Current.GetAttribute("ID", string.Empty);
                string fieldtype = iterator.Current.GetAttribute("Type", string.Empty);
            }

            //Console.ReadKey();
        }

The following is a small catalog of samples that I’ve played with along the way. I have a few more  but these should represent 80% of the use cases most encountered in daily work. More to come in subsequent parts. See the original MS examples as well and always compile and improve examples you find online yourself – Unfortunately I’ve downloaded broken code that I had spent considerable time studying – certainly validate my samples here well. Get in the habit of using LINQ, it’s simply a better way to communicate with your data.

In case your wondering here is my “using” declarations for the following samples. I’ve not checked yet if some of these includes are overkill…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Xml.Schema; 
using System.IO;
using System.Net;
using System.Web;

======================================================================

/// <summary>
/// demo UpdateList web service
/// </summary>
/// <param name="proxy"></param>
/// <param name="listname"></param>
public static void List_NewField(ListServices.Lists proxy, string listname)
{
    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;
    string listversion = node.Attributes["Version"].Value;

    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode listnode = xmlDoc.CreateNode(XmlNodeType.Element, "List", "");
    XmlNode fieldsnode = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

    // define ‘New’ column command
    fieldsnode.InnerXml = @"<Method ID=’1′>
                              <Field Type=’Number’ Name=’MyPercentComplete’ FromBaseType=’TRUE’ Percentage=’TRUE’ Min=’0′ Max=’1′ DisplayName=’My % Complete’>
                               <Default>0.5</Default>
                              </Field>
                            </Method>";

    // execute ‘New’ column command, note fieldnode in third position
    returnnode = proxy.UpdateList(listguid, listnode, fieldsnode, null, null, listversion);
}

================================================================== 

public static void List_UpdateField(ListServices.Lists proxy, string listname)
{

    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;
    string listversion = node.Attributes["Version"].Value;

    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode listnode = xmlDoc.CreateNode(XmlNodeType.Element, "List", "");
    XmlNode fieldsnode = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

    // changed default from 50% to 10%, also note internal field name representation
    fieldsnode.InnerXml = @"<Method ID=’1′>
                              <Field Type=’Number’ Name=’My_x0020__x0025__x0020_Complete’ FromBaseType=’TRUE’ Percentage=’TRUE’ Min=’0′ Max=’1′ DisplayName=’My % Complete’>
                               <Default>0.1</Default>
                              </Field>
                            </Method>";

    // , note fieldnode in fourth position
    returnnode = proxy.UpdateList(listguid, listnode, null, fieldsnode, null, listversion);

}

================================================================== 

public static void List_DeleteField(ListServices.Lists proxy, string listname)
{

    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;
    string listversion = node.Attributes["Version"].Value;

    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode listnode = xmlDoc.CreateNode(XmlNodeType.Element, "List", "");
    XmlNode fieldsnode = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

    // you must use the internal representation of the field name. See .GetList for field enumeration
    fieldsnode.InnerXml = @"<Method ID=’1′>
                              <Field Name=’My_x0020__x0025__x0020_Complete’/>
                            </Method>";

    // note fieldnode in fifth position
    returnnode = proxy.UpdateList(listguid, listnode, null, null, fieldsnode, listversion);

}

================================================================== 

public static void List_NewItem(ListServices.Lists proxy, string listname)
{
    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;
    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode listnode = xmlDoc.CreateElement("Batch");
    // changed default from 50% to 10%, also note internal field name representation
    listnode.InnerXml = @"<Method ID=’1′ Cmd=’New’>  
                              <Field Name=’Title’>Room 2</Field>  
                              <Field Name=’Rooms’>Room 2</Field>
                              <Field Name=’Building’>Building B</Field>
                            </Method>";  

    // add item
    returnnode = proxy.UpdateListItems(listguid,listnode);

}

================================================================== 

public static void List_UpdateItem(ListServices.Lists proxy, string listname)
{
    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;
    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();
    XmlNode listnode = xmlDoc.CreateElement("Batch");

    // changed default from 50% to 10%, also note internal field name representation
    listnode.InnerXml = @"<Method ID=’1′ Cmd=’Update’>
                              <Field Name=’ID’>4</Field>
                              <Field Name=’Title’>Room 3</Field>  
                              <Field Name=’Rooms’>Room 3</Field>
                              <Field Name=’Building’>Building C</Field>
                            </Method>";

    // update item
    returnnode = proxy.UpdateListItems(listguid, listnode);

}

================================================================== 

public static void List_QueryForItem(ListServices.Lists proxy, string listname)
{
    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;

    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();

    XmlNode query = xmlDoc.CreateElement("Query");

    // changed default from 50% to 10%, also note internal field name representation
    query.InnerXml = @"<Where>
                          <Gt>
                            <FieldRef Name=’ID’ />
                            <Value Type=’Counter’>2</Value>
                          </Gt>
                       </Where>";

    System.Xml.XmlElement fields = xmlDoc.CreateElement("ViewFields");
    fields.InnerXml = @"<FieldRef Name=’Title’ />
                        <FieldRef Name=’Rooms’ />
                        <FieldRef Name=’Building’ />";

    System.Xml.XmlElement options = xmlDoc.CreateElement("QueryOptions");
    options.InnerXml = "<DateInUtc>TRUE</DateInUtc>"; // for example only

    string rows = "10";
    System.Xml.XmlNode ListItems = proxy.GetListItems(listguid, null, query, fields,rows, options, null);

    XPathNavigator navigator = ListItems.ChildNodes[1].CreateNavigator();
    XPathNodeIterator iterator = navigator.SelectDescendants("row", "#RowsetSchema", true);

    // iterate over the fields
    while (iterator.MoveNext())
    {
        string Title = iterator.Current.GetAttribute("ows_Title", string.Empty);
        string Rooms = iterator.Current.GetAttribute("ows_Rooms", string.Empty);
        string Building = iterator.Current.GetAttribute("ows_Building", string.Empty);
    }

}

================================================================== 

public static void List_DeleteItem(ListServices.Lists proxy, string listname)
{

    XmlNode returnnode = default(XmlNode);

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;

    // build minimal xml
    XmlDocument xmlDoc = new XmlDocument();

    XmlNode listnode = xmlDoc.CreateElement("Batch");

    listnode.InnerXml = @"<Method ID=’1′ Cmd=’Delete’>
                              <Field Name=’ID’>4</Field>
                            </Method>";

    returnnode = proxy.UpdateListItems(listguid, listnode);
}

================================================================== 

public static void List_AddAttachment(ListServices.Lists proxy, string listname)
{

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;

    // grab a file from a file share (or some URI)
    FileStream fstr = File.OpenRead(@"C:UsersAdministratorDocumentsVisual Studio 2008ProjectsListServicesSamplesListServicesSamplesstatus2.stp");

    string fname = fstr.Name.Substring(3);
    byte[] fcontents = new byte[fstr.Length];
    fstr.Read(fcontents, 0, (int)fstr.Length);
    fstr.Close();

    string itemid = "2";

    string addAttach = proxy.AddAttachment(listguid, itemid, fname, fcontents);

}

================================================================== 

public static void List_DeleteAttachment(ListServices.Lists proxy, string listname)
{

    // use GetList to pull back some needed attributes
    XmlNode node = proxy.GetList(listname);
    string listguid = node.Attributes["ID"].Value;

    string itemid = "2";

    XmlNode filecollection = proxy.GetAttachmentCollection(listguid, itemid);
    XmlNodeList attachments = filecollection.ChildNodes;

    for (int i = 0; i < attachments.Count; i++)
    {
        string delUrl = attachments[i].InnerText;
        proxy.DeleteAttachment(listguid, itemid, delUrl);
    }

}

================================================================== 

This uses the following proxy

copy.Url = "http://team.currier.com/_vti_bin/Copy.asmx";
copy.Credentials = new System.Net.NetworkCredential("username", "password");

public static void Library_UploadDocumentToNewRow(CopyServices.Copy proxy, string libraryname)
{

    string encodedname = HttpUtility.UrlEncode(libraryname);
    List<CopyServices.FieldInformation> fields = new List<CopyServices.FieldInformation>();
    CopyServices.FieldInformation fieldInfo = new ListServicesSamples.CopyServices.FieldInformation();

    fieldInfo.DisplayName = "Comment";
    fieldInfo.Value = "This is a comment associated with the document";
    fieldInfo.Type = CopyServices.FieldType.Text;

    fields.Add(fieldInfo);

    // grab a file from a file share (or some URI)
    FileStream fstr = File.OpenRead(@"C:UsersAdministratorDocumentsstatus2.stp");

    string fname = fstr.Name.Substring(3);
    byte[] fcontents = new byte[fstr.Length];
    fstr.Read(fcontents, 0, (int)fstr.Length);
    fstr.Close();

    string destinationfilename = "MyNewFile3";

    string[] url = new string[1] ;

    // note: not encoded
    url[0] = "http://team.currier.com/Shared Documents/" + destinationfilename;

    CopyServices.CopyResult[] result = null; 

    proxy.CopyIntoItems("+", url, fields.ToArray(), fcontents, out result);

}

I’ll be adding additional SOAP calls as I try them out myself. In part 3 (which may not be the next post) I’ll cover making these calls with JavaScript and JQuery. Copy these and have some fun.

Advertisements

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 )

Google+ photo

You are commenting using your Google+ 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 )

Connecting to %s