Karine Bosch’s Blog

On SharePoint

Walkthrough 2 – Developing an Event Receiver for a Document Library


You can also develop event receivers that run on document libraries. The basic techniques are the same as for custom lists. In more advanced scenarios you can hook up an event receiver when files are checked in and checked out, attachments added, updated or deleted, files moved or converted.

In this walkthrough I will explain how you can write a simple event receiver for a document library. This event receiver will calculate a code for each Word 2007 document that is added or uploaded based on the selected planet and the number of documents already uploaded to the document library.

Preparation

  1. Create a document library with the name Planet Documents, based on a Word 2007 document template.
  2. Add a column with the name Code. This column will contain a calculated code and is of type Single line of text . Set the maximum number of characters to 20.
  3. Add a column with the name Planet. This column is a lookup column that points to the Planets list.

Step 1 – Create the event receiver

1. Open Visual Studio 2008 and create a class library project with the name DocumentsEventReceiver.

2. Add a reference to the Microsoft.SharePoint.dll which can be found on the .NET tab under the label Windows SharePoint Services.

3. Add the following using statement to the top of the class file:

using Microsoft.SharePoint;

4. Give your class the name DocumentItemEventReceiver and let it inherit from SPItemEventReceiver.

public class DocumentItemEventReceiver: SPItemEventReceiver
{
   // add your code here
}

5. You want to calculate the code for a document. This can only be achieved if the item is already available in the ListItem property of the properties argument. Therefore you have to add an event handler for the ItemAdded event. Add therefore the following code:

public override void  ItemAdded(SPItemEventProperties properties)
{
    base.ItemAdded(properties);
}

4. Here follows the complete code. You can find some explanation below the code fragment.

public override void  ItemAdded(SPItemEventProperties properties)
{

            // when a Word document is created from within the document library or uploaded to the document library,
            // the ItemAdded event can be triggered get the name of the planet that needs to be saved.
            int planetid = 0;
            int.TryParse(properties.AfterProperties["Planet"].ToString(), out planetid);

            // use the using syntax to avoid memory leaks caused by the SPWeb object
            using (SPWeb web = properties.OpenWeb())
            {
                try
                {
                    // get the Planet list (will be queried for existing planet name)
                    SPList planetList = web.Lists["Planets"];
                    SPListItem planetItem = planetList.GetItemById(planetid);

                    // then query the current document library for documents for this planet
                    SPList list = web.Lists[properties.ListId];

                    // build the query: by setting the sort order to descending, which retrieve the document with the highest code first

                    // (I'm aware that this is not completely correct as the alfabetical MARS-10 will be considered ordered behind MARS-9, 

                    // but it is for demo purposes only).
                    SPQuery query = new SPQuery();

                    query.Query = string.Format(
                          "<Where><BeginsWith><FieldRef Name=\"Code\" /><Value Type=\"Text\">{0}</Value></BeginsWith></Where>"
                        + "<OrderBy><FieldRef Name=\"Code\" Ascending=\"FALSE\" /></OrderBy>", planetItem["Planet"]);

                    // if you store the documents in folders, you will also have to set the ViewAttributes property of the query

                    query.ViewAttributes = "Scope = 'Recursive'";

                    // execute the query
                    string code = null;
                    SPListItemCollection items = list.GetItems(query);
                    if (items != null && items.Count > 0)
                    {
                        SPListItem item = items[0];
                        string oldcode = item["Code"].ToString();

                        // Retrieve the number after the - sign
                        int counter = 1;
                        if (oldcode.Contains("-"))
                            int.TryParse(oldcode.Substring(oldcode.IndexOf("-") + 1), out counter);

                        // calculate the new code
                        code = planetItem["Planet"].ToString().ToUpper() + "-" + (counter + 1).ToString();
                    }
                    else
                    {

                        // this is the first document for this planet
                        code = planetItem["Planet"].ToString().ToUpper() + "-1";
                    }

                    //Stop other events from firing while this method executes
                    this.DisableEventFiring();

                    // save the new value
                    properties.ListItem["Code"] = code;
                    properties.ListItem.SystemUpdate();
                }
                catch (Exception ex)
                {
                    properties.ErrorMessage = string.Format("An error occurred during execution of ItemAdding event: {0}.", ex.Message);
                    properties.Status = SPEventReceiverStatus.CancelWithError;
                    properties.Cancel = true;
                }
                finally
                {  

                    //Enable event firing again
                    this.EnableEventFiring();
                }
            }
}

