Applies To: SharePoint 2010, .NET Framework (C#, VB.NET)
As mentioned in a previous post, I’ve recently put together a solution for automatically configuring your SharePoint servers to use the Adobe PDF icon for PDF files. You can download the solution as well as the source for free from CodePlex here: WireBear PDFdocIcon. I’m going to show some of the code as it currently exists below, but be sure to check out the CodePlex site to ensure you have the latest version.
I’ve also provided the bulk of the code and some explanation for installing/uninstalling a custom job from a SharePoint solution in my last post: Implementing a Custom SharePoint Timer Job. In this post we’ll explore what’s actually happening in the execution of the timer job.
The goal is to update the DOCICON.xml file in the 14\TEMPLATE\XML folder within the SharePoint 2010 Hive to include or remove a mapping entry for a specific file extension. Here is the entire DocIconJob class:
The Code:
Imports Microsoft.SharePoint.Administration Imports System.IO Imports Microsoft.SharePoint.Utilities Imports System.Xml Public Class DocIconJob Inherits SPServiceJobDefinition #Region "Properties" Private _dociconPath As String Public ReadOnly Property DocIconPath() As String Get If String.IsNullOrEmpty(_dociconPath) Then _dociconPath = SPUtility.GetGenericSetupPath("TEMPLATE\XML\DOCICON.XML") Return _dociconPath End Get End Property Private Const InstallingKey As String = "DocIconJob_InstallingKey" Private Property _installing() As Boolean Get If Properties.ContainsKey(InstallingKey) Then Return Convert.ToBoolean(Properties(InstallingKey)) Else Return True End If End Get Set(ByVal value As Boolean) If Properties.ContainsKey(InstallingKey) Then Properties(InstallingKey) = value.ToString Else Properties.Add(InstallingKey, value.ToString) End If End Set End Property Private Const FileExtensionKey As String = "DocIconJob_FileExtensionKey" Private Property _fileExtension() As String Get If Properties.ContainsKey(FileExtensionKey) Then Return Convert.ToString(Properties(FileExtensionKey)) Else Return String.Empty End If End Get Set(ByVal value As String) If Properties.ContainsKey(FileExtensionKey) Then Properties(FileExtensionKey) = value Else Properties.Add(FileExtensionKey, value) End If End Set End Property Private Const ImageFilenameKey As String = "DocIconJob_ImageFilenameKey" Private Property _imageFilename() As String Get If Properties.ContainsKey(ImageFilenameKey) Then Return Convert.ToString(Properties(ImageFilenameKey)) Else Return String.Empty End If End Get Set(ByVal value As String) If Properties.ContainsKey(ImageFilenameKey) Then Properties(ImageFilenameKey) = value Else Properties.Add(ImageFilenameKey, value) End If End Set End Property #End Region Public Sub New() MyBase.New() End Sub Public Sub New(JobName As String, service As SPService, Installing As Boolean, FileExtension As String, ImageFilename As String) MyBase.New(JobName, service) _installing = Installing _fileExtension = FileExtension _imageFilename = ImageFilename End Sub Public Overrides Sub Execute(jobState As Microsoft.SharePoint.Administration.SPJobState) UpdateDocIcon() End Sub Private Sub UpdateDocIcon() Dim x As New XmlDocument x.Load(DocIconPath) Dim mapNode As XmlNode = x.SelectSingleNode(String.Format("DocIcons/ByExtension/Mapping[@Key='{0}']", _fileExtension)) If _installing Then 'Create DocIcon entry If mapNode Is Nothing Then 'Create Attributes Dim keyAttribute As XmlAttribute = x.CreateAttribute("Key") keyAttribute.Value = _fileExtension Dim valueAttribute As XmlAttribute = x.CreateAttribute("Value") valueAttribute.Value = _imageFilename 'Create Node mapNode = x.CreateElement("Mapping") mapNode.Attributes.Append(keyAttribute) mapNode.Attributes.Append(valueAttribute) Dim byExtensionNode = x.SelectSingleNode("DocIcons/ByExtension") Dim NodeAdded As Boolean = False If byExtensionNode IsNot Nothing Then 'Add in alphabetic order For Each mapping As XmlNode In byExtensionNode.ChildNodes If mapping.Attributes("Key").Value.CompareTo(_fileExtension) > 0 Then byExtensionNode.InsertBefore(mapNode, mapping) NodeAdded = True Exit For End If Next If Not NodeAdded Then byExtensionNode.AppendChild(mapNode) x.Save(DocIconPath) End If End If Else 'Remove DocIcon entry If mapNode IsNot Nothing Then Dim byExtensionNode = x.SelectSingleNode("DocIcons/ByExtension") If byExtensionNode IsNot Nothing Then byExtensionNode.RemoveChild(mapNode) x.Save(DocIconPath) End If End If End If End Sub End Class
What’s Going On:
Lines 9-73 are just the declaration of and logic needed to persist some properties. Again more information can be found in my last post, but basically I am using the SPJobDefinition’s Properties HashTable to store my own properties as specified in the constructor. Except for in the case of the DocIconPath property which is really just wrapping up some logic to get a reference to the 14 Hive’s TEMPLATE\XML directory using the SPUtility class.
The Execute method beginning in line 86 is what is called when the Timer Job actually runs. I override this method to ensure my custom code gets called instead. My custom code really begins in the UpdateDocIcon method starting at line 90.
In lines 91-94, I load the DOCICON.xml file into and XmlDocument object and attempt to find the mapping node that applies to the appropriate file extension (In this case it’s going to be pdf).
If this job is installing (Running on Solution Activation), then I just check to see if the node was found. If so, all done! If not, then it’s time to add it. I create the node and setup it’s attributes in lines 100-108 using standard objects from the System.Xml namespace.
In order to work, the mapping node needs to be added as a child of the ByExtension element, so we find that in line 110. By default the mapping nodes are listed in alphabetical order by their extension. Since I’m anal, I use a method in lines 114-120 presented by Steve Goodyear to ensure I insert the mapping node in it’s proper position. Failing that, I add it to the end in Line 122 and save the file in line 123.
If this job is uninstalling (Running on Solution Deactivation) and the mapping exists, we delete it and save the file in lines 128-134.
Isn’t that Super Exciting?!?! Hopefully this example will help make the concepts I was talking about in my previous post make some sense. If not, then sadness will fill my soul and flowers will no longer bloom or something.
[…] post is very similar to my post Updating an XML File in the 14 Hive Using a Custom Timer Job and assumes you know some basics about custom timer job creation (If not, check my other post […]
is there other way to do that .. what about create a site feature instead a write this piece of code at the feature activate/deactivate event
@Michael I investigated several ways of doing this and found that only a timer job would allow my changes to be propagated to all my web servers. Just doing this on either the activation/deactivation or install/uninstall events only affected a single server. A good example of the solution you propose can be found here. If you read through the comments or run the code you’ll see the problem I am referring to.
The good news is that the simple timer job shown above takes care of this issue by running on each server and since it is only run on feature activation/deactivation, it’s really not much different than just running this code in those events. The main difference is that it works, which is always a good thing! Thanks for your comments!