I’ve just been looking at an interesting problem that a colleague has had. We’ve a customer who wants to ‘inherit’ metadata values from Folders in a SharePoint Document Library to Documents uploaded to within it. For example, if the Folder has column ‘Case ID’, they want the value of the ‘Case ID’ column of any Document within it to be automatically set to the same value. And another twist – we won’t know the columns beforehand.
The problem is, they want this value to be pre-populated before the EditForm.aspx page is displayed.
Here, for example, the Case ID is autopopulated from the parent folder. So, what’s the best way of doing that?
Well, clearly an SPItemEventReceiver class seems a good bet. We can override one of the events, and in the event get the parent folder, and map across any column values. But what event should we override?
- ItemAdding seems a good choice. This is a “Synchronous Before event that occurs when a new item is added to its containing object“. However, because this is before the item is added, there is no SPListItem object to write the values too, or to even see what fields the item has.
- Consequently, my colleague had decided to use the ItemAdded event. This is an “Asynchronous After event that occurs after a new item has been added to its containing object.” Note that is is Asynchronous. That is, it runs in a different thread, at an indeterminate time.
As has been blogged about elsewhere, this puts the EditForm.aspx page and our event handler into race conditions. The edit form contains a ‘last update’ time, to detect concurrent updates. Our event handler update the item. The possible outcomes are:
- If the event receiver wins, all is good. The item gets it’s parents values, then the edit form displays with those values.
- If the form wins, it displays without any inherited values. Then the event receiver updates the item, which means that when the users tries to save changes they’ve made in the edit form, they receive an error telling them “The file X has been modifed by Y“
So, what’s the solution? Well, although the ItemAdding event doesn’t have an SPListItem to set the values for – ‘cos it’s not been created yet – the properties of the event does have an AfterProperties collection. This is a set of values we want set in the properties of our new item when it is finally created. Note that the collection is keyed on the DisplayName, not the InternalName, at least on my system.
Now, we don’t what the Fields we’re mapping are beforehand. And as our SPListItem doesn’t exist yet, we can’t look the up – so how do we handle that? Well, I copy all the parent Folder’s properties. Fields that are the same on the child will use the value of the appropriate property – and additional properties won’t be used. For example, if our Folder has a ‘Case Owner’ and our Document does not, we’ll be setting a property called ‘Case Owner’ on the child item – but it isn’t used, and so it isn’t a problem, and we can always find and remove it on ItemUpdated.
public override void ItemAdding(SPItemEventProperties properties) { //Get the URL to the folder we're working at. using(SPWeb web = properties.OpenWeb()){ string url = properties.BeforeUrl.ToString(); url = url.Substring(0,url.LastIndexOf("/")); //Get the folder we're using. Get the ListItem too (if it has one // - root folders don't) SPFolder folder = web.GetFolder(url); SPListItem folderItem = folder.Item; //If not a root folder... if (folderItem != null) { //For each folder field, if it's inheritable, add it as a property. //This may add unnecessary properties - ones for which the child doesn't //have a field. See ItemUpdated. SPContentType folderContentType = folderItem.ContentType; foreach (SPField field in folderContentType.Fields) { if (IsInheritable(field)) { properties.AfterProperties[field.Title] = folderItem[field.Id].ToString(); } } } } //End Using }
EDIT – there is an error in this code – AfterProperties should be set as shown here, not using field.Title
Note that the IsInheritable function is ‘cos we do have some extra criteria over what fields to map (they have to be from a certain Group of Site Columns)
This is an interesting one. Thanks for sharing!
Karine
Hi,
Did you ever actually get this working? ItemAdding is fired AFTER the user hits save, not before the page is loaded… Could I have missed something?
Yes, this is in production and works. I think you may have missed something, though.
ItemAdding is fired after the user hits ‘Upload’ be before the item actually appears in SharePoint. This is before editform.aspx is shown.
ItemAdded is fired after the user’s item has been put into SharePoint, and in race with EditForm.aspx being displayed.
See http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventreceiver.itemadding.aspx
As they’ve noted there, the item’s details aren’t available in ItemAdding as the item doesn’t exist yet – and it must exist before the EditForm.aspx will be displayed.
Wondering how this would work, or if it would for a people picker field. Also, what does the IsInheritable function look like? I seem to get an error on that function and not able to figure that out. Thanks in advance.
Ah, well, the IsInheritable function is one I inherited from my colleague. For us, all it does is check if the column in a particular column group; you may well have your own logic as to what site columns should be used in this value inheritance. Heck, maybe you want ALL columns to inherit if they match! If so, just have that function always return ‘true’.
It should work with a people picker field. We’re reading the internal value as it is stored from the parent folder, and saving it to the child. I can’t see any problems with people fields over any others.
Out of curiosity, is there a reason you used BeforeUrl instead of AfterUrl?
For example, if someone uses Web Folders to move a file from one document library into this one, the BeforeUrl will be the previous location, while the AfterUrl will allow you to use the same code.
There was (not sure if there still is) a problem with AfterUrl being empty in ItemAdding.
Mentioned here: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterurl.aspx
There was hotfix for it, apparently.
I think I might have checked this and found that that was the case in our system; that the AfterUrl was always empty in ItemAdding.