Add a Counter to a SharePoint List Using an Event Receiver
I have often had the need to add a sequential counter to a list in lieu of the built-in list ID field. Why? Well, to begin with, the ID field isn’t very user friendly. Let’s say you have a list with 1000 items in it, and your view breaks those items down into chunks of 100 per page. Assuming that items are added and removed from the list on a regular basis, those ID’s will be all over the map. When SharePoint creates a new list item it never duplicates a previous ID but rather creates a new unique ID value. So if you delete item #24 and create a new entry it could end up having an ID of 326, or 1001, or whatever the upper bound of the list is at the time. So if you’re looking at results 100 – 150 how would you know which item is number 136? There’s just no way to tell using the ID field alone.
In addition, I find that I’m partial to retrieving list items into a datatable for binding to a data grid, drop-down list, multi-select list, and so on. Without a sequential list of ID’s it’s difficult to apply any intelligent sorting and filtering without having to resort to a bunch of inefficient workarounds. Paging also becomes an issue that is easily overcome with sequential ID’s.
So the question is, how best to achieve this? One option is a custom field type, which is the most flexible and easiest to use repeatedly but they’re also wickedly hard to code, deploy and test. I have yet to find a solution to the problem using calculated fields that involves anything less than a dozen steps and two separate lists, so that’s not really an effective option. My favorite method is to create a simple event receiver that overrides the ItemAdded event and applies a sequential ID to each item after it is inserted into the list.
Below is a C# code sample of a very basic Event Receiver to handle this task:
public class ItemCounter : SPItemEventReceiver
{
//Override the ItemAdded event intead of ItemAdding to prevent concurrency issues
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
try
{
//Stop other events from firing while this method executes
this.DisableEventFiring();
//Get the list item from the event properties
SPListItem item = properties.ListItem;
//Get the parent list from the list item
SPList list = item.ParentList;
//Create a variable to hold the new ID value
int sId = 0;
//Get a count of all list items
int iCount = list.ItemCount;
//The ID of the last item in the list is the count minus one
int iLast = iCount – 1;
if (iCount > 0)
{
//Set the sId variable to the value of the last item in the list plus one
sId = Convert.ToInt32(list.Items[iLast]["SequentialId"].ToString());
item["SequentialId"] = sId + 1;
//Update the list item to apply the new value
item.Update();
}
}
catch
{ }
finally
{
//Re-enable event firing
this.EnableEventFiring();
}
}
}
Once applied to the list, the Event Receiver code will set the SequentialId field of the new item to a value equal to the previous item’s SequentialId field plus one. This insures an uninterrupted list of sequential ID’s.
With regards to applying the Event Receiver to a list, one of the most common frustrations I hear from developers is the inability to scope the receiver to a specific list using a feature. This is a noted limitation in the feature framework and there is no alternative but to do it programmatically. If you’re not feeling up to the task, the Event Receiver Manager on CodePlex will solve the problem for you. But if you’d like to take a stab at doing it yourself, here’s some code you can add as codebehind for an ASPX page and deploy to the /_layouts directory (obviously, you’ll need to create the matching fields in the .aspx page and format it as necessary):
public partial class EventReceiverActivation : System.Web.UI.Page
{
//Declare the visual elements
protected Label lblError;
protected TextBox tbBlogUrl;
protected TextBox tbListName;
protected Button btnActivate;
protected Button btnDeactivate;
protected string sAssemblyName = "BinaryWave.ListEvents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=32c475cd525bcb35";
protected string sClassName = "BinaryWave.ListEvents.ItemCounter";
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//Wire up the event handlers for the Activate and Deactivate buttons
this.btnActivate.Click += new EventHandler(btnActivate_Click);
this.btnDeactivate.Click += new EventHandler(btnDeactivate_Click);
}
protected void btnActivate_Click(object sender, EventArgs e)
{
try
{
using (SPSite site = new SPSite(tbBlogUrl.Text))
{
using (SPWeb web = site.OpenWeb())
{
//This is a bit of a cheat – it would be far better to enumerate all the lists in the web and provide a drop-down for the user
//instead of forcing them to enter a list name manually.
SPList list = web.Lists[tbListName.Text];
//Add the new Event Receiver to the collection
list.EventReceivers.Add(SPEventReceiverType.ItemAdded, sAssemblyName, sClassName);
string sClasses = "";
sClasses += "Class " + sClassName + " has been successfuly added to " + tbListName.Text + ".<br /><br />";
sClasses += "The following Events are now associated with this list: <br />";
//Enumerate all the Event Receivers and list them at the top of the page
SPEventReceiverDefinitionCollection eventdefs = list.EventReceivers;
foreach (SPEventReceiverDefinition eventdef in eventdefs)
{
sClasses += "<li>" + eventdef.Class + "</li>";
}
lblError.Text = sClasses;
}
}
}
catch (System.Exception ex)
{
lblError.Text = ex.Message;
}
}
protected void btnDeactivate_Click(object sender, EventArgs e)
{
try
{
using (SPSite site = new SPSite(tbBlogUrl.Text))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[tbListName.Text];
//Find the receiver in the collection and remove it
SPEventReceiverDefinitionCollection eventReceivers = list.EventReceivers;
foreach (SPEventReceiverDefinition def in eventReceivers)
{
if (def.Class == sClassName)
{
def.Delete();
break;
}
}
string sClasses = "";
sClasses += "Class " + sClassName + " has been successfuly removed from " + tbListName.Text + ".<br /><br />";
sClasses += "The following Events are now associated with this list: <br />";
SPEventReceiverDefinitionCollection eventdefs = list.EventReceivers;
foreach (SPEventReceiverDefinition eventdef in eventdefs)
{
sClasses += "<li>" + eventdef.Class + "</li>";
}
lblError.Text = sClasses;
}
}
}
catch (System.Exception ex)
{
lblError.Text = ex.Message;
}
}
}
I didn’t have time to comment the code thoroughly but it’s pretty straightforward; nothing more than getting the list of receivers for the list and adding/deleting your receiver class to/from the collection, along with some success/fail messages for the user. It’s worth noting that this probably isn’t the way you would deploy this in production but it is much easier to test and debug if you are new to working with Event Receivers. A Feature Receiver (which essentially uses the same code without all the visual interface elements) deployed as part of the solution package would be more efficient; however, it has the downside of not providing the capability to remove the receiver after it has been applied.
Happy SharePointing!
Hi!
Great tip. I’ve coded something very similar myself. But I noticed in your override ItemAdded method you call the base.ItemAdded(properties) method before doing any of your custom code. Is this really necessary? I’ve written a couple of Event Receivers and I’ve always deleted the call to the parent, and now I’m wondering if I am actually messing up the list by doing so, although I have not noticed any problems with my approach…
Consider this: I have list of 100 items, I add a new item and my sequential ID is now 101. I then delete an item, so now again I am back to 100 items, now when I add a new itme I again am adding a sequential ID of 101. Now consider when I delete two more items so now I am back to 99, and then I add another which will now have a sequential ID of 100.
Maybe I am missing the point of this solution?
I really needed a way to do this.
Hi,
I tried to use this code as it is provided copy/paste and found out that when the ItemAdded method is called, new item is already added to the list! This means logic you using is not correct.
Meaning:
1. if (iCount > 0) — has to be –> if (iCount > 1)
2. next line:
list.Items[iLast][“… — has to be –> list.Items[iLast-1][”
After these changes all works fine.
Thanks a lot!
I’ve put together a blog post with some of the more esoteric details of how SharePoints Feature Receiver Events work. Thinks like what triggers each event and which account it will run under – not quite as obvious as it first sounds.
http://blog.pentalogic.net/2010/06/sharepoint-feature-receivers-events-details/
I’ve put together a blog post with some of the more esoteric details of how SharePoints Feature Receiver Events work. Thinks like what triggers each event and which account it will run under – not quite as obvious as it first sounds.
http://blog.pentalogic.net/2010/06/sharepoint-feature-receivers-events-details/
Welcome to our shop, there are many watches. The watch is top quality, get a watch now!
Hi,
this is a nice post, by the way, is it posible if this type of scenario.
I’ll create a database and table
ID(int) Serial(string) Counter(int)
1 PurchaseOrder 1
2 ExpenseReport 1
then when a new transaction is added like in the purchase order it will take the next number from that database and increment.
i need to alter the default behaviour of sharepoint attachment in such a way that when user click ‘attach file’ the “partAttachment” span get visible which asks user to upload a file.
i want to add another filed of “Title” i.e. the title of the file being uploaded, in the partAttachment span. by doing this i want that sharepoint stores filepath , as it alread does, and title of the file as well. and in the end i want this titile to be stored where sharepoint stores the file path and i should be able to retrive the paths and title when needed. can y ou plz suggest smething.