The code start by encapsulating the whole in a using (SPWeb web = properties.OpenWeb()) this to dispose from the SPWeb instance and avoid memory leaks when the code finishes running. The rest of the code is wrapped in a try-catch-finally block in order to handle an eventual error correctly: generate an error message, set the status to CancelWithError and cancel the event handler.

Before the list item is updated, you have to disable the event receiver from firing by executing the this.DisableEventFiring() method. In this case it is not really necessary because there is no ItemUpdating or ItemUpdated event handler. But if these event receivers are available executing the this.DisableEventFiring() method will avoid the ItemUpdating and/or ItemUpdated event handler from being executed.

Use the SystemUpdate() method of the list item to not modify the system columns like Created and Created By. This event receiver will work when a Word 2007 document is created from within the document library and when a Word 2007 document is uploaded. After the item is updated with the calculated code you have to execute the this.EnableEventFiring() method to enable the event receivers again. It’s a good practice to place these two methods as close to the Update method as you can.

Step 2 – Create the feature

The best way to deploy an event handler is by creating a feature that can be installed and activated to be used and can be deactivated and uninstalled if not needed anymore. As in the first walkthrough, the assembly containing the event handlers must be deployed to the Global Assembly Cache. The structure of the feature is exactly the same, except from the elements.xml file.

  • The Receivers element must have the ListTemplateId set to 101 to add the event receiver to a document library
  • The type of the event handler is ItemAdded
<Elements Id="{F5610B81-A2E2-4957-ABF2-B8ECCD765AF3}" 
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="101">
    <Receiver>
      <Name>PlanetDocumentAdded</Name>
      <Type>ItemAdded</Type>
      <SequenceNumber>1</SequenceNumber>
      <Assembly>Walkthrough.DocumentsEventReceiver, Version=1.0.0.0, Culture=neutral, PublicKeyToken=841f382b41c47c60</Assembly>
      <Class>Walkthrough.EventHandlers.DocumentItemEventReceiver</Class>
    </Receiver>
  </Receivers>
</Elements>

Each time a document is created or uploaded, the event handler will execute:

PlanetDocLib

The event receiver will run for each document library in the site collection on which the features is deployed. If you want this event handler to only run on this particular document library, you could test on the list name in your code. A better way is to work with a content type that you can add to this document library, and add the event receiver to this content type.

