Sunday, April 22, 2012

SharePoint Client: How to get item attachments

SharePoint item attachments are an easy way to handle files associated with list items. In this post I will demonstrate how to get to the attachments through client-side code.

The SharePoint Client Object Model (SPCOM) allows us to work with ShaePoint from the client side. SPCOM is basically a web service wrapper, exposing much of the existing web services functionality through nice classes. A few features are unfortunately either missing or just inconvenient through the SPCOM, in which case we need to revert to the old web services way. List item attachments is such a case.

To get a list of attachments for a given list item in SharePoint from the client-side we have two options:
  1. Use the SharePoint Client Object model
  2. Use the built-in web services directly

Option #1: Using the SharePoint Client Object Model

Attachments are basically files that reside in a folder in a specific location:
<site URL>/<list root folder>/Attachments/<item id>
To get to there through the clint object model we need to start at the web level, then get a reference to the list, then the root folder, then the attachments folder, then the item folder, and finally we can get to the files. The code to achieve this is:
public IEnumerable<string> GetAttachemntUrls(
   SP.ClientContext ctx,
   Guid listId,
    int itemId)
{
   List<string> urls = new List<string>();
   ctx.Load(ctx.Web, w => w.Lists);
   SP.List list = ctx.Web.Lists.GetById(listId);
   ctx.Load(list, l => l.RootFolder);
   SP.Folder rootFolder = list.RootFolder;
   ctx.Load(rootFolder, rf => rf.Folders);
   SP.FolderCollection folders = rootFolder.Folders;
   ctx.Load(folders);
   ctx.ExecuteQuery();
   foreach (SP.Folder folder in folders)
   {
     if (folder.Name == "Attachments")
     {
       SP.FolderCollection attachmentFolders = folder.Folders;
       ctx.Load(attachmentFolders);
       ctx.ExecuteQuery();
       foreach (SP.Folder itemFolder in attachmentFolders)
       {
         if (itemFolder.Name == itemId.ToString())
         {
           SP.FileCollection files = itemFolder.Files;
           ctx.Load(files);
           ctx.ExecuteQuery();
           foreach (SP.File file in files)
           {
             urls.Add(file.ServerRelativeUrl);
           }
           break;
         }
       }
       break;
     }
   }
   return urls;
}
You now have a list of server-relative attachment URLs.
In the code above I have added a reference to the client library like this:
using SP = Microsoft.SharePoint.Client;

Option #2: Using the built-in web services directly

First step is to ad a reference to the Lists web service. This service is located at <siteUrl>/_vti_bin/Lists.asmx. I have called the reference ListsService.
AddListsWebService
Tip: If you cannot add a web reference in Visual Studio, start by adding a service reference, then in the wizard click ‘Advanced’, and then ‘Web service’.
This web service includes a method to get the attachments for a specific list item. Use it like this:
public IEnumerable<string> GetAttachmentUrls(
   string siteUrl,
    ICredentials credentials,
    Guid listId,
    int itemId)
{
   List<string> urls = new List<string>();
   Lists service = new Lists();
   service.Url = siteUrl.TrimEnd('/') + "/_vti_bin/Lists.asmx";
   service.Credentials = credentials;
   XmlNode node = service.GetAttachmentCollection(
       listId.ToString("B"),
       itemId.ToString());
   foreach (XmlNode childNode in node.ChildNodes)
   {
     urls.Add(childNode.InnerText);
   }
   return urls;
}
The URLs you get by this method are absolute URLs, not server-relative URLs. Note that if you already have a ClientContext object, you can use this to get the service URL and credentials, thus making the signature of the two methods identical.
To make it work add these references:
using SPCodeCollection.Client.ListsService; *
using System.Xml;
using System.Net;
* = namespace of your web service reference.

Summing it up, and then some

We can get the item attachments solely by using the SharePoint Client Object Model. This requires several web service calls (through the ClientContext.ExecuteQuery method). If performance is of essence, a better method may be to call the Lists.GetAttachmentCollection method.
Apart from the performance, the difference between the two methods is that the client object model way provides you with server-relative URLs that you can use diectly to download the files with through the client object model. The web service method gives you absolute urls instead. To convert the absolute URLs to server-relative URLs you could do the following:
string serverRelativeUrl = url.Substring(url.IndexOf(ctx.Web.ServerRelativeUrl));
where ctx is our client context. This if course requires that the web relative URL has been loaded into context which, in the worst scenario, requires an extra web service call.

3 comments:

  1. The below link will clearly explain "how to read list item attachments using client object model
    http://vangalvenkat.blogspot.com/2011/08/sharepoint-2010-getting-list-item.html

    ReplyDelete
  2. Use fromContext.Load(fromList, l => l.RootFolder.Folders.Where(f => f.Name == "Attachments"));
    for better performance

    see

    Download List Item Attachment and Upload it To Document Library
    (with SharePoint Server Object Model and Client Object Model)

    http://ethan-deng.blogspot.com/2013/05/download-item-attachment-and-upload-it.html

    ReplyDelete
  3. I am getting an "The type or namespace name 'DownloadListItems' could not be found (are you missing a using directive or an assembly reference?)" error. To see more info please visit essayswriters.org/buy.
    I am trying this example from a machine which does not have Sharepoint 2010 installed on it, but trying to download all attachments from a list where sharepoint is installed on another server.

    ReplyDelete