I was recently posed with the question of managing some of our HR open positions using SharePoint while having the ability to display these open positions on our external website. This sent me down a path that has given me some great understanding of the SharePoint Lists Web Service.
The first thing I wanted to do is make sure that I don't spend a lot of time recreating classes for use on this project. I imagine that at some point in the future someone else will approach me about presenting a list of their data on our external web site (or for use by an internal application). So, the first goal is to get as much reuse as possible. This is where I discovered some of the power of Generics for the first time.
As usual, I started with a search of the web and discovered this article. This was exceptional information and thus allowed me to begin to understand how I might accomplish my mission.
So, following the pattern of this article I assembled a SharepointListItems class that would be the basis for all sharepoint list data retrieved from the Lists Web Service (http://server/_vti_bin/lists.asmx). Here is the code for the SharepointListItems class (it's in VB because this is our language at work)
SharepointListItems.vb
Imports System.Xml Imports System.Xml.Serialization Imports System.Text Imports System.Collections.Generic Imports System <XmlRoot(ElementName:="listitems", Namespace:="http://schemas.microsoft.com/sharepoint/soap/")> _ Public Class SharepointListItems(Of T As BaseListItem) Private rows As RowData(Of T) = Nothing <XmlElement(ElementName:="data", Namespace:="urn:schemas-microsoft-com:rowset")> _ Public Property RowData() As RowData(Of T) Get Return rows End Get Set(ByVal value As RowData(Of T)) rows = value End Set End Property Public Shared Function FromXml(ByVal xmlNode As String) As SharepointListItems(Of T) Dim xs As New XmlSerializer(GetType(SharepointListItems(Of T))) Dim xtr As New XmlTextReader(xmlNode, XmlNodeType.Element, Nothing) Dim o As Object = xs.Deserialize(xtr) xtr.Close() Return DirectCast(o, SharepointListItems(Of T)) End Function End Class
All of the work for this class is being handled through the shared function FromXML. This allows me to make a simple method call to get my results as a group of objects.
Next, I built the RowData class to hold a reference for the <rs:data> element. This class is also a generic class so I can hold a specified list of items
RowData.vb
Imports System.Xml Imports System.Xml.Serialization Imports System.Text Imports System.Collections.Generic Imports System <XmlRoot(ElementName:="listitems", Namespace:="http://schemas.microsoft.com/sharepoint/soap/")> _ Public Class SharepointListItems(Of T As BaseListItem) Private rows As RowData(Of T) = Nothing <XmlElement(ElementName:="data", Namespace:="urn:schemas-microsoft-com:rowset")> _ Public Property RowData() As RowData(Of T) Get Return rows End Get Set(ByVal value As RowData(Of T)) rows = value End Set End Property Public Shared Function FromXml(ByVal xmlNode As String) As SharepointListItems(Of T) Dim xs As New XmlSerializer(GetType(SharepointListItems(Of T))) Dim xtr As New XmlTextReader(xmlNode, XmlNodeType.Element, Nothing) Dim o As Object = xs.Deserialize(xtr) xtr.Close() Return DirectCast(o, SharepointListItems(Of T)) End Function End Class
Finally, I needed my base class that I would use to capture common list items. This class uses the XmlAttribute attribute to map the contents of the returned Xml to the object's properties.
BaseListItem.vb
Imports System.Xml Imports System.Xml.Serialization Imports System.Text Imports System.Collections.Generic Imports System Public Class BaseListItem Protected _id As Integer = 0 Protected _attachments As Integer = 0 Protected _hiddenVersion As Integer = 0 Protected _linkTitle As String = String.Empty Protected _title As String = String.Empty Public Sub New() End Sub <XmlAttribute("ows_Title")> _ Public Property Title() As String Get Return _title End Get Set(ByVal value As String) _title = value End Set End Property <XmlAttribute("ows_Attachments")> _ Public Property AttachmentCount() As Integer Get Return _attachments End Get Set(ByVal value As Integer) _attachments = value End Set End Property <XmlAttribute("ows_owshiddenversion")> _ Public Property HiddenVersion() As Integer Get Return _hiddenVersion End Get Set(ByVal value As Integer) _hiddenVersion = value End Set End Property <XmlAttribute("ows_ID")> _ Public Property ID() As Integer Get Return _id End Get Set(ByVal value As Integer) _id = value End Set End Property <XmlAttribute("ows_LinkTitle")> _ Public Property LinkTitle() As String Get Return _linkTitle End Get Set(ByVal value As String) _linkTitle = value End Set End Property End Class
Now I am ready to begin my project of importing job listings from my HR site into objects that I can use on the external website (or other applications). This object inherits it's functionality from the BaseListItems class and extends the functionality to include items specific to this list.
JobListing.vb
Imports EAS.SharepointListWebService Public Class JobListing Inherits BaseListItem Private _PositionType As String Private _PostingDepartment As String Private _Description As String Private _ContactPerson As String Public Sub New() End Sub <XmlAttribute("ows_Position Type")> _ Public Property PositionType() As String Get Return _PositionType End Get Set(ByVal value As String) _PositionType = value End Set End Property <XmlAttribute("ows_Posting Department")> _ Public Property Department() As String Get Return _PostingDepartment End Get Set(ByVal value As String) _PostingDepartment = value End Set End Property <XmlAttribute("ows_Description")> _ Public Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property <XmlAttribute("ows_Contact Person")> _ Public Property ContactPerson() As String Get Return _ContactPerson End Get Set(ByVal value As String) _ContactPerson = value End Set End Property End Class
NOTE: I discovered that the _x0020_ convention used for the returned attributes will actually cause problems. If you'll notice, I have some attributes that look like ows_Contact Person instead of the expected ows_Contact_x0020_Person.
Now for the implementation of the code. Here's how I use the code in my application to populate a group of objects.
GetData Function
Private Sub GetJobData(ByVal username As String, ByVal password As String) Dim CAMLDoc As New XmlDocument Dim ndQuery As XmlNode = CAMLDoc.CreateElement("Query") Dim HR As New HRListsWebService.Lists ndQuery.InnerXml = "<Where>" & _ "<And>" & _ "<Eq>" & _ "<Value Type='Text'>Open - Approved</Value>" & _ "<FieldRef Name='Open'></FieldRef>" & _ "</Eq>" & _ "<Eq>" & _ "<Value Type='Integer'>1</Value>" & _ "<FieldRef Name='List_x0020_On_x0020_External_x00'></FieldRef>" & _ "</Eq>" & _ "</And>" & _ "<OrderBy>" & _ "<FieldRef Ascending='TRUE' Name='Posting Department'/>" & _ "</OrderBy>" & _ "</Where>" Dim results As SharepointListItems(Of JobListing) Try HR.Credentials = New System.Net.NetworkCredential(userName, password) Dim ndListItems As XmlNode = HR.GetListItems("Open Positions List", String.Empty, ndQuery, Nothing, String.Empty, Nothing, Nothing) results = SharepointListItems(Of JobListing).FromXml(ndListItems.OuterXml) Catch ex As Exception Throw New Exception("Unsuccessful query to the SharePoint web service.", ex) End Try _JobListItems = results End Sub
I hope this helps someone out there working with lists. I'm open to suggestions as I'm still learning how this really works!