Patrick's SharePoint Blog

SharePoint's Booming world

Archive for April, 2010

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 »

Your search cannot be completed because this site is not assigned to an indexer

Posted by Patrick Boom on April 29, 2010

I encountered this error when I set up my fresh SharePoint 2010 farm and created the site collection before completing the configuration of the search service.

I checked all my settings, but the search service was up and running and presented no errors. Still, I was not able to fire a query from code, as this exception kept popping up. After doing a search on internet, I found that the problem was not with the search service, but with the content database settings. Strange place I would say. It appears though that each content database is assigned its own indexer and because the search service was turned on after the content database had been created, no indexer was assigned to the content database.

So, to correct this problem, open Central Admin, go to Manage Content Databases, select your content database and select the appropiate indexer in the properties page. Once done, the problem was gone.

Easy enough right?

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

Using PowerShell 1.0 to change web application setting in SharePoint 2007

Posted by Patrick Boom on April 27, 2010

Sure, PowerShell is already available for a long time on Windows 2003, also in combination with SharePoint 2007. But untill this time, I could avoid the use 😉

But with PowerShell 2.0 becoming more important to SharePoint 2010, it was time to dive a little bit into this scripting language.
At my current customer, the maximum upload size for documents needed to be increased to 100 MB, from the default setting of 50 Mb. Obviously, we could do this using the Central Admin, but it becomes more of a problem when there are a lot of web applications, hence the choice for scripting. Also, a lot of other settings on different layers need to be adjusted to make this work, for example WebDAV settings in Vista and registry settings for the crawler, but this post only covers the PowerShell script to change the setting in the web application general settings.

So, below is my first PowerShell script. Note that this script is by no means the best one. It could be extended with exception handling, parameters to specify action and size, but for the purpose of this post, it is clear enough.

[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local

Write-Host("Get all web applications within each web service")
$websvcs = @($farm.Services | where -FilterScript {$_.GetType() -eq [Microsoft.SharePoint.Administration.SPWebService]})

foreach ($websvc in $websvcs) {
  foreach ($webpp in $websvc.WebApplications) {
    $webapp.MaximumFileSize = 100
    $webapp.Update()
  }
}

Not much exiting stuff going on here right? First I load the Microsoft.SharePoint.dll assembly by calling the LoadWithPartialName method of the System.Reflection.Assembly class. Because this method is static, we use the ‘::’ operator. Once loaded, we get the local farm by calling the SPFarm.Local method. Again, becuase Local is a static method, we use the ‘::’ operator.

When done, we get all SPWebService objects within the Services collection of the Farm. This line is a little less obvious. In C#, we would use the SPFarm.Services.GetValue<SPWebService>() method. In PowerShell, we filter the Services collection by using a sort of SQL like syntax. Get all services where type (GetType()) equals (-eq) Microsoft.SharePoint.Administration.SPWebService. The rest speaks for itself and looks quite a lot like C# code.

There you have it, my first PowerShell 😉 Naturally, we could create a simple command line utility that does the same using C#, but these scripts are created faster and are also easy adjustable to fit needs.

So, in short, with SharePoint 2010 embracing PowerShell, we have no choice then to venture in the world of PowerShell.

Till next time!

Posted in PowerShell, SharePoint 2007 | Tagged: , | 2 Comments »

Office 2010 and SharePoint 2010 RTM available on MSDN!

Posted by Patrick Boom on April 22, 2010

Finally, Office 2010 and SharePoint Server 2010 are available on MSDN for MSDN subscribers.

Great achievement guys! Congrats.

Pick up your copy on http://msdn.microsoft.com/subscriptions

SharePoint 2010 will be available to Volume License customers with Software Assurance on April 27th.
Customers without Software Assurance can order their copy on May 1st.
Public release will be on May 12th.

Retail availability is expected to be on June 15th.

Have fun with the best SharePoint version yet!

Posted in SharePoint 2010 | Tagged: , , , | Leave a Comment »

Migrating to SharePoint 2010 – Some thoughts

Posted by Patrick Boom on April 22, 2010

With Microsoft SharePoint 2010 approaching for official release on May 12th 2010 (earlier for MSDN, SA, VL licenses), businesses are already looking at how we can easily migrate to 2010.

So is it just click and install? Or are any special arrangements needed? Yesterday, we tested one of our custom extension packs on the new platform (RC).
There is some good news and some bad news 😉 The good news is that features contained in the WSP pack were installed without a problem. The bad news is that some of the features in the pack introduced problems when activated.

Basically, all standard features, like content types, document libraries, list definitions and site definitions worked without a problem. Also the eventhandlers attached were also correctly attached in 2010.
So far so good. Features that were dependant on the 12 hive (for whatever reason, it is obviously not good practice) have a problem as the 12 hive is now called 14 😉

The features that did not work upon install were those that were dependant or interacted with Shared Service Provider components. As the architecture behind these are complete different in SharePoint 2010, I did not expected it to work without a fight anyway 🙂 The move to Service Applications, where the individual components of the Shared Service Provider were disconnected and isolated, caused several of my features to fail, as they could not find the Shared Service Provider. Also, some of the classes used for UserProfiles are now deprecated and replaced.

Another thing I noticed was that when I created a document library from my installed doc lib definition, it would be created, but function like the 2007 version. The row selection, checkboxes and 2010 look and feel were not available, meaning that the xml definition also needs an upgrade if you fully want to utilize the 2010 functions.

In short, some actions are indeed needed, before one could easily move to 2010. For standard sites though, that do not contain any drastic customizations, it is failry easy, using the content database attach method.

Nice day everone!

Posted in SharePoint 2007, SharePoint 2010 | Tagged: , , | 2 Comments »

 
%d bloggers like this: