Patrick's SharePoint Blog

SharePoint's Booming world

Posts Tagged ‘Custom’

Creating a Unique Document ID provider for SharePoint 2010 based on content type

Posted by Patrick Boom on April 29, 2010

SharePoint 2010 introduces the Unique Document ID feature, that assigns an unique ID to each document uploaded in the site collection. As said, the scope is site collection, so the document cannot be found outside the site collection using this service.

Out-of-the-box, SharePoint uses its own provider to assign document ID’s. But what if you want to influence the way the unique ID’s are generated? Well, fortunately, SharePoint 2010 also offers the possibility to create your own provider. In this article though, I would like to take it one step further and make the generation of the ID’s dependant on the list item itself. I will use the content type in this example, but you can use any field of the list item you desire.

My case: I have a company that stores contracts and quotes in the same site collection. I have the following rules regarding the unique identifier of the document, also used for reference in correspondence:

  1. A contract should start with CTR
  2. An offer should start with QUO
  3. Any other should start with DOC
  4. It should be followed by universal date notation, meaning year (4), Month (2), day (2), Hour (2), Minutes (2) and Seconds (2)
  5. Then it should contain the customer number, consisting of 6 digits, obtained from the document properties. If the document does not contain a customer number field, or it is empty, we use 000000 as the customer number.

For example: CTR-20100425163145-123456

Create the custom fields and content types

Before we can create a provider based on content types and fields, we first need to define our content type and the customer number field. Right click your project, select add new item and select Content Type in the template window. Name your content type Contract and derive it from Document. Repeat these steps for the Quote content type.

Also add a new Empty Element and call it Fields. Within the Elements.xml file of the Fields element, include a field definition for the customer number field. See below xml definition:

<?xml version=1.0 encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
  
<Field
       ID={5744d18c-305e-4632-8bd1-09d134f4830d}

       Type=Text

       Name=CustomerNumber

       DisplayName=Customer Number

       Group=Boom.CustomDocumentIdProvider>
    </Field>
</Elements>

 Now that we have our field, we complete the content types to include this field. Other than the name, both content types are the same for the purpose of this blog. See below definition for the Contract content type:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<!– Parent ContentType: Document (0x0101) –>
   <ContentType
      ID=0x010100dc76f46f281449aaa449d8eb30decd03

      Name=Contract

      Group=Boom.CustomDocumentIdProvider

      Description=Contract

      Inherits=TRUE

      Version=0>
     
<FieldRefs>
        
<FieldRef
            ID={5744d18c-305e-4632-8bd1-09d134f4830d}
            Name=CustomerNumber
            DisplayName =Customer Number/>
     
</FieldRefs>
   </ContentType>
</Elements>

 And of course, the definition for the Quote content type:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<!– Parent ContentType: Document (0x0101) –>
   <ContentType
     
ID=0x010100ae26a4fc10bc4810bd234d75bb499864
     
Name=Quote
     
Group=Boom.CustomDocumentIdProvider
     
Description=Quote
     
Inherits=TRUE
      Version=0>
     
<FieldRefs>
        
<FieldRef
           
ID={5744d18c-305e-4632-8bd1-09d134f4830d}
            Name=CustomerNumber
            DisplayName =Customer Number/>
       
/FieldRefs>
   
</ContentType>
</Elements>

Now that we have our content types and fields, we can continue with the custom document id provider.

Create the custom document ID provider

 If we want to implement our provider, we need to extend the following class: Microsoft.Office.DocumentManagement.DocumentIdProvider. There are four methods we need to override to implement our provider:

  1. public override string GenerateDocumentId(SPListItem listItem) à returns the new ID
  2. public override bool DoCustomSearchBeforeDefaultSearch() à specifies whether we do a custom search first rather than using the search framework
  3. public override string[] GetDocumentUrlsById(SPSite site, string documentId) à implements our custom lookup method
  4. public override string GetSampleDocumentIdText(SPSite site) à returns an example of the ID’s that will be generated by our provider

Let us start by creating a new empty SharePoint project in Visual Studio 2010.

Once done, create a class called DocumentIdCache and include the code listed above for the cache object. Secondly, add a class called CustomIdProvider. Let the class inherit from the DocumentIdProvider in the Microsoft.Office.DocumentManagement namespace. For this, add a reference to the Microsoft.Office.DocumentManagement assembly that is located on the .NET tab.

Override the three methods and one property from the base class, so that you have the skeletons in your class as below:

namespace Boom.CustomDocumentIdProvider {
   public class CustomIdProvider : Microsoft.Office.DocumentManagement.DocumentIdProvider {
      public override string GenerateDocumentId(Microsoft.SharePoint.SPListItem listItem)
{
         throw new NotImplementedException();
     
}

     public override string[] GetDocumentUrlsById(Microsoft.SharePoint.SPSite site, string documentId) {
        throw new NotImplementedException();
    
}

