Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  96 Posts | 5 Stories | 256 Comments | 369 Trackbacks

News

Spotkasz mnie na CodeCamp'09 View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

Blogs I Read

Tools I Use

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).

posted on Friday, March 16, 2007 10:01 AM

Feedback

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 3/21/2007 12:37 PM Hani
Hey Man,
I can title you as God.... i have been searching for the exact thing what you have done.Even i am supposed to use these Discussion Boards in my web application.

Jesus you saved my time :-) .... Thanks a ton. If possible please mail me the necessary source code.I need to complete my requirement in 3 more days. Thanks Again.


Thanks,
Hani
honey008@gmail.com

# Links (3/19/2007) 4/25/2007 9:45 PM Member Blogs
.NET C# regex email address Convert Array List to DataTable Describing Types in .NET Basic C# coding

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 5/17/2007 6:21 AM Arun
Hey ...

Thanks a lot man ... you blog was really useful for me, otherwise it was supposed to be a big burden for me, for creating the Discussion in 2007.. actually I am working on migrating from 2003 to 2007..

Thanks a lot
May God Bless you ...


Regards
Arun.

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 7/11/2007 11:21 AM AdamK
Thank you so much...

This is exactly what I was looking for. The threading problem was causing me a big pain in the backside!

AdamK

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 8/21/2007 1:50 PM Kathir
Hi,

Great Post!!!...I was able to create a new discussion forum if not exists and able to create a discussion reply for the same. But i couldn't get to know about the CAML querying the discussion board to get all the items and want to render into the Tree view in my web application. How do you did for your application showing all the discussion items in the tree view (looks like exapand and collapse kind of).? Please help me in this regard. This is urgent!!.

Thanks,
Kathir

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 11/30/2007 9:27 PM Gopi
I have a custom column named 'category' in the Discussion list
How can I provide the value to it using SPBuiltInFieldId?
trying like this
discussion[SPBuiltInFieldId.Category] = Category; but ending up with "Invalid field name. {6df9bd52-550e-4a30-bc31-a4366832a87d}" exceptions?
how to map the custom column value using SPBuiltInFieldId?
Thanks
Gopi


# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 12/31/2007 10:44 PM Vivek
hi,

Thanks for the great help !!
I'll greatly appreciate if you help me to solve this problem:
I have used your webservice to export dicussions from an access database to MOSS, for the first few times (around 374 items) the webservice works fine , after that it starts giving error
"operation is not valid due to the current state of the object"..plz help!!

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 8/18/2008 3:08 PM Jagdish
Hi,

Thanks a lot Szymon Kobalczyk.

I would like to know how did you do Tree View of discussion board items. I have struggled a lot but couldn't find any hint on this.

Appreciate your help.

Thanks in advance.

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 6/1/2009 6:55 PM Miguel
How to get the parentID?

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 7/7/2009 3:18 AM MK
Hi Szymon Kobalczk,

This great. I hope I can get help from you.

I am working on similar task. I followed your suggestions and I have created a additional webservice and published it on Windows 2003 Server IIS 6.0. And calling the Webmethod CreateNewDiscussionReply at my client devlopment environment (Windows XP- VS 2008 - a WPF appln) by adding this addition webservice class. But I am getting an error - Object reference not set to an instance of an object.

I just create a Webservice application in Windows2003 Server and VS 2008 with mentioned two webmetods, and trying to call this Web service in my local system (XP- VS2008). I think I missed out some more functions in Service appln. Code snippet for the webservices is as follows:

public class NewLists : System.Web.Services.WebService
{
ListsWS.Lists listService = new ListsWS.Lists();

[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;
}

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;
}
}


And I cam calling this webmethods from my clinet dev env is as follows:

NewListsWS.NewLists ExtListService = new NewListsWS.NewLists();

XmlNode DisNode = ExtListService.CreateNewDiscussionReply("Team Discussion", 9, "Testing the Discussion repl");


Please help to advise where I have done mistake.

Or please help to send the complete code for additional Discussions web service class to understand throughly.

Thanks in advance
MK

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 7/29/2009 1:30 AM Vikash Sharma
Hi Szymon Kobalczyk,
you are the man. really i repect your effort, have you tried to do the same thing from outlook too. as i am working on outlook reply posting to the discussion board. as you have used windows forms,
i want to male discussion board as google or yahoo group as when you reply to the group id a mail is sent to all users and it gets published in the Froum it self.

my question is how discussion board works with mails. what is the parameter it takes from the mail so that it can distinguish between the mails of same subject. and when you reply the same mail it gets posted in as reply in the discussion board .

Thanks in advance

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 9/10/2009 10:23 AM Ram Sudama
This actually can be done through SharePoint Web Services without custom services on the server using the AddDiscussionListItem method on the Lists web service. I have discovered several tricks for using this method. First of all, the message has to be MIME encoded, with the following header fields:
Message-id
Thread-index
Subject
Mime-version
Content-type

This MIME text needs to be converted into an array of bytes to call Lists.AddDiscussionBoardItem. The key to creating a reply is in the Thread-index message header. If the call is made a second time with the same thread index, the second call will create a reply to the topic created by the first call.

It took me forever to figure this out. Doesn't anyone else out there use SharePoint Web Services?

# re: How to access SharePoint's Discussion Board using Web Services. Part 2. 9/10/2009 9:17 PM Mark Klinchin
Hi Ram, thank you very much for this encouraging message. I tried Lists.AddDiscussionBoardItem with MIME packed message with headers. It creates the first message in the discussion perfectly. But any time I use Thread-Index header, it fails with the exception "Invalid length for a Base-64 char array". The message seems reasonable but the same call without Thread-Index creates a new discussion thread pretty well. Did you have this error while you were trying this method? Maybe the list should be configured in some special way? How to avoid it to create a reply? Thank you very much, Mark

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: