Deploying a SharePoint Theme in a Solution

How to Deploying a SharePoint Theme in a Solution

In this article I will show you how to use one farm level feature to deploy the theme files and edit the SharePoint configuration files and a second optional web level feature to turn on the theme.  I'm going to assume you have Visual Studio 2008 with WSP Builder installed.  I'm going to create a Cold Elm Consulting theme as the example.

Setting up the Solution

  1. Create a new Project called CECTheming.

  2. Add a farm level feature with a receiver called CECThemeDeployment.

  3. Add a site level feature with a receiver called CECTheme.

  4. Add Themes and Images directories to /12/TEMPLATE template.

  5. Copy the theme  you have chosen into the Themes directory and name it and the internal files correctly.  Update the .inf file.

  6. Copy the relevant preview gif file into the images directory.  I've used thsimple.gif and renamed it.  I've also put a CEC logo image in the same directiry for use with the theming.

  7. You're project file structure should look like the one below.

    CECTheming project files
  8. Now we are ready to develop the features.

  9. We start with the CECThemeDeployment feature receiver.

When the solution is installed all the files shown above in the \12 directory will be copied into the ..\12 directory on all servers in the farm.  To complete the solution we need to code the two feature receivers.

Coding the Theme Deployment Feature Receiver

We use a farm level feature receiver because themes are deployed at the farm level.  We use the FeatrureActivated and FeatureDeactivated events because they are implemented across a farm.  Farm level features are  automatically activated when they are deployed.  The FeatureInstalled and FeatureUninstalling events only occur on the serer where they are run.  That's fine on a single server installation, but disasterous on a multi-server farm.

Here's the full set of code for the the feature with some commentary on the various sections.

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports Microsoft.SharePoint
Imports System.Xml
Imports Microsoft.SharePoint.Utilities
Imports System.IO

Namespace CECTheming
    Class CECThemeDeployment
        Inherits SPFeatureReceiver

#Region "Globals"

        Private _ThumbNailImage As String = "images/thColdElm.gif"
        Private _PreviewImage As String = "images/thColdElm.gif"
        Private _Description As String = "Cold Elm Theme"
        Private _DisplayName As String = "ColdElm"
        Private _TemplateID As String = "ColdElm"
        Private _properties As SPFeatureReceiverProperties
        Private _ThemeConfigPath As String = "TEMPLATE\lAYOUTS\1033\"
        Private _ThemeConfigFile = "SPTHEMES.XML"

#End Region         

Various private class level variables are declared and set to default values.  Some of these can be replaced by parameters passed into the receiver at run time by the activating feature.

#Region "Public Methods"

        Public Overloads Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)

            _properties = properties
            _GetParamaters()
            _AddSPTheme()

        End Sub         

This is the activation method.  It assigns the incoming propoerties to the class level object to allow the private methods to see the properties.  _GetParameters gets any incoming parameters from the feature and _AddTheme updates the theme configuration file

        Public Overloads Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)

            _properties = properties
            _GetParamaters()
            _RemoveSPTheme()

        End Sub         

This is similar to the activation, but calls _RemoveSPTheme to removes the theme from the theme configuration file.

        Public Overloads Overrides Sub FeatureInstalled(ByVal properties As SPFeatureReceiverProperties)
            'Throw New Exception("The method or operation is not implemented.")
        End Sub

        Public Overloads Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
            'Throw New Exception("The method or operation is not implemented.")
        End Sub

#End Region         

The install and uninstall methods which do nothing.

