Ok, I think I got it.
Answer is the combination of CSOM and CAML query.
CAML query returns only user visible folder's without any tweaking which is exactly what I need.
This approach works on SharePoint 2010 and SharePoint 2013
Here's the code sample that recursively searches trough the SharePoint list and fills the TreeView:
public TreeViewItem getRootTree(string libraryTitle) {
Folder rootFolder = getRootFolder(libraryTitle);
TreeViewItem rootItem = new TreeViewItem();
rootItem.Header = rootFolder.Name;
rootItem.Tag = rootFolder.ServerRelativeUrl;
rootItem.FontWeight = FontWeights.Normal;
addTreeChildren(rootFolder, rootItem);
return rootItem;
}
private void addTreeChildren(Folder parentFolder, TreeViewItem parentNode) {
CamlQuery spQuery = new CamlQuery();
String xmlCamlQuery = "<View><Query><Where><Eq><FieldRef Name=\"FSObjType\" /><Value Type=\"Integer\">1</Value></Eq></Where></Query></View>";
spQuery.ViewXml = xmlCamlQuery;
spQuery.FolderServerRelativeUrl = parentFolder.ServerRelativeUrl;
ListItemCollection items = currentLibrary.GetItems(spQuery);
spClientContext.Load(items);
spClientContext.ExecuteQuery();
foreach (ListItem item in items) {
Folder folder = getListItemFolder(item);
TreeViewItem childItem = new TreeViewItem();
childItem.Header = folder.Name;
childItem.Tag = folder.ServerRelativeUrl;
childItem.FontWeight = FontWeights.Normal;
parentNode.Items.Add(childItem);
addTreeChildren(folder, childItem);
}
}
private Folder getListItemFolder(ListItem listItem) {
spClientContext.Load(listItem, li => li.ParentList.ParentWeb);
spClientContext.ExecuteQuery();
Folder parentFolder = listItem.ParentList.ParentWeb.GetFolderByServerRelativeUrl((string)listItem["FileRef"]);
listItem.Context.Load(parentFolder);
listItem.Context.ExecuteQuery();
return parentFolder;
}
This CAML query returns all first level folders:
String xmlCamlQuery = "<View><Query><Where><Eq><FieldRef Name=\"FSObjType\" /><Value Type=\"Integer\">1</Value></Eq></Where></Query></View>";
This CAML query returns all subfolders (recursive search):
String xmlCamlQuery = "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name=\"FSObjType\" /><Value Type=\"Integer\">1</Value></Eq></Where></Query></View>";
I use my own recursion to fill the library folder tree and ListItem is converted to Folder object like this:
Folder parentFolder = listItem.ParentList.ParentWeb.GetFolderByServerRelativeUrl((string)listItem["FileRef"]);
"FileRef"
field is folder's server relative URL.