Tuesday, March 1, 2011

How to attach files to list item using Client Object Model

No way. It is just impossible to do that using Client Object Model.

This is my story.

The task was to copy list item with attachments to another location of separate farm. Everything was going fine. There are tons links in Google how to work with SharePoint 2010 Client Object Model. So my code was like that:


foreach (string attachment in sourceItem.Attachments)
{
string fileUrl = SPUtility.ConcatUrls(sourceItem.Attachments.UrlPrefix, attachment);
SPFile file = sourceItem.Web.GetFile(fileUrl);
Stream fs = file.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan);

string attachmentPath = string.Format("{0}/{1}/Attachments/{2}/{3}", SPUtility.ConcatUrls(targetContext.Web.ServerRelativeUrl,"Lists"), targetListName,
targetItem.Id, attachment);

Microsoft.SharePoint.Client.File.SaveBinaryDirect(targetContext, attachmentPath, fs, true);
}


Looks good, doesn't it? However when I ran it ... ups ... I got 409(Conflict) error message. After debugging and checking attachmentPath was correct (e.g. /Lists/testList/Attachments/10/testFile.doc) I googled the error and found the most possible reason was the folder not found on the target. Actually it wasn't. Folder for item id (10) in attachmentPath is never exist in this stage by design because targetItem had been just created. But SaveBinaryDirect doesn't create folder. It just save file and expects correct path provided.
I tried to create the folder with ListItemCreationInformation object but without any success. That technique is for creating ordinary folders and doesn't work for creating folders under Attachments folder. Then I found this stack overflow thread. Looks like Microsoft had confirmed it is not possible to achieve that using Client Object Model.

The only one way for now is using Lists.asmx web service.
So, I added web reference to the web service. In VS 2010 I did right click on References, then 'Add Server Reference', then clicked 'Advanced...' button on the bottom and provider web service url (any existing url e.g. http://spdev/_vti_bin/lists.asmx in my case).

This is my final code

ListsWebService.Lists objLists = new ListsWebService.Lists();
objLists.Credentials = targetContext.Credentials;
objLists.Url = SPUtility.ConcatUrls(targetWebUrl, "/_vti_bin/lists.asmx");

foreach (string attachment in sourceItem.Attachments)
{
string fileUrl = SPUtility.ConcatUrls(sourceItem.Attachments.UrlPrefix, attachment);
SPFile file = sourceItem.Web.GetFile(fileUrl);
byte[] fileContent = file.OpenBinary(SPOpenBinaryOptions.SkipVirusScan);
objLists.AddAttachment(targetListName, targetItem.Id.ToString(), file.Name, fileContent);
}


Hope it helps someone make things clear.

3 comments:

  1. What did you use for: targetContext.Credentials; ? Was this a client object model context?

    ReplyDelete