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!