It’s the age old question of should you allow users to create folders in document libraries and lists when designing your Information Architecture and governance plan. I worked for an organisation that banned folders and didn’t fully explain to users how to leverage meta data, in SharePoint 2007 with no Managed Meta Data and Hierachy Navigation filters in libraries this affected adoption. As the network drivers were shutdown, users had to store documents in SharePoint so they simply opened the library in explorer view and dragged in folders; the folder restriction was simply and interface change and not event handler.
Personally I think there is a place for folders which Mikhail Dikov
covers well in a blog on
SharePoint folders need more love.
So now you’re convinced that folders are a good thing we still need to keep some control otherwise users will create 5 level deep folder structures as they did on the file system with no metadata.
The solution I’ve built here uses an event receiver to allow folders to be created at the root level of a document library or list but no deeper i.e. one folder level only.
Following Karine Bosch’s Blog on creating an event receivers
http://karinebosch.wordpress.com/walkthroughs/event-receivers-walkthrough1/, I created an ItemAdding receiver that prevents sub-folders of folders being created.
The following code snippet checks if the item that is being created is a Folder Content Type or a content type inherited from folder (itemContentType.StartsWith(spFolderContentType)), if it is a folder, then the root folder of the library is compared to the parent folder (generated by Remove statement) and if they do not match then returns the error message. Items do have a ParentFolder property but this doesn’t appear to be set until the item has been added to the library.
public
override
void ItemAdding(SPItemEventProperties properties)
{
string spFolderContentType = “0x0120”;
string itemContentType = properties.AfterProperties[“ContentTypeId”].ToString();
// Is this a folder, check if the content Type starts with the base Folder ID?
if (itemContentType.StartsWith(spFolderContentType))
{
//Get the web site for this list
using (SPWeb web = properties.OpenWeb())
{
// get the list
SPList list = web.Lists[properties.ListId];
SPFolder rootFolder = list.RootFolder;
string folderURL = properties.AfterUrl;
string folderName = properties.AfterProperties[“Title”].ToString();
string folderParentURL = folderURL.Remove(folderURL.Length – folderName.Length -1, folderName.Length +1);
// Is the new folder being created in the root of the library
if (folderParentURL != rootFolder.Url)
{
properties.ErrorMessage = “Folders can only be created in root of this library/list”;
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;
}
else
{
base.ItemAdding(properties);
}
}
}
else
{
base.ItemAdding(properties);
}
}
The following “Error” message is displayed to the user if they try to create a folder in another folder:
This isn’t a very nice message as it infers that an error occurred when actually it was more a policy was applied. You will probably need to provide some better feedback explaining that it is not an error and where to read the policy.
And then I thought I was done until I tried to test explorer view at which point I discovered that the AfterProperties are not supplied in the ItemAdding event when someone copies or creates a new item directly in explorer view and the ItemUpdating event is also called. This thread covers the same question but the proposed answer doesn’t work:
So now what? Well I said a few bad words about the inconsistencies of SharePoint and then attempted to find an answer. I decided to ignore ItemAdding as this was essentially already completed as it was now doing an update and focus on building a solution around ItemUpdating.
Within the ItemUpdating function, I could access the ListItemID so was able to retrieve this list item object and it’s properties to evaluate if it was a top level folder. Interestingly (or annoyingly), you could get the item Title or Name it just included the base content type properties but the FileLeafRef provided the value for me to use in building the parentURL string.
So ignore the code above as this is what works with a tweak to ItemAdding to check if AfterProperties exist and if not presume this was an Explorer View update and ignore the item:
public
override
void ItemAdding(SPItemEventProperties properties)
{
string spFolderContentType = “0x0120”;
bool isFolder = false;
string itemTitle = “”;
//Get the web site for this list
using (SPWeb web = properties.OpenWeb())
{
// Need to handle explorer view not containing AfterProperties
if (properties.AfterProperties[“ContentTypeId”] != null)
{
string itemContentType = properties.AfterProperties[“ContentTypeId”].ToString();
if (itemContentType.StartsWith(spFolderContentType))
{
isFolder = true;
itemTitle = properties.AfterProperties[“Title”].ToString();
}
}
// Is this a folder, check if the content Type starts with the base Folder ID?
if (isFolder)
{
// get the list
SPList list = web.Lists[properties.ListId];
SPFolder rootFolder = list.RootFolder;
string folderURL = properties.AfterUrl;
string folderName = itemTitle;
string folderParentURL = folderURL.Remove(folderURL.Length – folderName.Length – 1, folderName.Length + 1);
// Is the new folder being created in the root of the library
if (folderParentURL != rootFolder.Url)
{
properties.ErrorMessage = “Folders can only be created in root of this library/list”;
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;
}
else
{
base.ItemAdding(properties);
}
}
else
{
base.ItemAdding(properties);
}
}
}
public
override
void ItemUpdating(SPItemEventProperties properties)
{
string spFolderContentType = “0x0120”;
bool isFolder = false;
string itemTitle = “”;
SPItem CurrentListItem = null;
//Get the web site for this list
using (SPWeb web = properties.OpenWeb())
{
// Need to handle explorer view not containing AfterProperties
if (properties.AfterProperties[“ContentTypeId”] == null)
{
SPList CurrentList = web.Lists[properties.ListId];
CurrentListItem = CurrentList.GetItemById(properties.ListItemId);
string itemContentType = CurrentListItem[“ContentTypeId”].ToString();
if (itemContentType.StartsWith(spFolderContentType))
{
itemTitle = CurrentListItem[“FileLeafRef”].ToString();
isFolder = true;
}
}
// Is this a folder, check if the content Type starts with the base Folder ID?
if (isFolder)
{
// get the list
SPList list = web.Lists[properties.ListId];
SPFolder rootFolder = list.RootFolder;
string folderURL = properties.AfterUrl;
string folderName = itemTitle;
string folderParentURL = folderURL.Remove(folderURL.Length – folderName.Length – 1, folderName.Length + 1);
// Is the new folder being created in the root of the library
if (folderParentURL != rootFolder.Url)
{
properties.ErrorMessage = “Folders can only be created in root of this library/list”;
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;
CurrentListItem.Delete();
}
else
{
base.ItemUpdating(properties);
}
}
else
{
base.ItemUpdating(properties);
}
}
}
When the user tries to add a folder through the explorer view, they will get the following message after changing the folder name:
When the explorer view is refreshed, the item doesn’t exist. They don’t see the returned ErrorMessage so no idea why this happened so it might be prudent to send them an email or even better an instant message to tell them what happened.
This isn’t the most robust code and I’m sure there are other ways of comparing the parent folder strings so don’t use this in production without some further testing and due diligence.