InfoPath and SharePoint auto number form ID using ItemAdded event receiver
InfoPath combined with SharePoint is a fantastic way to manage forms in an organization. One challenge though that comes up quite often is how to uniquely assign each submitted form an identifier. There are various solutions that have been blogged about for this problem including using a timestamp, or querying the list service and getting the MAX value of the ID column. All of these have one particular drawback however. The ID is assigned by the client and not the server. Because the form is responsible for the naming these solutions can all fail in high concurrency situations. You either end up with forms with nonsense names by concatenating a bunch of fields (time, date, name of user) or you use the MAX(ID) approach and risk 2 forms getting submitted with the same ID at the same time and creating a duplicate situation.
My customer recently faced this problem and asked me for a solution. They wanted a simple integer ID that was incremented whenever a form was submitted to SharePoint. Nothing fancy. At first we investigated using the list service to query for the MAX(ID) and using that number. We quickly ran into the concurrency situation though and had duplicate forms. I decided to take matters into my own hand and find an infallible solution once and for all.
The approach we took was to use an ItemAdded event receiver on the forms library. Under the hood a forms library is no different than any other SPList and the form is no different than any other SPListItem. The SPListItem object already has a property called ID which is a unique auto sequential ID. The challenge is renaming the file to match the ID and populating that in the form. I used LINQ to XML to change the contents of the submitted form and store the newly assigned ID on save.
Once you have the ID in the form you can use it to populate the form name for subsequent saves in case a user makes an update.
If you need a basic overview on how to implement and bind an event receiver here is a good tutorial: https://msdn.microsoft.com/en-us/library/gg749858(v=office.14).aspx
For a refresher on how to configure your InfoPath form to submit to SharePoint and to pull in our FormID here is a good tutorial: https://office.microsoft.com/en-001/infopath-help/submit-form-data-to-a-sharepoint-library-HA010107057.aspx
Here is the complete code for the ItemAdded event receiver.
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace EventReceivers
{
public class AutoNumberFormID : SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
try
{
//Turn off event firing so we don't end up with a conflict
base.EventFiringEnabled = false;
//Populate our current user so we can keep track of it
SPUser user = properties.Web.CurrentUser;
//Run the code elevated so we can rename our item
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(properties.Web.Site.ID))
{
using (SPWeb web = site.OpenWeb(properties.Web.ID))
{
//Grab a new copy of our list and listitem under the new security context
SPList list = web.Lists[properties.List.ID];
SPListItem item = list.Items.GetItemById(properties.ListItemId);
//Change name and title to match item ID
item.File.CheckOut();
item["Title"] = item.ID;
item["Name"] = item.ID;
//System update instead of update so we don't add a new version or update the modified dates
item.SystemUpdate(false);
item.File.CheckIn("Changed name");
//Get a fresh copy of the object to ensure we don't end up with a conflict
item = list.Items.GetItemById(properties.ListItemId);
//Read contents of XML and update the FormID field to match the ID of the document
SPFile file = item.File;
//Load the XML byte array and then convert it to a string using default encoding
string xml = Encoding.Default.GetString(file.OpenBinary());
//Parse the XML into a LINQ compatible XDocument
XDocument doc = XDocument.Parse(xml);
//We must specify the namespace that InfoPath uses for the "My" fields
XNamespace myNamespace = "https://schemas.microsoft.com/office/infopath/2003/myXSD/2013-09-18T16:52:38";
//Using LINQ to XML we query the document for all instances of a element named "FormID"
var fields = from field in doc.Root.Descendants(myNamespace + "FormID") select field;
//Loop through the results and set the value of that element based on the ID of the current item
foreach (XElement field in fields)
field.Value = item.ID.ToString();
//If we made any changes to the document we convert back to a byte array and save it into the file object
if (fields.Count() > 0)
file.SaveBinary(Encoding.Default.GetBytes(doc.ToString()));
//Check out, push to DB, check in the file
file.CheckOut();
file.Update();
file.CheckIn("Changed name and ID");
}
}
});
}
catch (Exception ex)
{
//Log an errors to the ULS
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("FBMS Event Reciever", TraceSeverity.Unexpected, EventSeverity.Error), TraceSeverity.Unexpected, String.Format("[FBMS Item Level Permissions] Unexpected exception on item added. Error: {0}", ex.Message), null);
}
finally
{
//Turn event firing back on
base.EventFiringEnabled = true;
}
}
}
}