In my previous post I wrote how you can access the contents of discussion boards using web services. For reading existing discussions threads the Lists web service was all that we needed. However there were problems when creating discussions using the same service. After whole day of trial and error I finally gave up this approach and decided to first try using SharePoint's object model instead.
On the side note, I was using our test server over Internet with was fine for working with web services. However to use object model I needed to have SharePoint locally so I could run the code directly on the server. For this I used Virtual PC machine with Windows 2003 Server and Windows SharePoint Services 3.0 installed. On this machine I've setup remote debugging so I could compile and debug the code in Visual Studio on my host machine while running it on the server.
So first I've tried to simply create new SPListItem with the Items.Add method on the corresponding SPList object. But although I tried setting several different properties I still got the same results as with Web Services. The only difference was that I somehow managed to get correct Content Type for messages.
While searching for some clue I also found an interesting benchmark from Michael Ekegren that compares various methods for inserting items to lists. Clearly using object model to add to Items collection has the worst performance.
After a bit more of searching I've finally found that in order to insert discussion items properly I must use two special methods on the SPUtility class. The CreateNewDiscussion static method creates a new discussion thread and the CreateNewDiscussionReply method creates a reply to existing thread. So I figure there has to be some magic involved for handling discussion if there are special methods just for this purpose. Later I found a blog post that shows how it could be done without using SPUtility.
With the help of these methods I could quickly construct an additional Discussions web service to add the missing functionality. It has two methods that simply wrap the calls to corresponding methods on SPUtility and map the parameters.
[WebMethod]
public XmlNode CreateNewDiscussion(string listName, string subject, string body)
{
SPList list = FindList(listName);
if (list == null)
throw new ArgumentException("List name is invalid.");
SPListItem discussion = SPUtility.CreateNewDiscussion(list.Items, subject);
discussion[SPBuiltInFieldId.Body] = body;
discussion.Update();
// this is only to ensure the Xml can be generated
object t = discussion[SPBuiltInFieldId.ThreadIndex];
XmlDocument doc = new XmlDocument();
doc.LoadXml(discussion.Xml);
return doc.DocumentElement;
}
[WebMethod]
public XmlNode CreateNewDiscussionReply(string listName, int parentId, string body)
{
SPList list = FindList(listName);
if (list == null)
throw new ArgumentException("List name is invalid.");
SPListItem parent = list.GetItemById(parentId);
SPListItem reply = SPUtility.CreateNewDiscussionReply(parent);
reply[SPBuiltInFieldId.Body] = body;
reply.Update();
// this is only to ensure the Xml can be generated
object t = reply[SPBuiltInFieldId.ThreadIndex];
XmlDocument doc = new XmlDocument();
doc.LoadXml(reply.Xml);
return doc.DocumentElement;
}
First method CreateNewDiscussion requires name of the discussion list, subject and body for the new discussion thread. The CreateNewDiscussionReply also takes the name of the discussion list, the Id of the parent message (the one you reply to) and the body of the reply. Both methods return the XmlNode with the data from the new item to reproduce the behavior of the Lists service's UpdateListItem method. You might notice a small hack where I read some property of the new item before converting it to XmlNode. I found that this is required in order to regenerate the Xml property. Without this extra call this property would always return null.
I wanted to allow the list name to be either it's Title or ID just like in the methods of the Lists WebService so I'm using a helper FindList method to do the mapping.
private SPList FindList(string listName)
{
if (String.IsNullOrEmpty(listName))
throw new ArgumentNullException("listName");
SPWeb site = SPContext.Current.Web;
SPListCollection listCollection = site.Lists;
try
{
Guid listID = new Guid(listName);
foreach (SPList list in listCollection)
{
if (listID == list.ID)
return list;
}
return null;
}
catch
{ }
foreach (SPList list in listCollection)
{
if (String.Equals(listName, list.Title, StringComparison.InvariantCultureIgnoreCase))
return list;
}
return null;
}
This method first tries to convert the list name to a Guid and find the list by it's ID. If the string is not a proper Guid it tries to find the list by it's name instead. Notice I've used the static String.Equals overload to do the comparison in invariant culture and ignoring case.
There are some additional steps that you need to perform to install this web service on SharePoint server. Follow the steps outlined in the nice walkthrough on creating custom web services in the documentation for WSS SDK .
So with the help of this additional service I finally managed to finish my first assigned task on the TSRI project. Below you can see a screenshot from the application showing the preview window for the discussion annotation.
Users can also reply to and edit individual discussion messages right in the application.
I will write more posts soon on the interface implementation (starting with the control templates for the glass toolbar).