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.