Get all sub-folders from a specific SharePoint 2013 library using CSOM

sharepointsharepoint-2010sharepoint-2013

How can I get a list of all sub-folders from a specific SharePoint library using CSOM? I want to provide the same functionality as in SharePoint when the user clicks the 'Choose Folder'.

enter image description here

I found this article CAML and the Client Object Model which I thought was pretty good but I'm getting error when I'm trying the following:

using (ClientContext clientContext = new ClientContext(this.SpSite))
{
    var web = clientContext.Web;
    var list = web.Lists.GetByTitle('Contracts');

    var query = new CamlQuery();
    query.FolderServerRelativeUrl = "/Contracts";
    query.ViewXml = "<View Scope='RecursiveAll'>"
    + "<Query>"
    + "   <Where>"
    + "      <Eq><FieldRef Name='FSObjType' /><Value Type='Integer'>1</Value></Eq>"
    + "   </Where>"
    + "</Query>"
    + "</View>";

    var folderItems = list.GetItems(query);
    clientContext.Load(folderItems);
    clientContext.ExecuteQuery();

    foreach (ListItem item in folderItems)
    {
        // item[ "..." ];
    }
}

I've also tried a different CALM query but to no avail:

query.ViewXml = "<View Scope=\"RecursiveAll\"> " +
"<Query>" +
"<Where>" +
"<Eq>" +
"<FieldRef Name=\"FileDirRef\" />" +
"<Value Type=\"Text\">" + title + "</Value>" +
"</Eq>" +
"</Where>" +
"</Query>" +
"</View>";

Any help would be appreciated.

Thanks.

UPDATE-1:

I found a question How to traverse SharePoint Library in Client Object Model? asked on StackExchange that's very close to what I need but still have a couple of issues with it:

  1. It's using recursion which means if you have a lot of levels, it would end up creating numerous requests and could show to be very slow.

  2. The data returned is not ordered. Based on the snapshot I've provided, instead of returning 01, 02, 03, etc… it returns the values in a random order.

I was hoping that I would be able to make a single call and get a list of all folders and sub-folders but I guess this could also end up being time consuming.

I guess I've got a couple of choices:

  1. Stick the recursion method and figure out how to sort out the data but I still need to figure out the latter.

  2. Only request the folders at the top level and only request sub-folder on demand when folder is expanded. Ideally if using this method, it would be nice to know if a folder has any sub-folder which means also having to figure this out.

Getting there but not quite there yet.

So if anyone can provide me with additional feedback, snippets, anything, please do.

Thanks.

Best Answer

I would suggest the following adjustments for the case of loading all the folders at once:

Since CSOM API already contains the built in method for that matter, loading all the folder items could be performed like this:

var folderItems = list.GetItems(CamlQuery.CreateAllFoldersQuery());

Secondly, i would suggest to retrieve folder objects from associated list items:

var allFolders = folderItems.Select(i => i.Folder).ToList();

Example:

public static List<Folder> GetAllFolders(List list)
{
    var ctx = list.Context;
    var folderItems = list.GetItems(CamlQuery.CreateAllFoldersQuery());
    ctx.Load(folderItems, icol => icol.Include(i => i.Folder));
    ctx.ExecuteQuery();
    var allFolders = folderItems.Select(i => i.Folder).ToList();
    return allFolders;
}

Since the loading of a whole folder structure could be a bottleneck from performance perspective, to avoid that you might consider load on demand approach:

Once the form is loaded, retrieve the list of 1st level folders and populate tree view:

var list = ctx.Web.Lists.GetByTitle(libraryTitle);
var folders = FolderManager.GetFolders(list.RootFolder);

var rootNode = new TreeNode(libraryTitle);
folderSelector.Nodes.Add(rootNode);
foreach (var folder in folders)
{
      var folderNode = new TreeNode(folder.Name) {Tag = folder};
      rootNode.Nodes.Add(folderNode);
}

and then load the sub folders on demand and populate nodes once the expand event is triggered:

private void folderTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
    if (e.Node.Level > 0 ) //skip root level
    {
         var folders = FolderManager.GetFolders((Folder)e.Node.Tag);
         foreach (var folder in folders)
         {
                var folderNode = new TreeNode(folder.Name) { Tag = folder };
                e.Node.Nodes.Add(folderNode);
         }
    }
}

where

public static List<Folder> GetFolders(Folder parentFolder)
{
    var ctx = parentFolder.Context;
    var result = ctx.LoadQuery(parentFolder.Folders.Where(f => !f.ListItemAllFields.ServerObjectIsNull.Value));
    ctx.ExecuteQuery();
    return result.ToList();
}   
Related Topic