Monday, December 3, 2007

Disable event firing in SharePoint when updating a list item outside of an event handler

As you probably know, when developing Event Handlers for SharePoint you have the option turning off event firing. This functionality is exposed through SPEventReceiverBase.DisableEventFiring() Method . This is very useful when you are updating the item from within the event handler and do not want an infinite loop to occur.


During my last project i discovered that you cannot disable event firing from code that is not part of an event handler, because DisableEventFiring() is an internal instance method. Still, i was determined to find a solution. So i whipped out Reflector and started investigating. Upon disassembling Microsoft.Sharepoint.dll, I discovered that the above mentioned method actually sets a static, thread-specific, property of SPEventManager class called EventFiringDisabled. SPEventManager class is responsible for instantiating event handlers and delivering events to them. Because this class is marked "internal", it is not intended for use by SharePoint API consumers . But what is a good solution without some HACKS? :) I used reflection to set EventFiringDisabled property.


Below, you will find my solution neatly wrapped in a class. Please use at your own risk.



using System;

using
System.Collections.Generic
;

using
System.Text
;

using
System.Reflection
;

using
Microsoft.SharePoint
;


/// provides access to the internal Microsoft.SharePoint.SPEventManager class by using reflection

/// sample usage:

/// SPEventManagerWrapper.DisableEventFiring();

/// SPList myList = SPContext.Current.Web.Lists["Shared Documents"];
/// myList.Items[0].Update();

/// SPEventManagerWrapper.EnableEventFiring();

public static class SPEventManagerWrapper

{

private static readonly string _className = "Microsoft.SharePoint.SPEventManager"
;

private static readonly string
_eventFiringSwitchName = "EventFiringDisabled"
;

private static
Type _eventManagerType
;




/// gets the status of event firing on the current thread

public static bool
EventFiringDisabled

{

get { return GetEventFiringSwitchValue();
}

}


private static
Type EventManagerType

{

get

{



if (_eventManagerType == null
)

GetEventManagerType()
;



return
_eventManagerType
;



}

}



/// enables event firing on the current thread

public static void
EnableEventFiring()

{

SetEventFiringSwitch(
false)
;



}



/// disables sharepoint event firing on the current thread

public static void
DisableEventFiring()

{

SetEventFiringSwitch(
true)
;



}





/// sets the event firing switch on Microsoft.SharePoint.SPEventManager class using reflection

private static void SetEventFiringSwitch(bool value
)

{

PropertyInfo pi
= EventManagerType.GetProperty(_eventFiringSwitchName, System.Reflection.BindingFlags.Static System.Reflection.BindingFlags.NonPublic)
;



pi.SetValue(null, value, null)
;



}



private static bool
GetEventFiringSwitchValue()

{

PropertyInfo pi
= EventManagerType.GetProperty(_eventFiringSwitchName, System.Reflection.BindingFlags.Static System.Reflection.BindingFlags.NonPublic)
;



object
val = pi.GetValue(null, null)
;



return
(bool)val
;



}



private static
Type GetEventManagerType()

{

_eventManagerType
= typeof(SPList).Assembly.GetType(_className, true)
;



return
_eventManagerType
;

}

}

18 comments:

Ryan Nash said...

I've tried out your solution and ran into some snags. The compiler doesn't like:

PropertyInfo pi = EventManagerType.GetProperty(_eventFiringSwitchName, System.Reflection.BindingFlags.Static System.Reflection.BindingFlags.NonPublic);

The closest call I could see is (string, BindingFlags) for parameters but I've tried using just the Static and NonPublic to no success. They don't work as separate parameters either.

From what I understand from the MSDN information the BindingFlags are a filter, if I apply just one filter the PropertyInfo variable gets a null value, so how could applying both (if you could) make any difference?

I am deploying this as a ClassLibrary deployed to the GAC, the calls work fine until the SetEventFiringSwitch method tries to set the vale, since pi = null it crashes.

Ryan Nash said...

Oh and for clarity, the way I got it to successfully compile and deploy to the GAC is by using just one of the BindingFlags parameters.

Ryan Nash said...

Well I think I got it working now, those 24 minutes really helped :P

For the property info method call I dug a bit more into what it wanted and it wants a bitmask of one or more BindingFlags, so I just bitwise ORed them together and it worked like a charm. So:

PropertyInfo pi = EventManagerType.GetProperty(_eventFiringSwitchName, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

Is the final expression I used.

Eric Fang said...

As I understand, we should only handle the events of current thread (or current SPListItem ?). But I cannot find any code related to that from SPEventManagerWrapper, will that cause any problem?

Thiwanka Manamendra said...

Thousand times thanks for your code.
Its working well.


Just Change


PropertyInfo pi = EventManagerType.GetProperty(_eventFiringSwitchName, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

John Azzolina said...

Paul,

Thank you, thank you, and thank you. This is exactly what I needed. Can't thank you enough.

MAS said...

Hello Paul,
Thanks for the post, but i think the following code code be more easier:
namespace MyNamespace{public class MyEventFiring : SPItemEventReceiver{ public MyEventFiring() { } public void MyDisableEventFiring() { this.DisableEventFiring(); } public void MyEnableEventFiring() { this.EnableEventFiring(); }}public class MyClass{ //... MyEventFiring mef = new MyEventFiring(); mef.MyDisableEventFiring(); item.Update(); //This Update can be executed withou event firings mef.MyEnableEventFiring(); //...}}

Unknown said...

Hi Paul, thank you very much for this solution. I been looking for this long time and this now works a treat.

Thanks again.

Sue H said...

Works like a charm! Thank you for the post!

Anonymous said...

Seems like it disables but is not re-enabling event firing even when calling the EnableEventFiring()... Anyone else have that?

Anonymous said...

Sorry my mistake, I didnt have the event registered on this list duh.. sorry about that, works great thank you!

Dylan Berry said...

Fantastic! Saved me from a migraine.

Anonymous said...

Einfach wunderbar, vielen Dank!!

Anonymous said...

Disabling event firing will also solve this problem:

OnWorkflowItemChanged causes OnTaskChanged to stop firing
http://social.msdn.microsoft.com/forums/en-US/sharepointworkflow/thread/39dac3b4-1179-4796-b9ee-e04438f2eb6b/

Sometimes - even when you don't have OnWorkflowItemChanged element in the workflow - firing the event will stop the workflow. We had for example a CreateTask and an OnTaskChanged activity in a workflow, and for some reason the OnTaskChanged event did not fire at all because of OnWorkflowItemChanged events.

Thank you for your post.

Adam

Anonymous said...

Thanks for posting this as it has helped me a great deal!

Anonymous said...

Beware, disabling event firing if not re-enabled will put the thread into a state that no events will fire, and it will go back into the thread pool that way. Make sure to reset the event firing in the finally block if you are using one.

Anonymous said...

Thanks very much for the post, you save me a lot of time.

Syed Shoaib Adil said...

I used RPC call/Author.dll in SharePoint to upload a large file. Now whenever my code add/update the file in SP my event handlers are getting fired. I used ur code but it didnt helped me. still event handlers are firing....
Need more help :(