In many scenarios, it is useful to have a system-generated unique identifier for all items contained within a site. For example, you may wish to create a unique identifier for all records submitted to the Records Center. Regardless of which library that the record is stored, each record contained within the site has a unique identifier. SharePoint generates an ID for each item in a list, but these ID's are repeated in each list or library and are not unique across the site. Internally SharePoint creates a unique GUID for each item, but it is not exposed to the end user, and even if it were, GUID's are not very user-friendly.
Implementation of our unique ID includes a list for maintaining the value, and an event handler for retrieving and setting the value.
In this example we use a custom list named "Counter" containing one list item. The list item contains a value in the "Title" field which is used as the unique identifier. Using a SharePoint list automatically provides us with semaphore/locking behavior when retrieving and incrementing the value from the item in the Counter list. This is useful for handling race conditions which occur if multiple threads are using and incrementing the value at the same time.
Accessing and incrementing the unique identifier value is fairly straight-forward. You obtain reference to the list and item containing the identifier value, retrieve and increment the value, then update the item with the incremented value. The code is contained within a method named GetUniqueId and is called from the ItemAdding event handler when a document is added to a library. However, if two documents are added simultaneously, a race condition for the value occurs.
To handle this, the code is placed in a try-catch block. An exception is thrown if the value is dirty i.e. it was updated by another user between the time it was retrieved and updated. If the exception occurs, another attempt is made to retrieve and increment the value. In our example, the retry occurs five times before exiting.
Private Shared Function GetUniqueId(ByVal web As SPWeb) As Integer
Dim returnValue As Integer = -1
Dim reTryCounter As Integer = 0
Dim uniqueId As Integer = -1
' Get the list and list item
Dim list As SPList = web.Lists("Counter")
Dim item As SPListItem = list.Items(0)
' Get the value for the unique id
uniqueId = Integer.Parse(item("Title").ToString())
' Increment and update the value
item("Title") = uniqueId + 1
' Set the value to return
returnValue = uniqueId
Catch ex As Exception
' Handle the exception by retrying 5 times
reTryCounter += 1
If reTryCounter <= 5 Then
' Write exception to the log
Once the value is obtained, it needs to be set on the item that is calling the event handler. In this example, all processing occurs in the synchronous ItemAdding event. After disabling event firing for the event handler, we obtain an SPWeb object and call our GetUniqueId method. It should be noted that, in this example, because we must get the SPWeb object we are adding an extra database round trip for every item added to the repository. In general this is not an ideal situation. The returned value is added to the AfterProperties collection as a name/value pair using the name of the field for the identifier. SharePoint will then automatically set the value when base.ItemAdding is called. Finally, event firing is re-enabled and base .ItemAdding is called.
Public Overrides Sub ItemAdding(ByVal properties As SPItemEventProperties)
Using web As SPWeb = properties.OpenWeb()
' Set the unique id value on the item
properties. AfterProperties("MyUniqueId ") = GetUniqueId(web)
Our example uses a text field to hold the value. Other field types may be more appropriate based on the type of value you use. To ensure that the value is not arbitrarily modified by users, you should give consideration to security on the Counter list. The Counter list can also be hidden so it does not clutter navigation. Additionally, you can update the value and put the logic to retrieve into a separate class or in a location that controls access to the identifier value.
You can associate the event handler for retrieving and setting the identifier value with specific content types, such as Item or Document, to ensure that the value is generated for any item or document you add to the site. Also, the field used to hold and display the unique value should be read-only and sealed to prevent users from deleting it.
You can use a feature to deploy this functionality. When the feature is activated, you can use a callout code to provision the "Counter" list and ensure it is populated with a single item containing the starting value. You can associate the event handler for accessing and incrementing the counter value with a specific content type or registered on one or more libraries.