23 August 2007

Lists.asmx Serialization

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!

21 August 2007

Office Authentication Prompt in Vista for Sharepoint Documents

I recently ran across a problem with the Vista clients on my network being prompted for user credentials each time they opened a document from a document library -- even though they were already authenticated to the portal. With some help from my network administrator, we were able to find a solution that seems to work well. The information for this fix was posted here. We implemented solution 2 into our network and everything has been humming along perfectly. Hope this helps someone stuck with this problem!