I’m usually disappointed when writers employ oft-overused metaphors to describe a situation. With that in mind, SharePoint 2010 is like a sea of icebergs – there is a lot going on under the surface that you may not notice until it’s too late. Unfortunately, that makes your project like the Titanic. I don’t mean that it’s largest and most luxurious application every written, but rather that you may be cruising headlong into a nasty rendezvous with an iceberg that could deal a severe blow to your project. We may never know about all of the dangers lurking out there, but today we’re going to cover at least one danger you may encounter while writing event receivers – an annoying issue with the ItemUpdating and ItemUpdated events firing twice. I should also point out that I know the difference between a metaphor and simile in case that was bothering you from the opening sentence. I am nothing if not a masterful linguist after a beer or two or more.
What is an Item Event Receiver?
Hopefully you know about item event receiver if you are having problems with them firing twice. If not, kudos to you for tackling the object model with reckless abandon. Sometimes that is the most exciting way to learn, but for those less adventurous I will briefly cover the topic here. You can think of an item event receiver like a database trigger: it has different events that fire during the course of SharePoint running an operation on a list item (or document item).
Event Name |
Synchronous |
Description |
Item Events |
||
ItemAdding |
Yes |
Runs before an item is added to the list. |
ItemAdded |
Runs after an item has been added to the list. |
|
ItemDeleting |
Yes |
Runs before an item is deleted from the list. |
ItemDeleted |
Runs after an item has been deleted from the list. |
|
ItemUpdating |
Yes |
Runs before an item is updated in the list. |
ItemUpdated |
Runs after an item has been updated in the list. |
|
Check-In Events |
||
ItemCheckingIn |
Yes |
Runs before an item is checked in. |
ItemCheckedIn |
Runs after an item has been checked in. |
|
ItemCheckingOut |
Yes |
Runs before an item is checked out. |
ItemCheckedOut |
Runs after an item has been checked out. |
|
ItemUncheckingOut |
Yes |
Runs before an item is un-checked out. |
ItemUncheckedOut |
Runs after an item has been un-checked out. |
|
File Events |
||
ItemFileConverted |
Runs before a file in a doc library is converted to a different file type. |
|
ItemFileMoving |
Yes |
Runs before a file is moved. |
ItemFileMoved |
Runs after a file has been moved. |
|
Attachment Events |
||
ItemAttachmentAdding |
Yes |
Runs before an attachment is added to an item. |
ItemAttachmentAdded |
Runs after an attachment has been added to an item. |
|
ItemAttachmentDeleting |
Yes |
Runs before an attachment is deleted from an item. |
ItemAttachmentDeleting |
Runs after an attachment has been deleted from an item. |
Item Event Receivers derive from the SPItemEventReceiver class and have a number of methods that can be overridden to respond to various events:
As you look through this list, you should notice that events have two types of endings:
- Events ending in ING:
- Examples include ItemAdding, ItemDeleting, ItemUpdating, etc.
- These events occur BEFORE the action takes place. This means that you have a chance to modify the item or cancel the operation before it occurs.
- These event handlers run synchronously – they occur in order and one must complete before the next is run.
- You may be able to modify property values in the event handler.
- Each event method has a SPItemEventProperties parameter named properties. If you wish to modify a property value on the list item during the event, the value should be updated in AfterProperties property of the properties parameter. SharePoint reads these values from the event parameter and modifies the item accordingly when the actual operation runs (e.g. the actual Add / Update operation for which the Adding / Updating event is being fired). Do NOT try to manually get the list item in code and update a property on it because the optimistic locking mechanism in SharePoint may throw an error later on when the operation associated with the event to which you are responding attempts to complete.
- Events ending in ED:
- Examples include ItemAdded, ItemDeleted, ItemUpdated, etc.
- These events occur AFTER the action takes place. This means that you can respond to the event but you cannot cancel it or modify anything about it.
- These events handlers may run synchronously or asynchronously depending on how they are configured. Asynchronous operations may occur in any order and complete in order.
- Property modification is not available in an asynchronous event handler (even if it is running synchronously).
- Although asynchronous events expose a SPItemEventProperties parameter named properties just like their synchronous counterparts, remember that the operation has already completed so you cannot modify anything in the properties parameter (well, you can, but it doesn’t do anything). Additionally, the properties parameter may not be populated with information that you would tend to expect to be present.
WARNING: One major gotcha you should know about the SPItemEventReceiver class is that while you can implement multiple list item event handlers in a single class, SharePoint instantiates a new instance of that class for each individual event it needs to handle. What this means is that you cannot store data in instance-level variables and share that data between event handlers. For example, if you define an instance level variable in the class to store data in the ItemUpdating event, then try to access that data in the ItemUpdated event, you will find that the data is not there when you go to check it in the ItemUpdated event. This is because you have two classes – one that is handling the ItemUpdating event and in which the instance level variable is set, and one that is handling the ItemUpdated event in which the instance level variable is not set.
When Do the ItemUpdating and ItemUpdated Events Fire Twice?
Simply put, the ItemUpdating and ItemUpdated fire twice when adding a document to a library that has the Require Check Out option enabled. To understand why this is happening, let’s first look at what happens when the user adds a document to the library when the Require Check Out option is disabled:
- User clicks on the Add a Document link
- SharePoint displays the Upload a Document dialog
- User uploads the Document to SharePoint
- SharePoint fires the ItemAdding Event
- SharePoint adds the document to the Library
- SharePoint fires the ItemAdded event
- SharePoint displays the file in the library
So the net result of this is that the document is uploaded and the ItemAdding and ItemAdded events have fired, which is pretty much what you would expect. Next, let’s look at what happens when the user adds a document when the Require Check Out option is enabled.
- User clicks on the Add a Document link
- SharePoint displays the Upload a Document dialog
- User uploads the Document to SharePoint
- SharePoint fires the ItemAdding Event
- SharePoint adds the document to the Library
- SharePoint fires the ItemAdded event
- SharePoint displays the property editing screen to the user
- User edits any properties they wish to change and clicks the Save button
- SharePoint fires the ItemUpdating event (first time it is fired)
- SharePoint sets any document property field entered in the property editor dialog
- SharePoint fires the ItemUpdated event (first time it is fired)
- SharePoint now begins the process of automatically checking in the document
- SharePoint fires the ItemUpdating event (second time it is fired)
- SharePoint fires the ItemCheckingIn event
- SharePoint fires the ItemUpdated event (second time it is fired)
- SharePoint fires the ItemCheckedIn event
The first time the ItemUpdating and ItemUpdated events fire it is in response to the document properties changing. The second time they fire it is in response to the document being checked in. It appears as though they are firing twice in this situation because SharePoint is updating the properties on the document and then checking it in on the same request. If you were to check the document out and edit the properties on the document, you would see the ItemUpdating and ItemUpdated events fire once. Later on, when you checked the document in, you would see those events fire again. So the double-event firing isn’t a bug, it’s just a result of the automatic check-in that occurs when you first add a document to a document library.
Note: when the property editor dialog displays, the user has the option to cancel out of the dialog. If the user opts to cancel, then only the ItemAdding and ItemAdded events will have fired and the document will be left in a checked out state.
Also note: the ItemUpdating and ItemUpdated events that fire in response to the properties being edited from the dialog will always occur, even if the user is not entering or changing any of the values.
Turning Off the Require Check Out Option
One option for fixing the double-event firing issue is to simply turn it off. You can locate the setting using the following instructions:
- Click on a list in the left navigation menu (or otherwise get into a list)
- Click the Library tab in the Ribbon
- Click Library Settings button in the Library Tab
- This will take you to the list information screen
- Click the Versioning settings link
- The Require Check Out radio button should appear in the list of settings
Programmatically Determining the Cause of ItemUpdating and ItemUpdated Firing
Turning off the Require Check Out option is a great quick fix if you don’t require the item to be checked out in order for it to be edited. But that option exists to be used, and some people really do need it. If you find yourself in this situation, then you’ll have to solve the problem in code. Fortunately, there is a relatively simple way to check whether the ItemUpdating and ItemUpdated events are firing in response to a check-in outlined in Knowledgebase Article 939307. You just have to check to see if the vti_sourcecontrolcheckedoutby property on the item was cleared:
1 2 3 4 5 6 7 8 9 10 11 12 |
public override void ItemUpdating(SPItemEventProperties properties) { if (properties.AfterProperties["vti_sourcecontrolcheckedoutby"] == null && properties.BeforeProperties["vti_sourcecontrolcheckedoutby"] != null) { //Code to run if event trigged by a check-in } else { //Code to run if event trigged by something else } } |
This code is using the BeforeProperties and AfterProperties on the properties parameter to see what the value of the vti_sourcecontrolcheckedoutby property on the item was before the update occurred, and what it will be after the update has completed. The vti_sourcecontrolcheckedoutby property identifies who the item is currently checked-out to. As such, if the item is being checked-in, the BeforeProperties will contain a value for the property and the AfterProperties will not. It’s a pretty simple fix, but we can definitely make it a bit more reusable for everyone on a development team and reduce the hassle of having to remember the specifics about how to run the check in their ItemUpdating and ItemUpdated event handlers.
Creating a Base Class for Event Receivers
Following is the code for a base class that adds a new parameter to the ItemUpdating and ItemUpdated methods that specifies whether the event was called as a result of a check-in operation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/// <summary> /// Base class for ItemEventReceivers that helps developers determine if the ItemUpdating /// and ItemUpdated events were thrown as a result of a Check-In operation. Please see /// http://support.microsoft.com/kb/939307 for more information. /// </summary> public abstract class SPItemEventReceiverBase : SPItemEventReceiver { /// <summary> /// Contains the SharePoint field name checked when determining if the item was checked-in. /// </summary> private const string SourceControlFieldName = "vti_sourcecontrolcheckedoutby"; /// <summary> /// Runs when the ItemUpdated event is raised. /// </summary> /// <param id="properties"">SharePoint event properties.</param> /// <param id="isCheckIn"">Contains a boolean value indicating whether the method is being run in response to a check-in operation.</param> public virtual void ItemUpdated(SPItemEventProperties properties, bool isCheckIn) { } /// <summary> /// Runs when the ItemUpdating event is raised. /// </summary> /// <param id="properties"">SharePoint event properties.</param> /// <param id="isCheckIn"">Contains a boolean value indicating whether the method is being run in response to a check-in operation.</param> public virtual void ItemUpdating(SPItemEventProperties properties, bool isCheckIn) { } /// <summary> /// Determines whether the ItemUpdated event has been raised as a result of a check-in /// operation and then passes that information to the overloaded ItemUpdated method. /// </summary> /// <param id="properties"">SharePoint event properties.</param> public sealed override void ItemUpdated(SPItemEventProperties properties) { if (properties.AfterProperties[SourceControlFieldName] == null && properties.BeforeProperties[SourceControlFieldName] != null) { ItemUpdated(properties, true); } else { ItemUpdated(properties, false); } base.ItemUpdated(properties); } /// <summary> /// Determines whether the ItemUpdated event has been raised as a result of a check-in /// operation and then passes that information to the overloaded ItemUpdated method. /// </summary> /// <param id="properties"">SharePoint event properties.</param> public sealed override void ItemUpdating(SPItemEventProperties properties) { if (properties.AfterProperties[SourceControlFieldName] == null && properties.BeforeProperties[SourceControlFieldName] != null) { ItemUpdating(properties, true); } else { ItemUpdating(properties, false); } base.ItemUpdating(properties); } } //class |
So, let’s talk about the code for a second. You should notice that we’ve added two new methods:
- public virtual void ItemUpdating(SPItemEventProperties properties, bool isCheckIn)
- public virtual void ItemUpdated(SPItemEventProperties properties, bool isCheckIn)
These methods are just like the ItemUpdating and ItemUpdated methods in the SPItemEventReceiver class, but they have an additional Boolean parameter named isCheckIn that indicates whether or not the event is being raised as result of a check-in operation. They are declared as virtual methods and have empty method bodies because we want developers to override them in a derived class.
Next, we have overridden the standard ItemUpdating and ItemUpdated methods defined in the SPItemEventReceiver class. In both methods we make the check to see if the vti_sourcecontrolcheckedoutby item property has been cleared out and then call our new ItemUpdating or ItemUpdated method with the appropriate Boolean value to indicate whether the event was called as a result of a check-in operation. Notice that both of these method are marked with the sealed keyword. This ensures that developers in a derived class do not accidentally override this method instead of the new one.
Using the Base Class
To use the base class in your project, just have your Item Event Receiver classes inherit from SPItemEventReceiverBase instead of SPItemEventReciver. Then override the new ItemUpdating or ItemUpdated methods and use the isCheckIn parameter to ensure you don’t run your event receiver code more than you need to run it.
1 2 3 4 5 6 7 8 9 10 11 |
public override void ItemUpdating(SPItemEventProperties properties, bool isCheckIn) { if (isCheckin) { //Code to run if event trigged by a check-in } else { //Code to run if event trigged by something else } } |
Warning: Do not call base.ItemUpdating(properties) or base.ItemUpdated(properties) from inside an overridden IteamUpdating or ItemUpdating method when using the SPItemEventReciverBase. This will cause an infinite recursion loop and instead of solving the “It’s firing twice problem” you will now have an “It’s firing 1000 times problem.”
How does your SharePoint application perform? The Beta release of ANTS Performance Profiler 8 adds support for SharePoint 2013, helping rapidly identify and understand SharePoint performance issues. Try the Beta.
Load comments