#Region "Private Methods"
        Private Sub _AddSPTheme()
            Dim strThemeConfigFile As String = String.Empty
            Dim xDoc As New XmlDocument
            Dim xRoot As XmlNode
            Dim xNode As XmlNode
            Dim xElement As XmlElement

            Try
                strThemeConfigFile = Path.Combine(SPUtility.GetGenericSetupPath(_ThemeConfigPath), _ThemeConfigFile)
                xDoc.Load(strThemeConfigFile)
                xRoot = xDoc.DocumentElement
                xNode = xDoc.CreateNode(XmlNodeType.Element, "Templates", "")
                xElement = xDoc.CreateElement("TemplateID")
                xElement.InnerText = _TemplateID
                xNode.AppendChild(xElement)
                xElement = xDoc.CreateElement("DisplayName")
                xElement.InnerText = _DisplayName
                xNode.AppendChild(xElement)
                xElement = xDoc.CreateElement("Description")
                xElement.InnerText = _Description
                xNode.AppendChild(xElement)
                xElement = xDoc.CreateElement("Thumbnail")
                xElement.InnerText = _ThumbNailImage
                xNode.AppendChild(xElement)
                xElement = xDoc.CreateElement("Preview")
                xElement.InnerText = _PreviewImage
                xNode.AppendChild(xElement)
                xRoot.InsertAfter(xNode, xRoot.LastChild)
                xDoc.Save(strThemeConfigFile)

            Catch ex As Exception
            End Try

        End Sub         

 The _AddSPTheme method uses XML objects to access the configuration file and add a new Templates node as the last element in the file.

        Private Sub _RemoveSPTheme()
            Dim strThemeConfigFile As String = String.Empty
            Dim xDoc As New XmlDocument
            Dim xRoot As XmlNode
            Dim xNode, xNode1 As XmlNode

            Try
                strThemeConfigFile = Path.Combine(SPUtility.GetGenericSetupPath(_ThemeConfigPath), _ThemeConfigFile)
                xDoc.Load(strThemeConfigFile)
                xRoot = xDoc.DocumentElement
                For Each xNode1 In xRoot.ChildNodes
                    If xNode1.ChildNodes.Count > 0 Then
                        If xNode1.ChildNodes.Item(0).InnerText = "CEC" Then
                            xNode = xNode1
                        End If
                    End If
                Next
                Try
                    xRoot.RemoveChild(xNode)
                    xDoc.Save(strThemeConfigFile)
                Catch ex As Exception
                End Try
            Catch ex As Exception

            End Try

        End Sub         

The _RemoveSPTheme methods uses XML objects to access the configuration file and delete the Template node for the theme.

        Private Sub _GetParamaters()

            _TemplateID = _GetParam("TemplateID", _TemplateID)
            _DisplayName = _GetParam("DisplayName", _DisplayName)
            _Description = _GetParam("Description", _Description)
            _PreviewImage = _GetParam("PreviewImage", _PreviewImage)
            _ThumbNailImage = _GetParam("ThumbNailImage", _ThumbNailImage)

        End Sub          

 The _GetParamaeters method gets any incoming parameters supplied by the feature and updates the class level variables. You will see how to declare these later in the paper.  There's a helper function _GetParam that actually does the work.

        Private Function _GetParam(ByVal Param As String, ByVal DefaultValue As String) As String
            Dim strTemp As String = String.Empty

            Try
                strTemp = _properties.Definition.Properties.Item(Param).Value()
            Catch ex As Exception
                strTemp = DefaultValue
            End Try

            Return strTemp

        End Function
#End Region
    End Class
End Namespace                              

Now we need to configure the feature configuration file - feature.xml. This is shown below. Note that I use CEC for my top level namespace labelling so I've had to update the recevier assembly and class information. Also note the properties section used to feed parameters into the receiver.  We have deleted the elements.xml file as we don't need it.

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="b18e6a9a-27db-4dc6-948c-cabccc001624"
          Title="CEC Theme Deployment"
          Description="CEC Theme Deployment"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Farm"
          DefaultResourceFile="core"
          ReceiverAssembly="CEC.Theming, Version=1.0.0.0, Culture=neutral, PublicKeyToken=032d2754dd6fcb70"
          ReceiverClass="CEC.Theming.CECThemeDeployment"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <Properties>
    <Property Key="TemplateID" Value="ColdElm" />
    <Property Key="DisplayName" Value="ColdElm" />
    <Property Key="Description" Value="ColdElm Theme" />
    <Property Key="ThumbNailImage" Value="images/thcoldelm.gif" />
    <Property Key="PreviewImage" Value="images/thcoldelm.gif" />
  </Properties>
</Feature>

Coding the Theme Feature Receiver

We use a web level feature receiver because themes are applied at the web level.  Again we use the FeatrureActivated and FeatureDeactivated events to execute our code.  Note that we are applying the theme and also setting various other web level settings.

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports Microsoft.SharePoint

