In a previous post, I described a feature that would take install and retract modifications to SPTHEMES.XML. Peter Seale suggested providing a method to reapply the changes without a deactivate/activate cycle, specifically for new servers added to a farm. It should be as simple as providing a user interface to call FeatureThemesJob.InstallThemes
, but that presents a bit of a problem: InstallThemes
expects the name of the themes file, which I declare in the feature receiver. So before we can work on a reapplication interface, let’s move that file name to a more accessible location.
The Revised Feature
A better way to store the theme file name would be as a Feature Property:
<Feature Id="E2F8D046-607D-4BB6-93CC-2C04CF04099E" Title="SPHOLS Themes" Description="Installs SPHOLS and SPHOLSX themes on farm." Version="1.0.0.0" Scope="Farm" ReceiverAssembly="MyBranding, ..." ReceiverClass="MyBranding.MyThemesFeatureReceiver" xmlns="http://schemas.microsoft.com/sharepoint/"> <ElementManifests> <ElementFile Location="SPTHEMES.XML" /> </ElementManifests> <Properties> <Property Key="Solutionizing:ThemesFile" Value="SPTHEMES.XML" /> </Properties> </Feature>
And we can remove references to THEMES_FILE
from our receiver:
namespace MyBranding { public class MyThemesFeatureReceiver : SPFeatureReceiver { public override void FeatureActivated(SPFeatureReceiverProperties properties) { if (properties == null) throw new ArgumentNullException("properties"); FeatureThemesJob.InstallThemes(properties.Definition); } public override void FeatureDeactivating(SPFeatureReceiverProperties properties) { if (properties == null) throw new ArgumentNullException("properties"); FeatureThemesJob.DeleteThemes(properties.Definition); } public override void FeatureInstalled(SPFeatureReceiverProperties properties) { } public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { } } }
Note that this code is now completely feature-agnostic, reusable for any themes feature.
FeatureThemesJob
Other than purging the logic to persist _themesFile
, which I’ll leave as an exercise for the reader, we just need to update Execute
to use our new feature property:
private const string PROP_THEMES_FILE = "Solutionizing:ThemesFile" private const string SPTHEMES_PATH = @"TEMPLATE\LAYOUTS\1033\SPTHEMES.XML"; public override void Execute(Guid targetInstanceId) { SPFeatureDefinition fDef = Farm.FeatureDefinitions[_featureID]; if (fDef != null) { SPFeatureProperty themesFileProp = fDef.Properties[PROP_THEMES_FILE]; if(themesFileProp == null) throw new SPException(string.Format("Feature '{0}' is missing property '{1}'.", fDef.DisplayName, PROP_THEMES_FILE)); DoMerge(SPUtility.GetGenericSetupPath(SPTHEMES_PATH), Path.Combine(fDef.RootDirectory, themesFileProp.Value)); } }
But since we’re in a refactoring mood, we might as well extract the code to retrieve the themes file path:
internal const string PROP_THEMES_FILE = "Solutionizing:ThemesFile" private const string ERR_FEATURE_NOT_FOUND = "Feature '{0}' not found in farm."; private const string ERR_MISSING_PROPERTY = "Feature '{0}' is missing property '{1}'."; internal string ThemesFilePath { get { SPFeatureDefinition fDef = Farm.FeatureDefinitions[_featureID]; if (fDef == null) throw new SPException(string.Format(ERR_FEATURE_NOT_FOUND, _featureID)); SPFeatureProperty prop = fDef.Properties[PROP_THEMES_FILE]; if (prop == null) throw new SPException(string.Format(ERR_MISSING_PROPERTY, fDef.DisplayName, PROP_THEMES_FILE)); return Path.Combine(fDef.RootDirectory, prop.Value); } }
Which makes Execute
rather elegant:
private const string SPTHEMES_PATH = @"TEMPLATE\LAYOUTS\1033\SPTHEMES.XML"; public override void Execute(Guid targetInstanceId) { DoMerge(SPUtility.GetGenericSetupPath(SPTHEMES_PATH), ThemesFilePath); }
Now everything we need for the timer job is available from the feature definition. The next step is to build an interface to run the job on demand. Stay tuned!