     public
override string GetSampleDocumentIdText(Microsoft.SharePoint.SPSite site)
{
        throw new NotImplementedException();
     }

     public override bool DoCustomSearchBeforeDefaultSearch
{
        get {
           throw new NotImplementedException();
        }
     
}
  
}
}

We will implement the easy ones first 😉
In the DoCustomSearchBeforeDefaultSearch property, make sure in returns false. This will instruct SharePoint to use the default search method to find the document. Creating a custom loop method is beyond the scope of the blog, but if you have a better way of finding the document, you can do so in the GetDocumentUrlsById method.
In the GetSampleDocumentIdText method, make sure it returns a string that resembles the pattern you will return for your id’s, for example CTR-20100422153421-123456.
The GetDocumentUrlsById method contains our logic to find documents other than search. Because we use the standard search method, we return a new empty string.

Property and methods should now resemble something like below:

public override string GetSampleDocumentIdText(Microsoft.SharePoint.SPSite site) {
    return “CTR-20100422153421-123456”;
}

public override bool DoCustomSearchBeforeDefaultSearch {
   get {

      return false;
  
}
}

public override string[] GetDocumentUrlsById(Microsoft.SharePoint.SPSite site, string documentId) {
   return new string[0];
}

Great, halfway there J. Now we are going to look at generating the Id’s. The ID is generated in the GenerateDocumentId method. See below code snippet:


public override string GenerateDocumentId(Microsoft.SharePoint.SPListItem listItem)
{
   string customerNumber = “000000”;

   // Get current date and time in universal notation

   string dateString = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Hour + DateTime.Now.Minute + DateTime.Now.Second;

   string contentTypeString = null;

   try
{
      // Get the customer number of the document

      if (listItem.Fields.Contains(new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)))
{
         if (listItem[new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)] != null)
           
customerNumber = listItem[new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)].ToString();
     
}
      // Obtain the content type
      SPContentType documentType = listItem.ContentType;

      // determine identifier

      switch (documentType.Name) {

         case “Contract”: {
            contentTypeString = “CTR”;
            break;
         }
         case “Quote”: {
            contentTypeString = “QUO”;
            break;
         }
         default : {
            contentTypeString = “DOC”;
            break;
        
}
      }

      string idString = string.Format(“{0}-{1}-{2}”, contentTypeString, dateString, customerNumber);
      return idString;
   } catch (Exception ex) {
      return string.Format(“DOC-{0}-{1}”, dateString, customerNumber);
  
}
}

We first check whether our list item contains a field called CustomerNumber (defined in our solution). If the list item contains the field, we check whether or not a value was entered and if so, obtain the value.

We then continue to get the content type of the list item. Based on the content type’s name, we define the first part of our document id.

Finally, we glue all of it together and return the identifier.

Last thing we need to complete now is register the custom provider with our site collection. Right click the features node in the solution explorer and select Add Feature. Right click the created feature and select Add Event Receiver.
We will attach the custom provider in this receiver. Implement the FeatureActivated and FeatureDeactivating events. See below code snippet.

public
override void FeatureActivated(SPFeatureReceiverProperties properties)
{
   SPSite site = (SPSite)properties.Feature.Parent;

   DocumentId.SetProvider(site, new CustomDocumentIdProvider.CustomIdProvider ());
}

public
override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
   SPSite site = (SPSite)properties.Feature.Parent;

   DocumentId.SetDefaultProvider(site);
}

We register our provider by calling the static DocumentId.SetProvider method, passing an instance of our fresh custom Id provider. In the deactivating method, we reset the provider by using the static DocumentId.SetDefaultProvider method.

Your solution explorer should now resemble the following:

I have included the feature receiver in its own feature, to disconnect the provider registration from the type declarations. Your entire package (wsp) should look like below image:

Deploy and test the solution

 

Right click the project node in the solution explorer and select Deploy Solution. Once deployed, navigate to your site and create a document library that contains both additional content types, like below image:

Now test your provider by adding a document, contract and quote content type. Once the provider job has run, each document should have been assigned with its own identifier, like below:

Because I enabled the Document Id Service after I have created the documents, each document has the same date time stamp, because they are all processed in the same batch. But normally, this is not the case.

Final thoughts

Off course, there are some drawbacks to this implementation. For instance, if multiple normal documents were uploaded and the service was turned on at a later stage, they would have the same ID. However, the scope of this blog is just to show that you could create your own provider and that you could use properties of the document and site to generate the identifiers for the document. Most important aspect to keep in mind is that you have to ensure that the generated ID is unique within the site collection.

I have uploaded the Visual Studio 2010 solution here. Good luck with creating your own!

Posted in SharePoint 2010 | Tagged: , , , | 9 Comments »

 
%d bloggers like this: