Unique Document ID Across the Farm with Custom Document ID Provider

The Document ID Service of SharePoint is probably the single most interesting component of the platform for everyone trying to achieve true records management with the software. This service allows for unique document IDs to be assigned to documents within a site collection. The challenge encountered by many organizations with this service however is the fact that if you want to ensure documents gets a unique ID across your entire SharePoint farm, you need to specify a unique Prefix ID for each site collection. For most organizations, I work with, this means coming up with over 1,000 unique prefixes and applying them to each site collection either using PowerShell or any other automation solution.
While this may seem like it would be a show stopper for organizations to use the service, it is important to understand that the Document ID Service is extensible, and that Microsoft is making it very easy for organizations to implement their own Custom Document ID Provider (CDIP). By creating such a CDIP, we can control the entire logic of how document IDs will be generated and assigned to our documents, and therefore implement a logic that will ensure IDs are unique across our entire SharePoint farm, without having to come up with unique prefixes for each site collection. To get started with our CDIP creation, all we need to do is create a new SharePoint Server-Side using Visual Studio.
Within our newly created project, add a new class library named whatever you want (in my case I named it ContosoCDIP). Now let us add a reference to the Microsoft.Office.DocumentManagement.dll library to our project. This DLL file is normally found under /ISAPI.


Go back to the class library we just created, and make sure your class inherits from the Microsoft.Office.DocumentManagement.DocumentIDProvider. By doing this, you should automatically get an error letting you know that in order for your class to inherit from that parent class, you need to override 3 methods and 1 property:


What we are going to do next, is declare stubs for each of these 4 missing items. You can go ahead and manually retype all the missing override methods, or you can take the easy way and let Visual Studio do it all automatically for you. Using the Application Insight icon (lightbulb) that appears when you mouse over the error, you should see the option to let Visual Studio “Implement Abstract Class”. Select that option and Visual Studio will automatically go and create the stubs for the missing methods and property for you.


We currently have a fully valid Custom Document ID Provider. If you were to compile your project at this point, everything would come back fine and there should not be any compilation errors. Off course all items that we just added will throw an error upon being call, and we will get back to this shortly, but first let us ensure our CDIP can be properly deployed. For our CDIP to be deployed to our environment, we need to create a new feature receiver. First off, we need to create a new Feature which will allow us to deploy our Custom Document ID Provider. In the Solution Explorer window, right click on the Features folder and select Add.

Change the scope of your feature to be deployable at the Site Collection level.

You can now go ahead and right click on your feature in the Solution Explorer window, and select Add Event Receiver.


This will automatically create the Event Receiver class, with all the available methods being commented out. Make sure you add a clause at the top of your Event Receiver class to import the Microsoft.Office.DocumentManagement namespace.

using Microsoft.Office.DocumentManagement;

Let us start by uncommenting the “FeatureActivated” method. Inside that method, what we need to do is globally register a DocumentID provider on the current Site Collection where the feature was just activated. In there, type in the following line of code, where CDIPDemo.ContosoCDIP is the Document ID Provider class we created previously. What we are effectively doing here is creating a new instance of our Custom Document ID Provider, and setting it as the Default Provider for the current site collection.

DocumentId.SetProvider(properties.Feature.Parent as SPSite, new CDIPDemo.ContosoCDIP());

Because we are good coders, we also need to handle the case where the feature is Deactivated to ensure we remove our Custom Document ID Provider and set it back to the Out-of-the-box one for the given Site Collection. To ensure this scenario is handled, uncomment the “FeatureDeactivating” method. What we need to now is simply call into the SetDefaultProvider method of the DocumentID class to reset the Default Document ID Provider:

DocumentId.SetDefaultProvider(properties.Feature.Parent as SPSite);

At this point in time, we have a fully functional CDIP, and we are able to deploy it to our environment. In order to make sure our provider is able to leverage the Document ID Service, we first need to activate the Document ID Service feature on our site collection.


If we were to deploy our CDIP right now, and activate its feature, we can verify that the deployment was successful by going to Site Settings > Document ID Settings


If everything worked as expected, you should see the following two messages in red at the Top of your page:
Configuration of the Document ID feature is scheduled to be completed by an automated process.
A custom Document ID provider has been configured for this Site Collection. Refer to the documentation supplied with that custom provider to learn how to configure its settings.

The first one means that the Document ID enable/disable timer job has not yet run to completely configure your new CDIP. The second message confirms that a Custom Document ID Provider has been configured for the current Site Collection, which is exactly what we were looking for.


Now that we have a confirmation that our Custom Document ID Provider was successfully deployed, we can go back into Visual Studio and start playing with the code. By default, the Document ID Service relies on the Search engine to retrieve a document based on an ID. When a Document ID is assigned to a document, a hyperlink with the ID is automatically placed in the Document ID field of your libraries. When you click on it, it sends you to http:///_layouts/15/DocIdRedir.aspx?ID=. By default, the service will query the Search engine to retrieve the location of the document that has its Document ID field set to the ID received through the query string. We can however, decide to override this logic and come up with our own logic to retrieve a document’s URL based on its ID. Unless you have a good business rationale to change this behavior, I would recommend letting it use the Search engine to do its retrieval.

However, if you do need to put in your own custom logic, you will need to put your logic in the GetDocumentUrlsById method. This method will return a string array containing the URLs (because there can still be cases where you may want more than one document to have the same id) of the documents that match the received ID. In my case, I will simply have the method return a NotImplementedException() which indicates that I have not provided any logic to this method and will throw an error. You do not have to worry about the method throwing an exception, there is a way to make sure this method is never called.

Remember that when we created our CDIP class, we had to override 3 methods and 1 property. Well, that property we had to override is what controls what “retrieval” behavior our Custom Document ID Provider should have. If this value is set to false, then our provider will always use the Out-of-the-box search engine to retrieve the document, and the GetDocumentUrlsById method will never get called. If you set it to true, then the method will be called everytime a user clicks in the Document ID hyperlink. In my case, I will leave the value as false.


The next method we need to interact with is the GetSampleDocumentIdText method which simply returns an example of what the Document ID generated by our CDIP should look like. For this example, I have decided that my Document IDs should be in the form of “”. Therefore, my method will return any valid ID, for example “Wednesday- c32a6ab7-4c97-449b-a3d8-b5a82cf9eca7”. When you activate the Document ID Service on a site collection, it makes a new Web part named “Find Document by ID” available under the Search category. If you drop that web part on a page, it will automatically call into this method to retrieve an example of what a valid document ID could be and displays it as an insight on the web part.


Last by not the least, is the GenerateDocumentID method, which is where all the magic happens. This is the method where you will be generating your unique ID. In my case, every ID generated should be unique in nature because it uses a GUID in it (that is only in theory as it is possible that by chance I will get two IDs to be the same). If you wish to come up with a simpler naming convention, but still keep the IDs unique across your entire farm, what you can do is keep a reference to the last ID that was emitted in a list or in a property bag inside a given site, and increment that number every time you release a new ID. In my case, my logic will simply be the following:

Guid guid = Guid.NewGuid();
string dayName = DateTime.Now.DayOfWeek.ToString();
return dayName + " - " + guid;

Now that all of our methods have been covered, we are ready to deploy our CDIP. Once the feature is activated, simply upload a document to any document library and take a look at the Document ID column (you will need to add it to your view). You should see the assigned ID matching the logic of your CDIP!