Namespace CECTheming
    Class CECTheme
        Inherits SPFeatureReceiver
        Private _Theme As String = "ColdElm"
        Private _DefaultTheme As String = "none"
        Private _SiteImage As String = "/_layouts/images/ColdElmlogo.png"
        Private _properties As SPFeatureReceiverProperties
        Private _Web As SPWeb  

Various class level variables are declared with default values.  Some can be replaced through parameters supplied by the feature.

        Public Overloads Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)

            _properties = properties
            _GetParamaters()
            Try
                _Web = DirectCast(properties.Feature.Parent, SPWeb)
                _Web.ApplyTheme(_Theme)
                _Web.SiteLogoUrl = _SiteImage
                _Web.AllProperties("__IncludeSubSitesInNavigation") = "True"
                _Web.Navigation.UseShared = False
                _Web.Update()
            Catch ex As Exception
            End Try
        End Sub  

The activation method.  Note that we get the web assocaited with the feature through the properties object, not via SPCurrentcontext.  This is important as the feature may be activated by a site definition from another web. Here the current context would be the site from where the site definition is activated, not the site being created.  The receiver also sets the site image, sets the site navigation to include sub sites and the top global navigation to use the local site navigation, not the global navigation.  Note that we nned to call the update methos on the site to save the changes.

        Public Overloads Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)

            _properties = properties
            _GetParamaters()
            Try
                _Web = DirectCast(properties.Feature.Parent, SPWeb)
                _Web.ApplyTheme(_DefaultTheme)
                _Web.SiteLogoUrl = ""
                _Web.Update()
            Catch ex As Exception
            End Try

        End Sub

        Public Overloads Overrides Sub FeatureInstalled(ByVal properties As SPFeatureReceiverProperties)
            '           Throw New Exception("The method or operation is not implemented.")
        End Sub

        Public Overloads Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
            '            Throw New Exception("The method or operation is not implemented.")
        End Sub  

The deactivating method.  It reverts the themr to the default theme and removes the custom log image.

        Private Sub _GetParamaters()

            _Theme = _GetParam("Theme", _Theme)
            _SiteImage = _GetParam("SiteImage", _SiteImage)
            _DefaultTheme = _GetParam("DefaultTheme", _DefaultTheme)

        End Sub         

We use the same _GetParameters method and _GetParam helper function to get the parameters passed in by the feature

        Private Function _GetParam(ByVal Param As String, ByVal DefaultValue As String) As String
            Dim strTemp As String = String.Empty

            Try
                strTemp = _properties.Definition.Properties.Item(Param).Value()
            Catch ex As Exception
                strTemp = DefaultValue
            End Try

            Return strTemp

        End Function
    End Class
End Namespace

Now we need to configure the feature configuration file - feature.xml. This is shown below. Again note the use of CEC. as my top level namespace labelling, so updated recevier assembly and class information. Also note the properties section use to feed in the parameters we implemented code to receive.  We have deleted the elements.xml file as we don't need it.

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="6c9d5137-8a1c-4fab-b7df-37f534774852"
          Title="CEC Theme"
          Description="CEC Theme"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          DefaultResourceFile="core"
          ReceiverAssembly="CEC.Theming, Version=1.0.0.0, Culture=neutral, PublicKeyToken=032d2754dd6fcb70"
          ReceiverClass="CEC.Theming.CECTheme"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <Properties>
    <Property Key="Theme" Value="ColdElm" />
    <Property Key="DefaultTheme" Value="none" />
    <Property Key="SiteImage" Value="/_layouts/images/CEClogo.png" />
  </Properties>
</Feature>

Deploying the Solution with WSPBuilder

If you are testing on your own development system you can deploy the solution directly from Visual Studio using WSPBuilder. As soon as you have deployed it once you can use the WSBuilder shortcut methods for deployment - copy to 12 Hive and Copy to GAC.  Use the Copy to 12 Hive for all changes except .net code changes.  For these build the code first and then copy to GAC.  

Conclusion

This solution hopefully has shown you how to build a solution to deploy a custom theme.  Along the way you should also have seen the use of feature properties, how to get the feature web context during activation and how to modify in code some of the standard web features exposed through the web interface.  A zipped version of this project can be downloaded from here.