CheckedIn / Added events on libraries

So I had to do another event receiver for a wiki pages library. There are different versions depending on the development state – they are different in that the library either has a force checkout or not. See below image and then the lower of the two red rectangles shows the setting that is either yes or no. The other one defines if a simple approval workflow is used to manage versions.
Versioning Settings

So, what am I doing in my eventreceiver? I am adding the title, if it was not set (watch out, there is a difference between the dialog for a new page via the site settings menu and the new item in the pages library. The other thing I am doing is changing the settings of XsltListViewWebParts (change the view, set the row limit to at least 100 and filter the view using the title and a column I use called ‘page’.

These are the two interesting parts of the code:

This one sets the title. That means I need a web opened as an administrator and then check to see if the title is the way I want it to be…

So I get the list and the item and then check the title field.
if the title is null, then I set it, item.Title does not have a set-method so there we go…quick question what is faster? GetItemById, or GetItemByUniqueId? I would expect the first option…I did try it once though, and the id returned 33 while the number of items was 13 (I deleted a lot of pages) and the method returned an error, so I was playing safe here.

      private void SetTitle(SPItemEventProperties properties)
        {
            SPListItem item = properties.ListItem;
            if (properties.Web != null)
            {
                SPSecurity.RunWithElevatedPrivileges(
                    delegate
                    {
                        using (SPSite site = new SPSite(properties.Web.Url))
                        using (SPWeb web = site.OpenWeb())
                        {
                            SPList list = web.Lists[properties.ListId];
                            SPListItem admItem = 
                                           list.GetItemByUniqueId(item.UniqueId);
                            if (string.IsNullOrEmpty(
                                admItem[SPBuiltInFieldId.Title] as string)
                            )
                            {
                                title = admItem.Name.Replace(".aspx", "");
                                admItem[SPBuiltInFieldId.Title] = title;
                            }
                            else 
                            {
                                title = item.Title;
                            }
                            EventFiringEnabled = false;
                            site.AllowUnsafeUpdates = true;
                            web.AllowUnsafeUpdates = true;
                            admItem.SystemUpdate(false);
                            web.AllowUnsafeUpdates = false;
                            site.AllowUnsafeUpdates = false;
                            EventFiringEnabled = true;
                        }
                    });
            }
        }

The event firing and the updating is just fine right there. Watch for the system update, which is crucial.

Problem right now is still, that the check-in will overwrite the modified-by user that did the check-in that triggered the event, that I will solve with <a href="this.

     private void CheckView(SPWeb web, XsltListViewWebPart wp)
        {
            Guid oGuid = new Guid(wp.ViewGuid);
            SPList list = web.Lists[wp.ListId];
            SPView oWebPartView = list.Views[oGuid];
            int limit = 100;
            bool changed = false;
            if (oWebPartView.RowLimit < limit)
            {
                oWebPartView.RowLimit = 
                        Math.Max(limit, oWebPartView.RowLimit);
                changed = true;
            }
            string query = "" + title + "";
            if(!query.Equals(oWebPartView.Query))
            {
                oWebPartView.Query = query;
                changed = true;        
            }
            if(changed)
                oWebPartView.Update();
        }

In this method I simply take the webpart, which was already casted, then I pick up the Guid and get the view from the list. Initially I thought: what if somebody has an XsltListViewWebPart referring to a list in a sub-web…well that’s not possible – I checked – at least not ootb.

So now that I developed these two functions for the check-in event I thought: what if there is no force-checkout? Well that I still need to implement, adding an added-event and having it work only if there is a valid item to work on, because the properties.ListItem member does not exist after the added event when force-checkout is in-place.
So you got to watch that!

        private SPLimitedWebPartManager GetManagerFromItem(SPListItem item)
        {
            if (item == null || item.File == null) return null;
            SPFile itemFile = item.File;
            // open file...
            // GetLimitedWebPartManager from SPFile object
            SPLimitedWebPartManager wpmgr = 
                         itemFile.Web.GetLimitedWebPartManager(
                         itemFile.ServerRelativeUrl, 
                         PersonalizationScope.Shared);
            return wpmgr;
        }
        ...
        SPLimitedWebPartCollection collection = wpmngr.WebParts;
        List consumers = new List();
        foreach (var wp in collection)
        {
          if (wp != null)
          {
            if (wp is XsltListViewWebPart)
            {
              XsltListViewWebPart view = (XsltListViewWebPart) wp;
              CheckView(web, view);
              consumers.Add(view);
            }
          }
        } 
        ...                        

So what happens here is just that I get the limited webpart manager from the spfile object’s relativeurl and then iterate over the webpartcollection getting only the xsltlistviewwebparts…this is also discussed at many other places, you can google that, if you need more in-depth information.

Advertisements

UniqueFieldReceiver using CAML

A nice example of how a small requirement can be come a big SharePoint task is the unique field problem.

The requirement would be best explained as analogy to the unique constraint on relational databases. If you want a field to be unique you need to make sure, that each element that is inserted has a different value.

I solved this by creating an event receiver for events

  • SPEventReceiverType.ItemAdding
  • SPEventReceiverType.ItemUpdating

You would expect this to be straight forward. You take the SPItemEventProperties.AfterProperties SPItemEventDataCollection (because ItemAdding and ItemUpdating are synchronous events) and check if an item exists with this property. If this is the case you terminate the insert / update action.

This is not that simple. The only properties included in the AfterProperties SPItemEventDataCollection are those that are in the add (/HttpEncodedListName/NewForm.aspx) / edit (/HttpEncodedListName/EditForm.aspx) forms. So choose the field and set the properties of the field (SPField.ShowInNewForm = true, SPField.ShowInEditForm = true) accordingly.

SPField.StaticName == “Title” is a good choice.

The next thing to consider is that you of course need an elevated web or you will only get those items that the current user can see.

...
SPSecurity.CodeToRunElevated = 
(
  delegate 
  {
    using(SPWeb web = properties.OpenWeb())
    {
      // your code here
    }
  }
);
...

You need to make a difference between inserting and updating. Inserting is a tad easier.
All you have to do is send the caml query to the database asking whether an item exists.

public static bool Exists(SPList list, 
                        string fieldName, 
                        string fieldValue, 
                        string fieldType)
{
  string queryString = 
    string.Format("<Where><Eq><FieldRef Name=\"{0}\" />" +
                  "<Value Type=\"{1}\">{2}</Value></Eq></Where>"
                   fieldName,
                   fieldType,
                   fieldValue          
    );
  SPQuery query = new SPQuery { Query = queryString };
  SPListItemCollection result = list.GetItems(query);
  if(result.Count > 0)
  {
    return true;
  }
  return false;
}

For the updating event you have a little more to consider. First of all: Did a “true” update on the field itself take place or are the two values (before / after) identical?
In this case you need to take the ID of the item into consideration. Either you check before / after images or you correct the caml query accordingly. This does not work however for the inserting action, because then the ID of course does not exist yet.

<Where><And>
  <Neq>
  <FieldRef Name="ID" /><Value Type="Counter">{3}</Value>
  </Neq>
  <Eq>
    <FieldRef Name="{0}" /><Value Type="{1}">{2}</Value>
  </Eq>
</And></Where>

{3} should be set using the id of the existing item (which to ignore).
You can also work on the SPListItemCollection, which I do not recommend for performance reasons (keep communication data (between database and front-end server) as small as possible) though.

This is essentially it. Make an exception for lookup and datetime fields when comparing before and after images.

The rest should be straight forward.

Adding your new eventreceiver to lists is done like this:

public static void Add(SPWeb web, 
                       string assemblyFullName, 
                       string classFullName)
{
  SPList list = web.Lists["MyExistingList"];
  list.EventReceivers.Add(SPEventReceiverType.ItemAdding, 
                            assemblyFullName, 
                            classFullName);
  list.EventReceivers.Add(SPEventReceiverType.ItemUpdating, 
                            assemblyFullName,
                            classFullName);
}

Synchronous and Asynchronous Eventhandling

The idea of events and eventhandling is simple. When an event occurs (a typical example in SharePoint: an item is added to a list or library) the system looks for all registered listeners waiting for this event to happen. These listeners are implemented by pieces of code (methods) that confirm to the following standards:

  • The class the method is contained in is derived from SPItemEventReceiver
  • The method name is equal to one of the entries of the enumeration SPEventReceiverType
  • The return value of the method is void
  • The signature of the method contains one parameter only of type SPItemEventProperties
  • The method has public visibility and is non-static

In conclusion, the method you write overrides an existing method from the SPItemEventReceiver class. A sample would look like this:

namespace MyEventReceivers 
{
  public class MyFirstEventReceiver : SPItemEventReceiver 
  {
     public override void ItemAdded(SPItemEventProperties properties)
     {
       // your code that executes when event occurs here.
     }
  }
}

Now that an eventreceiver has been created this needs to be compiled in an assembly and transported to the GAC using either on a testmachine a tool like gacutil (search for gacutil.exe on your system, it should be provided with .NET SDK) or directly creating a solution with either wspbuilder, visual studio extensions for SharePoint 2007 or manually writing a manifest and using the makecab command.

To inform SharePoint that it should execute your code whenever a certain event (in our case the ItemAdded event, which is an asynchronous event) occurs you need to register the receiver on the object you want to use.
ItemAdded is typically a list event, so we will register the event receiver on a list with the name MyListWithEventReceiver. You can do this either using Microsofts xml notation and deploying the solution or you can use a feature or executable (only development system) to do this.

public static void Main(string[] args)
{
  const string listName = "MyListWithEventReceiver";

  const string assemblyName = "MyAssemblyFullName";
  const string eventReceiverName = "MyFirstEventReceiver";

  using(SPWeb web = new Site("http://myServer:myPort").OpenWeb())
  {
       SPList list = web.Lists[listName];
       list.EventReceivers.
             Add(SPEventReceiverType.ItemAdded, 
                   assemblyName, 
                   eventReceiverName);
       list.Update();
  }
}

Check out this link and Dave Hunter’s Blog as well.

Important to know now is that everytime you write code to insert an item and then use a SPList.Update() call from our generic list your code will be executed. If for some reason you don’t want this, you can turn it off by using the DisableEventFiring() method as I explained here.

Of course this will also occur whenever someone adds an item to your list using the web front-end of SharePoint.

Now that the basics of SharePoint Events and EventReceivers are laid out to get to the topic at hand:
The difference between synchronous and asynchronous eventhandling. Synchronous events such as “ItemAdding”, “ItemUpdating”, “ItemDeleting” are executed logically during the event itself, asynchronous events are handled after the event has taken place.

So the “sync events” can be typically used for validation testing, the “async events” for consecutive actions, very much like workflows. “Async events” can be seen as primitive one-step workflows. Of course workflows can do a lot more, including waiting on human interaction to determine the next execution step.

Programatically, how does this effect the objects at your disposal?
The properties object contains an SPWeb object, which you can use to get all your object or identify the working web and create a new one with elevated privileges. You also have the BeforeProperties, AfterProperties and ListItem members.

The “async events” have the ListItem member, because it has been created already, the “sync events” have the before and afterProperties, which contain the values from the form, that was used to change the listItem. So it is always error-prone to change existing eventreceivers from async to sync and vice-versa.

Happy SharePointing!

The SPEventReceiverType enumeration gives an idea of all the events you can create listeners for.

Layer Architecture and DisableEventFiring

Some time ago, I had the problem that I wanted to stop an event from triggering cascadingly. That is usually not the biggest problem. The SPItemEventReceiver class contains a protected method for static void DisableEventFiring(); and static void EnableEventFiring(); that it inherits from Microsoft.SharePoint.SPEventReceiverBase. The first method prevents code from triggering further events and the second reactivates the default functionality.

Default functionality: Every Update(), Update(bool) (SystemUpdate(), SystemUpdate(bool)) method on SPWeb, SPList or SPListItem triggers all events that are registered with the according object (for ListItems this is the list they belong to (SPListItem.ParentList)).

If you have a layer architecture and you do not want to put all your code into the feature receiver methods, then you have a little problem that can be solved by created the following class:

public class DisableEventFiring: SPEventReceiverBase, IDisposable
{
  public DisableEventFiring()
  {
     DisableEventFiring();
  }
  public void Dispose()
  {
    EnableEventFiring();
    GC.SuppressFinalize(this);
  }
}

Now you can call from anywhere in your code:

void MyMethod(SPListItem itemWithTriggerEvents)
{
  using(new DisableEventFiring())
  {
    itemWithTriggerEvents.Update(); 
    // or itemWithTriggerEvents.SystemUpdate();
  }
}

Important to understand is also the disposal of the object. Using the “using”-block you can dispose the object directly and also you don’t need to worry about reactivating the event firing.

Happy SharePointing!

SPEventReceiverType.CheckingIn

I was just working with document libraries and the dreaded “CheckingIn”-Event (SPEventReceiverType.CheckingIn).

The problem you get, when you try to change the
properties.ListItem (SPItemEventProperties.SPListItem) is that when listItem.Update(); (listItem.Update(bool);) are executed an Exception “Save conflict” occurs and your listitem is not checked in at all.

There are two nice blogs with good hints to the problem (also read the comments):

For me, it is not acceptable to catch exceptions and try the same action again, so I tried the variant of

listItem.SystemUpdate(false);

This of course requires elevation of privileges, because system update is supposed to be done by the system user, so the complete code looks like this:

public override void ItemCheckingIn(SPItemEventProperties properties)
{
  SPSecurity.RunWithElevatedPrivileges (  
    delegate 
    {
      SPListItem listItem = properties.Listitem;
      // change your listItem here
      // listItem["MyTextFieldInternalName"] = "foo bar";
      listItem.SystemUpdate(false);
    });
}

This worked for me. I hope it does for you, too!