Monday, October 11, 2010

SharePoint 2010: Send Notification on Item Approved/Rejected (when Content Approval Status is changed)

In SharePoint 2010 new improvements are made for better event receiving management. Few new event handlers are added for site, web, list, listitems etc. However, One thing that I think badly needed was the content approval events  for list items.

Nowadays content approval has become an integral part of content management system. Requirements has come up to do more work on content approval/reject. But unfortunately, SharePoint list/library doesn’t have events like ContentApproved, ContentRejected, ContentRequestedForReview so that user can tap the events to do their own work on content approval status changes. So it seems we need to do a lot of works manually to send notifications on content approval status changes.

 

Problem: Approving Status change events Missing

One of my client wanted to get notification on the following scenarios:

1. On Item Add/Update: If a user edit an item and item goes to pending status as the item needs approval, the approving teams need to be notified that an item is waiting for their approval.

2. On Item Approved: If the approving team approve the item,the user who added/updated the item needs to be notified.

3. On Item rejected: If the approving team reject the item, the user who added/updated the item needs to be notified with reasons why the item rejected.

But the SharePoint Object Model doesn’t have the extensibility at this point where approving status changes.

Why Approval Status change event missing?

The best solution would be if SharePoint team would provide us with out-of-box events for content approval. In that case, two events would be suffice. The events might be : ContentApprovingStatusChanging and ContentApprovingStatusChanged and the event argument’s AfterProperties and BeforeProperties values could be filled with the the old value and new value of Content Approving Status field value. However, one may argue that ItemAdded/ItemUpdate events are similar like Content Approval events. So when user add/edit an item and as part of the add/edit if approval status field get updated then which events to fire? ItemAdded/ItemUpdate or content approval events. Hmm.. maybe there’s complexities with the new content approval events and SharePoint team has not added the new content approval events.

 

Resolution: Use ItemAdded, ItemUpdating and ItemUpdated events to keep track of approval status changing

So consider now the problem we’re going to talk about. We need a notification system where we need to send notification to the approver or user (who is waiting for approval) on approval status change. We’ll develop a list item event receiver for ItemAdded and ItemUpdated events. When a new item will be added it’s easy to identify item status and if the status is pending then we can send notification to all people in the approving team. But when an Item is updated, you need to keep track of if the Approving status field value is changed, if so then u need to send notification. However, you can only get the old approval status field value in ItemUpdating event, but you don’t want to send notification in ItemUpdating. So it’s safe to send notification in ItemUdated event but in ItemUpdated event you’ll not get the old value. You can access the old value in ItemUpdating. So here’s the deal:

  • Create a new field say OldStatus in the list. This field will be used to keep track of if the approval status field value has been changed.
  • In ItemUpdating event, set the current approval status (before updating) to OldStutus field.
  • In ItemUpdated, compare the current status to OldStatus field value and if they are not same then it’s for sure that the approval status has been changed. So send notification.

 

So let’s go with the steps. First we need an List Event Receiver that will listen three events of the list: ItemAdded, ItemUpating and ItemUpdated. You need to send notification on two events: ItemAdded and ItemUpdated. However, we need to hook the event ItemUpdating to know whether the approval status is going to be changed

Create a List Event Receiver to send notification

  1. Send notification on Item Added

    On Item added event, check if the item status is pending. If so then send notification. The following code snippet may give you the gist.

    public override void ItemAdded(SPItemEventProperties properties)
    {
        const string approvalStatusFieldInternalName = "_ModerationStatus";
    
        var list = properties.List;
        var approvalStatuField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
        var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
        var approvalStatus = (approvalStatusFieldValue == null) ? string.Empty :
                                approvalStatusFieldValue.ToString();
        if (approvalStatus == "Pending")
        {
            //SendNotification()
        }
    }
  2. Keep track of the approval status field value (before updated) on Item Updating event

    I’m assuming that you have a field OldStatus where I’ll keep the approval status field value which is going to be changed. I’ll explain later in this post how to automatically add the field in list. But for now just take for granted that you have a field OldStatus in your list of type string. The following code show how to keep the approval status (before update) value in OldStatus field in ItemUpdating Event.

    public override void ItemUpdating(SPItemEventProperties properties)
    {
        const string approvalStatusFieldInternalName = "_ModerationStatus";
    
        var list = properties.List;
        var approvalStatuField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
        var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
        var approvalStatusValue = (approvalStatusFieldValue == null) ? string.Empty :
                    approvalStatusFieldValue.ToString();
    
        if (string.IsNullOrEmpty(approvalStatusValue)) return;
    
        EventFiringEnabled = false;
        properties.ListItem["OldStatus"] = approvalStatusValue;
        properties.ListItem.SystemUpdate(false);
        EventFiringEnabled = true;
    }
  3. Check the OldStatus field value and current approval status value to know if the approval status changed.

Item updated is fried once the update is done. So we’ll get the updated value of Approval Status. But fortunately, we have kept the old value of Approval Status field in OldStatus field during ItemUpdating event as shown in step 2.

public override void ItemUpdated(SPItemEventProperties properties)
{
    const string approvalStatusFieldInternalName = "_ModerationStatus";
    var list = properties.List;
    

    var approvalStatusField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
    var currentStatuFieldValue = properties.ListItem[approvalStatusField.Id];
    var currentStatus = (currentStatuFieldValue == null) ? string.Empty : 
                        currentStatuFieldValue.ToString();

    var oldStatusFieldValue = properties.ListItem["OldStatus"];
    var oldStatus = (oldStatusFieldValue == null) ? string.Empty : oldStatusFieldValue.ToString();

    if (string.IsNullOrEmpty(oldStatus) && oldStatus != currentStatus)
    {
        //SendNotification();
    }
}

 

