Windows SharePoint Services 3.0 (WSSv3) and Microsoft Office SharePoint Server 2007 (MOSS2007) now support the ability to create folders within lists. This is quite frankly one of the coolest features in this release of the products (well, aside from all the OTHER coolest featuresJ), but the point is, it also adds a layer of complexity to how you enumerate lists now if you want to traverse a List folder hierarchy the same way you might want to enumerate a Document Library folder hierarchy.
This is something that was giving me a real headache trying to figure out how to do all day long. (Strangely, the solution I came up with I thought about a couple of days ago, I just didn’t want to have to do it this way, nor did I think I would “Have” to do it this way). I searched around, referenced the SDK, searched around more, but still came up blank. After collaborating with a lot of my peers and friends (And for those of you I pinged on this, thanks for your suggestions), I found that the suggestions they were sending me, were paths I already took and found they would not work J But, just after I harassed them, I realized what the solution was J
What I’m going to do first, is provide you a bit of background on what I attempted, and show you why those solutions don’t workJ. Then show you the solution that will work against both Lists and Document Libraries, or any list in generalJ.
Traversing a Document Library (In WSSv2 or WSSv3) was a simple process. Consider the following code if you were enumerating the document libraries using the lists collection of a SPWeb
// Within some method somewhere
foreach(SPList list in web.Lists)
{
if(list.BaseType == SPBaseType.DocumentLibrary)
{
//Traverse the folder
TraverseFolder(list.RootFolder);
}
}
TraverseFolder might be implemented something like this:
public
long TraverseFolder(SPFolder folder)
{
// Do something interesting on the folder
// Perhaps sum up the file sizes
long lFolderSize = 0;
try
{
foreach (SPFile file in folder.Files)
lFolderSize += file.Length;
if (bRecurseFolders)
{
foreach (SPFolder subfolder in folder.SubFolders)
lFolderSize += TraverseFolder(subfolder);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return lFolderSize;
}
It would be nice if you could do the same thing with a base SPList, but you can’t J
You may be inclined to start with something like this, the same way I was inclined to do so J
foreach (SPListItem item in list.Items)
{
Console.WriteLine(“List item: “ + item.Name);
Console.WriteLine(“List item type: “ + item.ContentType.Name);
}
// Folders are not counted in list.Items, so we have to look at those seperately.
// But, the Folders collection is just another collection of SPListItems.
foreach (SPListItem item in list.Folders)
{
Console.WriteLine(“List Folder item: “ + item.Name);
Console.WriteLine(“List item type: “ + item.ContentType.Name);
TraverseListItem(item);
}
But there are a couple of problems with this approach
- The items returned from via SPListItemCollection at list.Items, returns ALL items over the whole list hierarchy
- The items returned from via the SPListItemCollection at list.Folders, returns ALL folders over the whole list hierarchy
So consider a list that was structured this way:
- Announcements
- Item 1 in the root of the Announcements list
- Item 2 in the root of the Announcements list
- Folder 1 in the root of the Announcements List
- Item 1 in Folder 1
- Item 2 in Folder 1
- Folder 2 under Folder 1
- Item 1 in Folder 2
- Item 2 in Folder 2
The enumeration of list.Items above would return the following items:
- Item 1 in the root of the Announcements list
- Item 2 in the root of the Announcements list
- Item 1 in Folder 1
- Item 2 in Folder 1
- Item 1 in Folder 2
- Item 2 in Folder 2
And the enumeration of list.Folders above would return the following items:
- Folder 1 in the root of the Announcements List
- Folder 2 under Folder 1
Let’s assume you have not discovered that yet and when you detect that the item is a folder, you try to treat it this way J
You’re TraverseListItem () call may be implemented something like this:
private
static
void TraverseListItem(SPListItem listitem)
{
SPFolder folder = listitem.Folder;
if (folder != null)
{
// This item is a folder, therefore call on contained items recursively.
Console.WriteLine(“Found a folder!!!”);
Console.WriteLine(“Folder: “ + folder.Name);
foreach (SPFile file in folder.Files)
{
// Do something
}
}
}
The problem is that even though the list item is a folder, and you get a valid SPFolder
back from listitem.Folder, but unlike a Document Library, it will have a count of zero.
But hey! There is a listitem.ListItems collection! That would work right?
private
static
void TraverseListItem(SPListItem listitem)
{
SPFolder folder = listitem.Folder;
if (folder != null)
{
// This item is a folder, therefore call on contained items recursively.
Console.WriteLine(“Found a folder!!!”);
Console.WriteLine(“Folder: “ + folder.Name);
foreach (SPListItem item in listitem.ListItems)
{
// Do something
}
}
}
No, that collection returns the base SPListItemCollection from the list itself, just like SPList.Items and this would get you into a never ending loop, as you’d just recurse the same items, over and over, and over, and over, <Hey go away you pesky wabbit!>
Ok, so maybe you rethink it all, and decide to treat the list as a SPDocumentLibrary
and do what you know works. But no, the base type of a List is GenericList, not SPDocumentLibrary, therefore something like this won’t work:
SPDocumentLibrary docLib = (SPDocumentLibrary)web.Lists["Announcements"];
So, just how the heck to you enumerate the folder items in a list when you discover that the item you’re looking at is in fact a folder?
It’s actually very easy, but I would vote it’s a horrible way to have to go about it.
Enter SPQuery:
private
static
void TraverseListFolder(SPFolder folder)
{
// Get the collection of items from this folder
SPQuery qry = new SPQuery();
qry.Folder = folder;
SPWeb web = null;
try
{
web = folder.ParentWeb;
SPListItemCollection ic = web.Lists[folder.ParentListId].GetItems(qry);
foreach (SPListItem subitem in ic)
{
Console.WriteLine(“List item: “ + subitem.Name);
Console.WriteLine(“List item type: “ + subitem.ContentType.Name);
if (subitem.Folder != null)
TraverseListFolder(subitem.Folder);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
// Cleanup that nasty web reference.
if (web != null)
web.Dispose();
}
}
Ok so cool, we now have a way to get ONLY the items in a folder within a list at any level without introducing any items that are not in scope.
So, we’ll just use this method, and to traverse the list, but hey, where do you start? Well, fortunately SPList exposes a RootFolder property that gives you a SPFolder object to start with J
So we can write our List traversal Kick off method as such:
private
static
void TraverseList(SPList list)
{
Console.WriteLine(“Traversing list: “ + list.Title);
Console.WriteLine(“Base Type: “ + list.BaseType.ToString());
TraverseListFolder(list.RootFolder);
}
Simply pass in a reference to a list, and away we go. Very compact, and it works regardless if the list being passed in is a Document Library or any kind of list.
Hope this helps!