The Client Object Model (OM) in SharePoint 2010 is great for many things, but it seems that it is not great for adding the first attachment to a list item. The problem is that the OM is unable to create the attachments folder for a list item. When no folder exists, attempting to add an attachment will result in an error. The error that I kept receiving was “(409) Conflict”.
To get around this problem, it is necessary to use a service to create the attachments folder for you:
1.One strategy is to use Lists.asmx, as shown by Vladimir Buyevich.
2.Another strategy is to use the ListData.svc service available in SharePoint 2010.
In this blog, I want to build on the strategies that I have linked to above and show two approaches I used for working with existing attachments. I will go over checking for and overwriting attachments. For this code, I am assuming that I will always be uploading a file where the name never changes.
First Approach
The first approach that I used was the combination of the OM and Lists.asmx, with Lists.asmx creating the initial attachment folder if it did not already exist.
To start, I need to retrieve a specific list item using the OM:
using (ClientContext clientContext = new ClientContext(“Your Site”)
{
clientContext.Credentials = new NetworkCredential(“Your Credentials”);
ListItemCollection collection = null;
CamlQuery camlQuery = newCamlQuery();
List list = clientContext.Web.Lists.GetByTitle(“Test”);
camlQuery.ViewXml = @”<View><Query><Where><Eq><FieldRef Name=’TestField’/><Value Type=’Text’>” + testValue + “</Value></Eq></Where></Query></View>”;
collection = list.GetItems(camlQuery);
clientContext.Load(collection);
clientContext.ExecuteQuery();
IEnumerable<ListItem> items = collection;
ListItem item = items.First();
Now that I have my list item, I want to check to see if an attachment already exists. To do this, I used the following lines of code:
var file = clientContext.Web.GetFileByServerRelativeUrl(new Uri(“Your Site”).AbsolutePath + “Lists/Test/Attachments/” + item[“ID”].ToString() + “/test.txt”);
clientContext.ExecuteQuery();
//check to see if the file already exists
if (file.ServerObjectIsNull == null)
{
ServerObjectIsNull returns “null” or “false”. I could also use Lists.asmx to check if the attachment exists, but I found that it was marginally faster to use ClientContext.
If the file does not exist, then I will make my call to Lists.asmx and upload a blank file with the same name. My strategy here is to create the attachments folder while knowing that I intend to overwrite this blank file in my next code section.
using (ListsWebService.Lists lists = new ListsWebService.Lists())
{
lists.Credentials = clientContext.Credentials;
lists.Url = “Your Site” + “/_vti_bin/lists.asmx”;
lists.AddAttachment(“Test”, item[“ID”].ToString(), “test.txt”, new byte[1]);
}
Knowing that the attachments folder has been created for the list item, I now will upload the actual document using the OM. What I like about using the OM is that I can use FileStream. To use Lists.asmx, you can look here for converting a document to a byte array.
using (System.IO.FileStream strm = new System.IO.FileInfo(“log.txt”).Open(System.IO.FileMode.Open))
{
Microsoft.SharePoint.Client.File.SaveBinaryDirect(clientContext, newUri(“Your Site”).AbsolutePath + “Lists/Test/Attachments/” + item[“ID”].ToString() + “/test.txt”, strm, true);
}
Second Approach
The second approach that I used was a combination of the OM and ListData.svc. The reason that I had to use both is that I was unable to get my ListData.svc client to recognize that a list item had an attachment.
To start out, I need to retrieve my list item using ListData.svc:
ListData.TestDataContext context = new ListData.TestDataContext(new Uri(“Your Site” + “_vti_bin/ListData.svc”));
context.Credentials = new NetworkCredential(“Your Credentials”);
ListData.ProjectItem item = context.Project.Where(i => i.Test == testValue).FirstOrDefault();
Now that I have my list item, I will add a few lines of code to delete the existing attachment. The only way I have found to do this is using the OM.
using (ClientContext clientContext = new ClientContext(“Your Site”))
{
clientContext.Credentials = context.Credentials;
var file = clientContext.Web.GetFileByServerRelativeUrl(new Uri(“Your Site).AbsolutePath + “Lists/Test/Attachments/” + item.Id + “/test.txt”);
file.DeleteObject();
clientContext.ExecuteQuery();
}
Now that I know the item does not have an attachment with the same name, I can continue attaching my file using ListData.svc.
ListData.AttachmentsItem attachment = new ListData.AttachmentsItem { EntitySet = “Test”, ItemId = item.Id, Name = “test.txt” };
context.AddToAttachments(attachment);
using (FileStream strm = new FileStream(@”test.txt”, FileMode.Open, FileAccess.Read))
{
context.SetSaveStream(attachment, strm, false, “text/plain”, “Test|” + item.Id + “|test.txt”);
context.SaveChanges();
}
These are the two approaches that I took to adding attachments using the OM and services. If anyone knows of a way to better manipulate attachments using the ListData.svc, I would appreciate some feedback.
Thank you so much. Was stuck with this for a long time.