Create a feature receiver to attached List Event Receiver and to create field OldStatus

Finally We need an feature receiver (not list event receiver) which will do two works: Attached our list event receiver to a list and create a field OldStatus in the list.

  • FeatureActivating Event

In FeatureActivating you need to check first if the event is already registered. If not registered then register the event. Also make sure the list has OldStatus field. In the code below, listNeedsToAttachedNotitificationReceivers is array of list names which needs to attach the event receivers.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
    var myAssemblyName = "MyProject.SharePoint";

    foreach (var listName in listNeedsToAttachedNotitificationReceivers)
    {
        var web = properties.Feature.Parent as SPWeb;
        var list = web.Lists[listName];
        SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection = list.EventReceivers;
        if (!IsEventReceiverAlreadyAttached(spEventReceiverDefinitionCollection, myAssemblyName))
        {
            //Attach three ItemAdded, ItemUpdating and itemUpdated event receivers
            SPEventReceiverType eventReceiverType = SPEventReceiverType.ItemAdded;
            spEventReceiverDefinitionCollection.Add(eventReceiverType, 
                Assembly.GetExecutingAssembly().FullName, 
                "MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
            eventReceiverType = SPEventReceiverType.ItemUpdated;
            spEventReceiverDefinitionCollection.Add(eventReceiverType, 
                Assembly.GetExecutingAssembly().FullName, 
                "MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
            eventReceiverType = SPEventReceiverType.ItemUpdating;
            spEventReceiverDefinitionCollection.Add(eventReceiverType, 
                Assembly.GetExecutingAssembly().FullName, 
                "MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
            list.Update();
        }
        EnusureOldStatusFieldExists(list);
    }
}

private static bool IsEventReceiverAlreadyAttached(SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection, string myAssemblyName)
{
    bool eventReceiverAttached = false;
    for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
    {
        if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
        {
            eventReceiverAttached = true;
            break;
        }
    }
    return eventReceiverAttached;
}

private static void EnusureOldStatusFieldExists(SPList list)
{
    var field = list.Fields.TryGetFieldByStaticName("OldStatus");
    if (field == null)
    {
        list.Fields.Add("OldStatus", SPFieldType.Text, false);
        list.Update();
    }
}
  • Feature Deactivating Event

In feature deactivating event, unregister the list event receivers. If you want you can delete the OldStatus field. However I have not deleted the field in the code below:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
    var myAssemblyName = "MyProject.SharePoint";


    foreach (var listName in listNeedsToAttachedNotitificationReceivers)
    {
        var receiversToRemove = new List<Guid>();
        var web = properties.Feature.Parent as SPWeb;
        var list = web.Lists[listName];
        SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection = list.EventReceivers;
        for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
        {
            if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
            {
                receiversToRemove.Add(spEventReceiverDefinitionCollection[i].Id);
            }
        }
        if (receiversToRemove.Count > 0)
        {
            foreach (var guid in receiversToRemove)
            {
                list.EventReceivers[guid].Delete();

            }
            list.Update();
        }
    }
}

How it works all together?

It’s a bit complex huh? oK, let’s me explain how it works.

  • The feature receiver needs to be activated first. The feature receiver attached the event receiver to list and create a string field OldStatus in the list.
  • Next if an item is added to the list, the listItem event gets fired and if the item status is pending (means needs approval) then send notification.
  • If an existing item is edited and saved then ItemUpdating event is fired. This is the event where the item is not yet saved. So I have put the current approval status in the OldStatus field. In ItemUpated event I have compared the OldStatus and current status field value. If the valued doesn’t match then the approval status is changed and we need to send the notification.

8 comments:

  1. who wrote this?
    http://www.turhaltemizer.com/2010/11/sharepoint-2010-send-notification-on.html

    ReplyDelete
  2. Hi guys,

    For me to implement SPListItem.ModerationInformation.Status
    instead of list.Fields.GetFieldByInternalName(approvalStatu
    sFieldInternalName)
    was a better option

    ReplyDelete
  3. Yes you are right. When I had written the post I didn't aware of the properties. But later I've come to know about the properties and didn't get time to update the post.

    ReplyDelete
  4. I wanted to send a notification when the Item is added but using a Javascript, how this will be achieved?.
    Please, check this link on MSDN:
    http://social.technet.microsoft.com/Forums/en-US/sharepoint2010programming/thread/45f07bea-2865-468c-a90c-27961cc42550/#35c09688-4ab4-4091-bbb7-eaa3a0236999

    ReplyDelete
  5. Hi,

    I want to send alert notifiation when anyone changed the permission level for anyone. Suppose i have a read only permission and anyone change my permission level to contribute in this case send alert notification to the admin of the site collection.

    It is possible OOTB or u have any other approach.

    Thanks,
    Mohammad Yusuf Hussain

    ReplyDelete
    Replies
    1. @Yusuf, do you want to do this for a list or libraries? There's no built-in support for that but if you want this for list/libraries then you can use list event receiver and check manually if the permission has been changed.

      Delete
  6. when Approval Status is Approved.... the Document moves from one document to another document


    code in event receiver.. can any help me

    ReplyDelete
  7. Hi, instead all of that you can analyze existence and value of AfterProperties["vti_doclibmodstat"]

    ReplyDelete