50 Comments »

  1. Hi Karine,

    Thanks for the post.

    Can you please tell me.

    If I have a list on a site and created the event receiver for it let’s say (itemAdded). Now, will that event reciver still work if I save the site as a template.

    I mean, will it still work on the new instance of the site and list or not?

    I would appreciate your feedback.

    Comment by PrakashJJ | November 4, 2009 | Reply

  2. Hi Prakash,
    The event receiver will still be attached to the list after creation of a new site instance based on the site template you saved. If you do it on the same server, then the DLL is still in the GAC and your event handler will work. But if you use the site template on a different server it will only work after you have deployed the DLL to the GAC.
    Karine

    Comment by Karine Bosch | November 4, 2009 | Reply

  3. Many Thanks for your feedback and it has saved my day. I am currently, writing my code on the test server but when I move it to production server then I will deploy it again so that it works there as well (like you pointed out).

    I very much appreciate it.

    You are the best..!

    Comment by PrakashJJ | November 4, 2009 | Reply

  4. Thanks for the great posts! I am searching for code examples of how to bind a receiver to a document list template (instead of a single instance of a document library).

    Is that possible to do in a feature deployment code?
    Thanks

    Comment by Jonathan | December 22, 2009 | Reply

  5. Hi Jonathan,
    By specifying you indicate that you want to bind the event receiver to each list or document library of that type.
    Is this the info you are looking for? Or did you mean something else?
    Karine

    Comment by Karine Bosch | December 23, 2009 | Reply

    • I would like to bind the handler to a document template. So anytime a document library is initiated, then, the handler will run for it.

      I do not want the handler to run for all the document libraries in the site. Only the ones made from a particular (custom) document library template (one I made).

      Thanks for all the great information.

      Comment by JP | February 4, 2010 | Reply

  6. Test-1…As a test, I deploy a handler that has been tested when bound to an existing document library with a receiver.

    Test-2…Now I do a test where I dynamically make a document library and attempt to bind the handler to it (same document type as first test).

    The handler gets bound, but, does not fire during the item added event as expected (like it did in test-1). What is interesting is…if I de-activate the handler feature, then, re-activate it, the handler begins to fire as desired.

    What is missing from my dynamic binding code? So the handler can be bound and fire as expected after dynamic binding?

    Thanks for any hints.

    Comment by JP | February 4, 2010 | Reply

  7. JP,
    In your case I would create a content type around that document template and bind my event receiver to that content type. Walkthrough 3 describes how to develop an event receiver for a list content type. You can easily do the same for a document library content type.
    If you would need sample code for this, I’ll wrap it in a walkthrough this weekend.

    Comment by Karine Bosch | February 5, 2010 | Reply

  8. Karine,

    In you example your using the ItemAdded method and using properties.Cancel = true; in the exception handling.

    Does this actually work with the post effect methods, ie. Added, Deleted and Updated? As I do not receive any error messages.

    Comment by Adam Carr | May 4, 2010 | Reply

  9. Adam,
    I haven’t tried that out but I don’t think so, as these events execute asynchronously.
    Karine

    Comment by Karine Bosch | May 4, 2010 | Reply

  10. Hi Karine/

    The ItemAdded event or Workflow that updating the document’s properties and run on Document Library will have an error when you upload a new document.

    This error happens because the ItemAdded event or Workflow execute asynchronously. When the Edit Properties Form is loaded, the ItemAdded or Workflow do not finish executing, so the SPItem Context in the Edit Properties Form is difference with the SPItem Context when ItemAdded (or Workflow) finish executing.
    So it is the reason for the error.

    Comment by Pha Trung | June 9, 2010 | Reply

  11. HI,

    I tried this code. But No error message is displayed on the page. Properties.cancel = true will not work for post event. Please help to display error message as i going to delete one item.

    Comment by hardik | August 5, 2010 | Reply

  12. Hi Karine,

    Iam new to sharepoint object model and so great of you that I got pretty good understanding on event handlers. I have the same scenario in my project and sure, your article will help me a lot.All the best to your all endeavors….

    Comment by Nimisha | March 22, 2011 | Reply

  13. Hi Karine,

    I have another query.Please help me in this problem

    I have a documenrt library where infopath form XMLs are stored. Each form is of a specific content type. Now I need to create a list column in the same document library which will display the sitecolumn “WFStatus” value for the particular report. Just by setting calculated value of list column as “WFStatus”, I cant get the site column value for the particular content type. Is there any other approach to solve the issue, rather than using sharepoint object model coding ( Custom code).

    Thanks in advance

    Comment by Nimisha | March 24, 2011 | Reply

  14. Hi Nimisha,
    It’s not that I don’t want to help you out, but I don’t really understand your question. I advise you to post your question in the MSDN forums for SharePoint, there you will have a better chance to get the right answer.
    Kind regards,
    Karine Bosch

    Comment by Karine Bosch | March 24, 2011 | Reply

    • Hi Karine,
      Thanks a lot for your concern.I have already posted my query on MSDN Forum.Iam trying also.Again can I ask you a doubt, how exaclty a site column reference is maintained for different content types?Is that like we create many objects for a class ? I again say you that Iam very new to sharepoint & a fresher also.So Iam confused with new concepts. Thanks a lot for helping me (all) .

      Regards,
      Nimisha

      Comment by Nimisha | March 24, 2011 | Reply

  15. Hi Nimisha,
    You can define one site column and reuse it in one ore more content types.
    Karine

    Comment by Karine Bosch | March 24, 2011 | Reply

  16. Hi Karine,

    Its my second post to you regarding SharePoint 2010.Iam trying out various features of SP 2010. Now I have to work on Integration of Business Intelligence Centre.My query is Can we craete a report or DashBoard against a Form Library Or Document Library.

    If you an idea on that please share me.

    Thanks in advance

    Comment by Nimisha | April 7, 2011 | Reply

  17. Hi Nimisha,
    Dashboards and Business Intelligence is not directly my best topic. I think you will have more luck if you post your question to the MSDN forum.
    Kind regards,

    Comment by Karine Bosch | April 7, 2011 | Reply

    • Hi Karine,

      ok.Iam looking more on the specific topics. Definitely will make help of MSDN forums.

      Thanks for your reply..

      Regards,
      Nimisha C.

      Comment by Nimisha | April 7, 2011 | Reply

  18. Hi Karine,

    I have created a similar ItemAdded event handler to stop adding files to a document library if the file format is not allowed.

    My issue is “properties.Cancel = true” dosen’t work and no “properties.ErrorMessage” is displayed.

    Could you please explain what I may be doing wrong.

    Comment by Dhilhaam | May 6, 2011 | Reply

  19. Hi Dhilhaam,
    Are you using SP2007 or SP2010?
    Karine

    Comment by Karine Bosch | May 7, 2011 | Reply

    • Hi Karine,

      SP 2007,

      Cheers,
      Dhilhaam

      Comment by Dhilhaam | May 9, 2011 | Reply

  20. Hi Karine,

    with sharepoint 2007 event recievers Iam facing an issue. I have attached an Item Updated and Item Added event to a particular document library.And when each event occurs Iam collective the details and stores in another list in the site.But on massive deployment of items to the document library ,multiple threads are running over the code simultaneously, so when one thread checks for entry count is not correct.

    Could you please help in this scenario. I tried this.disableeventfiring(), but it is working per thread only. In sps2010, I saw one property called, eventdefinition.synchronzation. Is there anything equivalent in sharepoint 2007? Or else, how will we deal with multithreading in event recievers?

    This is a showstopper for my application now.Please reply asap.

    Thank you.

    Comment by Nimisha | August 24, 2011 | Reply

  21. Hi Nimisha, is this massive deployment of items is caused through heavy use by users? Or by a program adding list items in bulk?

    Comment by Karine Bosch | August 24, 2011 | Reply

    • Karine,

      Thanks for your reply…

      Yes another program (another tool) is deploying big number of reports to this document library..

      I just now resolved the issue, by moving all code to Item Updating and Item Adding event handlers, since it fires synchrounously..But still I am interested to know how to handle multiple threading in asynchrounous event recievers ?

      Thanks..

      Comment by Nimisha | August 24, 2011 | Reply

  22. Did you wrote the code so that multiple threads are running? In that case you could lock the piece of code where you execute the count. In that case threads need to wait until the locked code is freed again.

    Comment by Karine Bosch | August 24, 2011 | Reply

  23. Hi Karine,
    This blog was very good. I have a requirement. Whenever an item is added to the document
    library i need to set default value to a custom column and make that as read only. The value
    needs to be set and shown at the time of displaying popup to enter the column values. In
    which event i need to write the event receiver code. If you have any sample code please
    share that with me.
    Regards,
    Sasi.

    Comment by Sasi | September 22, 2011 | Reply

  24. I don’t think you can use an event receiver at this point: your item gets created when you click the save button, not when your dialog pops up. You will need to execute some code when the dialog opens. If the popup is the New Item form, than you will have to customize that.

    Comment by Karine Bosch | September 23, 2011 | Reply

  25. Thanks Karine. Using event receiver I’m setting the default value and making that field as readonly. But if i set that field as readonly Im not able see the field in the popup. Whenever I’m setting any field as readonly that field is not coming in the popup. where do i change the setting to get the readonly only field visible in the popup.

    Comment by Sasi | September 25, 2011 | Reply

    • Are you using site columns and content types? In that case try adding the following to the definition of your site column:
      ReadOnly=”TRUE” ShowInNewForm=”TRUE” ShowInEditForm=”TRUE”

      Comment by Karine Bosch | September 25, 2011 | Reply

      • Im using the below code. After set ShowInNewForm=”TRUE” ShowInEditForm=”TRUE” still im not getting the column in the popup. Actually I created an event receiver to set default value and readonly property for a column in document library. How to get the column visible after set that as readonly.
        using (SPSite oSite = new SPSite(“http://server”))
        {
        using (SPWeb oWeb = oSite.OpenWeb())
        {
        base.EventFiringEnabled = false;
        if (properties.List.RootFolder.Name.Trim() == “SampleList”)
        {

        SPDocumentLibrary oDoc = (SPDocumentLibrary)oWeb.Lists["Project Documents"];

        oDoc.Fields["Sample"].DefaultValue = “Project Server”;
        oDoc.Fields["Sample"].ReadOnlyField = true;
        oDoc.Fields["Sample"].ShowInNewForm = true;
        oDoc.Fields["Sample"].ShowInEditForm = true;
        oWeb.AllowUnsafeUpdates = true;
        oDoc.Fields["Sample"].Update();
        oWeb.AllowUnsafeUpdates = false;
        properties.ListItem.SystemUpdate(false);
        EventFiringEnabled = true;
        }
        }
        }

        Comment by Sasi | September 26, 2011

  26. I think you missunderstood. You don’t have to do this for each item in the list, you have to set the properties when you create the field in CAML.

    Comment by Karine Bosch | September 27, 2011 | Reply

    • Can you explain me how to do that?. I’m setting an default value for a document library column when an item created in the list. This will help me to acheive my task.

      Comment by Sasi | September 27, 2011 | Reply

  27. I cannot add CAML to the comments field, but you can find more info on the syntax on how to define site columns here:
    - http://karinebosch.wordpress.com/walkthroughs/creating-a-custom-list-definition-based-on-a-generic-list-using-a-custom-content-type/
    - http://msdn.microsoft.com/en-us/library/ms437580.aspx

    Comment by Karine Bosch | September 27, 2011 | Reply

  28. Karine,

    Again I need some help!..

    I have a document library in which Infopath reports are stored.An Item Updating Event is attached to this library. THis event handler picks the report details and add to another list.

    Now this document library has a column “DeadLine” of Type Date, I need to pick this value and write to the other list’s “Report Details” column of Type Text.
    For this I wrote code as :

    DateTime dtDeadLine =DateTime.Now;
    dtDeadLine = dtDeadLine.ToUniversalTime();
    if (lstItem["DeadLine"] != null)
    {
    dtDeadLine = Convert.ToDateTime(lstItem[“DeadLine”].ToString());
    dtDeadLine = dtDeadLine.ToUniversalTime();
    }
    newlstItem[“Report Details"”]=dtDeadLine.ToString()+’…………(some other datas)’;
    Now this application is deployed on two servers, one Europe and Asia. Both sites has Date Formats, “DD/MM/YY” and “MM/DD/YY”. But now on Europe server, I am getting dates of two formats so inconsistently. For Eg: 09/29/2011 12:00:00 AM, and 29/09/2011 00:00:00 for same item’s updating events.I am not able figure where I went wrong in code, and why this is happening.On my server I couldn’t reproduce the same issue.

    I know this is not that related to event recievers, but I would really like to have your openion also.

    Regards,
    Nimisha

    Comment by Nimisha | September 30, 2011 | Reply

  29. Karine,

    regarding my previous comment, let me make you clear that my two sites have two date formats. so what I mean is “Both sites has Date Formats, “DD/MM/YY” and “MM/DD/YY” respectively”.

    still DD/MM/YY sites pickes data in different format inconsistently.

    Thank you….

    Comment by Nimisha | September 30, 2011 | Reply

  30. Hi Nimisha,

    I think that the Regional Settings on both sites are different and that this causes the date formats to be different. Can you check the Regional Settings on both sites?
    At the other side, I don’t think your code is correct. SharePoint stores its dates in format yyyy-mm-ddThh:mm:ssZ. So you can better use the following
    newsListItme["Report Details"] = SPUtility.CreateISO8601DateTimeFromSystemDateTime(dtDeadLine);
    (instead of using the .ToString() method which causes always problems with dates and numbers.

    Comment by Karine Bosch | October 1, 2011 | Reply

  31. Karine,

    Thanks a lot for your reply. I will change the code and ry.
    Then for the two sites Regional settings is different. But the Issue is for DD/MM/YY format sites, Date getting is in both formats i.e) “DD/MM/YY” and “MM/DD/YY” so inconsistenly. Iam not understanding why date is coming in both formats? either one of the formats should be followed na? Anyway, I will chang the code and try…

    Thanks alot….

    Regards,
    Nimisha

    Comment by Nimisha | October 3, 2011 | Reply

  32. Karine,
    Karine,

    Regarding the last issue, I have one doubt, Please hep me..

    Will there be any issue for the behaviour of Date Type Columns in sharepoint list , if we are using a site template of different Regionoal settings to create the site and then Changing the site settings latter. Still Iam not able to figure out the real reason for the inconsistent behaviour of my list. I dettached all event handler, now tried to exceute a SPD workflow to update the other list data to figure out the root cause. But still issue is there, which means that it is not related to code.

    Please help me ..

    Thank you.

    Comment by Nimisha | October 10, 2011 | Reply

  33. Hi,
    In sharepoint 2007 Is is possible to attach two events like ItemAdded,ItemAdding to a single Custom list like Employee.
    If yes? plz help me it’s urgent?

    Comment by deepak | October 13, 2011 | Reply

  34. Yes, that is possible. The ItemAdding event runs synchronously while the ItemAdded event runs asychronously.

    Comment by Karine Bosch | October 13, 2011 | Reply

  35. Hi Karine Bosch,

    Can i customize the files stored in _Layout Folder.If yes,How cani customize these type files trough sharepoint designer.

    Comment by Rama | October 16, 2011 | Reply

  36. Hi Rama,
    It is not recommended that you modify existing SharePoint pages stored in the _layouts folders. You have to create your own application pages containing your own logic, but you can only develop them using Visual Studio, not SharePoint Designer. You can find a decent walkthrough here: http://msdn.microsoft.com/en-us/library/bb418732(v=office.12).aspx

    Comment by Karine Bosch | October 16, 2011 | Reply

  37. Hi Karine,

    What options do I have if I need to override a specific event not currently available?

    For example, I would like to know when a user accesses (i.e. only opens and read) a document
    BUT there is no event method available for this.

    How can I do this?

    Thanks,
    Eric

    Comment by Eric D | December 8, 2011 | Reply

  38. I’m afraid that you can’t do anything as these events are not available for overwriting.

    Comment by Karine Bosch | December 9, 2011 | Reply

  39. I tried procedure and code above in sharepoint2010 foundation and I amafraid to say
    int.TryParse(properties.AfterProperties["Planet"].ToString(), out planetid); always returns null as a result changes which are show in picture never happen becuase exeception is thrown!!. If anyone has managed to get this to work in sharepoint foundation kindly post/share it

    Comment by Michael | October 24, 2012 | Reply

    • Hi Michael,
      Thanks for your remark. I’ll try to free some time this weekend to try it out in SP2010.
      Karine

      Comment by Karine Bosch | October 24, 2012 | Reply

  40. Hi karine,
    Thanks for the post.

    I have a problem. Can u please help me with my problem. Problem is that whenever i activate the content organiser i am getting error while adding the new document to any document library but if i deactivate it then i can upload the documents.

    The error that is coming is :

    Error : Access Denied

    Current User

    You are currently signed in as : HDC-MSODC2\NA249854

    Sign in as a different user

    Go back to site

    If anybody know how to resolve this problem please help me with this. I tried everything but i cant able to resolve it.

    Comment by Navin Ranjan | December 5, 2012 | Reply

  41. Hi karine,

    Can you guide me Synchronized InfoPath Attachment With Document Library

    I have an InfoPath 2010 form with a Document attachment.

    if any user upload infopath form with attachment, than attachment must be added in document library,

    if any user modify or delete any attachment form infopath library than that document must be update or delete from document library.

    - if any user modify or delete any document(s) in document library then that document(s) must be update / delete in infopath form attachment also.

    Can you guide me how can I perform this using C# programmatically

    Comment by Kapil Dave | May 18, 2013 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 172 other followers

%d bloggers like this: