Deploying Language Variations using Features

As I perform daily smoke & build using continuous integration, I required the rollout of variation labels to be automatic.

There are a few solutions out there, but the best solution out there, in my mind, is created by Waldek Mastykarz, which uses reflections to create the variation labels.

I have tweaked the code to make it feature configurable as I needed to specify the variations in the ONET.XML site definition as follows:

   1: <!-- Variation creation --> 



   2: <Feature ID="FE0615C8-582F-4a04-BECF-56BBD1E646A8"> 



   3:     <Properties xmlns="http://schemas.microsoft.com/sharepoint/"> 



   4:         <Property Key="VariationLabels" Value="ar,ar-SA,1025,العربية;en,en-US,1033,English;"/> 



   5:         <Property Key="Template" Value="MYCUSTOMTEMPLATE#1" /> 



   6:     </Properties> 



   7: </Feature>



 



The labels are delimited as “URL,CULTURE,LCID,TITLE;”

This is done within the feature receiver as:






   1: <?xml version="1.0" encoding="utf-8" ?> 



   2: <Feature 



   3:     Id="00000000-0000-0000-0000-000000000000" 



   4:     Title="Variation Creation" 



   5:     Description="" 



   6:     Scope="Web" 



   7:     Hidden="True" 



   8:     xmlns="http://schemas.microsoft.com/sharepoint/" 



   9:     ReceiverAssembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0000000000000000" 



  10:     ReceiverClass="MyAssembly.FeatureReceivers.VariationCreationFeatureReceiver"> 



  11:     <Properties> 



  12:         <Property Key="VariationLabels" Value="" /> 



  13:         <Property Key="Template" Value="" /> 



  14:     </Properties> 



  15:     <ElementManifests /> 



  16: </Feature> 




 



I add a call to create variations within the  method of the feature receiver as:





   1: public override void FeatureActivated(SPFeatureReceiverProperties properties) 



   2: { 



   3:     CreateVariations(GetVariationLabels(properties), 



   4:     properties.Feature.Properties["Template"].Value); 



   5: }




 



The GetVariationLabels  method gets all unique variations including labels from the system:





   1: /// <summary> 



   2: /// Gets the variation labels declared in the feature properties. 



   3: /// </summary> 



   4: /// <param name="properties">The feature properties.</param> 



   5: /// <returns>A strongly typed collection of variations.</returns> 



   6: private static ICollection<Variation> GetVariationLabels 



   7:     (SPFeatureReceiverProperties properties) 



   8: { 



   9:     List<Variation> variations = new List<Variation>(); 



  10:     foreach ( 



  11:         string label in 



  12:             properties.Feature.Properties["VariationLabels"].Value.Split( 



  13:                 new[] {';'}, 



  14:                 StringSplitOptions.RemoveEmptyEntries)) 



  15:     { 



  16:         string[] parts = label.Split(new[] {','}); 



  17:         bool source = variations.Count == 0; 



  18:         variations.Add(new Variation 



  19:             { 



  20:                 DisplayName = parts[0], 



  21:                 Label = parts[0], 



  22:                 Language = parts[1], 



  23:                 Locale = int.Parse(parts[2], CultureInfo.InvariantCulture), 



  24:                 Description = parts[3], 



  25:                 IsSource = source 



  26:             }); 



  27:     } 



  28:     return variations; 



  29: }








The CreateVariations method calls to create a variation entry per variation, sets the variation settings and creates the site hierarchies:





   1: /// <summary> 



   2: /// Creates the variation labels and the variation label hierarchy for a site collection. 



   3: /// </summary> 



   4: /// <param name="variations">A collection of variations to create.</param> 



   5: /// <param name="template">The ID of the template used to create new variation sites.</param> 



   6: private void CreateVariations(ICollection<Variation> variations, string template) 



   7: { 



   8:     if (variations.Count == 0) 



   9:     { 



  10:     return; 



  11:     } 



  12:     using (SPSite site = new SPSite(Site.ID)) 



  13:         { 



  14:         using (SPWeb web = site.OpenWeb(Web.ID)) 



  15:         { 



  16:             SPList list = web.Lists[new Guid( 



  17:             web.AllProperties["_VarLabelsListId"].ToString())]; 



  18:             foreach (Variation variation in variations) 



  19:             { 



  20:                 CreateVariation(list, variation, test); 



  21:             } 



  22:             UpdateVariationSettings(web, template); 



  23:             timeout = 5; 



  24:             CreateSiteHierarchies(list, timeout); 



  25:         } 



  26:     } 



  27: }



  28:  



  29: /// <summary> 



  30: /// Provides an instance of the Variation class. 



  31: /// </summary> 



  32: public class Variation 



  33: { 



  34:     #region Properties 



  35:  



  36:     /// <summary> 



  37:     /// Gets or sets the label. 



  38:     /// </summary> 



  39:     /// <value>The label.</value> 



  40:     public string Label { get; set; } 



  41:  



  42:     /// <summary> 



  43:     /// Gets or sets the display name. 



  44:     /// </summary> 



  45:     /// <value>The display name.</value> 



  46:     public string DisplayName { get; set; } 



  47:  



  48:     /// <summary> 



  49:     /// Gets or sets the language, i.e. the ISO specification for the language pack to use. 



  50:     /// For example, the culture en-GB, or 2057, is not a valid language pack but a valid 



  51:     /// culture and should therefore use en-US. 



  52:     /// </summary> 



  53:     /// <value>The language.</value> 



  54:     public string Language { get; set; } 



  55:  



  56:     /// <summary> 



  57:     /// Gets or sets the locale. 



  58:     /// </summary> 



  59:     /// <value>The locale.</value> 



  60:     public int Locale { get; set; } 



  61:  



  62:     /// <summary> 



  63:     /// Gets or sets a value indicating whether this instance is source. 



  64:     /// </summary> 



  65:     /// <value><c>true</c> if this instance is source; otherwise, <c>false</c>.</value> 



  66:     public bool IsSource { get; set; } 



  67:  



  68:     /// <summary> 



  69:     /// Gets or sets the description. 



  70:     /// </summary> 



  71:     /// <value>The description.</value> 



  72:     public string Description { get; set; } 



  73:  



  74:     #endregion 



  75: }






The method CreateVariation adds the variation to the system:






   1: /// <summary> 



   2: /// Creates a variation. 



   3: /// </summary> 



   4: /// <param name="list">The variations list.</param> 



   5: /// <param name="variation">The variation to create.</param> 



   6: private static void CreateVariation(SPList list, Variation variation) 



   7: { 



   8:     SPListItem item = list.Items.Add(); 



   9:     item["Hierarchy Is Created"] = false; 



  10:     item["Title"] = variation.Label; 



  11:     item["Description"] = variation.Description; 



  12:     item["Flag Control Display Name"] = variation.DisplayName; 



  13:     item["Language"] = variation.Language; 



  14:     item["Locale"] = variation.Locale; 



  15:     item["Hierarchy Creation Mode"] = "Publishing Sites and All Pages"; 



  16:     item["Is Source"] = variation.IsSource; 



  17:     item.Update(); 



  18: } 






The UpdateVariationSettings method sets overall variation settings. Note: I am considering adding these as feature properties as well, but have put that on hold. Feel free to modify to do so :)





   1: /// <summary> 



   2: /// Udpates the variation settings. 



   3: /// </summary> 



   4: /// <param name="web">The <see cref="SPWeb"/> containing the variations.</param> 



   5: /// <param name="template">The ID of the template used to create new variation sites.</param> 



   6: private static void UpdateVariationSettings(SPWeb web, string template) 



   7: { 



   8:     SPList variationSettingsList = web.Lists[ 



   9:                                        new Guid(web.AllProperties["_VarRelationshipsListId"].ToString())]; 



  10:     SPListItem variationroot = variationSettingsList.Items[0]; 



  11:     variationroot["ObjectID"] = string.Format( 



  12:                                     CultureInfo.InvariantCulture, 



  13:                                     "{0}/, /", web.Site.Url); 



  14:     variationroot.Update(); 



  15:     Hashtable properties = variationSettingsList.RootFolder.Properties; 



  16:     properties["SourceVarRootWebTemplatePropertyName"] = template; 



  17:     properties["EnableAutoSpawnPropertyName"] = true; 



  18:     properties["AutoSpawnStopAfterDeletePropertyName"] = true; 



  19:     properties["UpdateWebPartsPropertyName"] = true; 



  20:     properties["CopyResourcesPropertyName"] = true; 



  21:     properties["SendNotificationEmailPropertyName"] = true; 



  22:     variationSettingsList.RootFolder.Update(); 



  23: } 




Finally, the hierarchies are compiled using the method CreateSiteHierarchies. This is done using reflections, as no public interfaces exists for performing this task. Also, if the operation fails, as would happen if the site template has an error, then I need to use a timeout to fail the job.





   1: /// <summary> 



   2: /// Creates the site hierarchies. 



   3: /// </summary> 



   4: /// <param name="list">The variation labels list.</param> 



   5: /// <param name="timeOutMinutes"> 



   6: /// The minutes to wait before timeout, while the hierarchies are being created. 



   7: /// </param> 



   8: private static void CreateSiteHierarchies(SPList list, double timeOutMinutes) 



   9: { 



  10:     Assembly assembly = Assembly.GetAssembly(typeof (PublishingSite)); 



  11:     if (assembly == null) 



  12:     { 



  13:         throw new Exception("The Publishing API could not be loaded"); 



  14:     } 



  15:     Type type = assembly.GetType("Microsoft.SharePoint.Publishing.Internal.WebControls.CreateVariationHierarchiesLro", 



  16:                     false, 



  17:                     false); 



  18:     if (type == null) 



  19:     { 



  20:         throw new Exception("The variations API could not be loaded."); 



  21:     } 



  22:     //Waiting for hierarchy to be created. 



  23:     SPWeb web = list.ParentWeb; 



  24:     object lro = Activator.CreateInstance(type, 



  25:                     BindingFlags.Instance | BindingFlags.NonPublic, 



  26:                     null, 



  27:                     new object[] {web.Site.Url}, 



  28:                     CultureInfo.CurrentCulture); 



  29:     PropertyInfo redirectWhenFinished = type.GetProperty("RedirectWhenFinished"); 



  30:     redirectWhenFinished.SetValue(lro, 



  31:                                   false, 



  32:                                   null); 



  33:     type.InvokeMember("Start", 



  34:                         BindingFlags.InvokeMethod, 



  35:                         null, 



  36:                         lro, 



  37:                         new object[] {web}, 



  38:                         CultureInfo.InvariantCulture); 



  39:                         //Wait while hierachies are created. 



  40:     bool isNormalFinish = false; 



  41:     DateTime endTime = DateTime.Now.AddMinutes(timeOutMinutes); 



  42:     while (DateTime.Now < endTime) 



  43:     { 



  44:         int hierarchyIsCreatedCounter = 0; 



  45:         SPListItemCollection items = list.Items; 



  46:         foreach (SPListItem item in items) 



  47:         { 



  48:             bool hierarchyIsCreated; 



  49:             if (!bool.TryParse(item["Hierarchy Is Created"].ToString(), out hierarchyIsCreated)) 



  50:             { 



  51:                 hierarchyIsCreated = false; 



  52:             } 



  53:             if (hierarchyIsCreated) 



  54:             { 



  55:                 hierarchyIsCreatedCounter++; 



  56:             } 



  57:          } 



  58:         if (hierarchyIsCreatedCounter == items.Count) 



  59:         { 



  60:             isNormalFinish = true; 



  61:             break; 



  62:         } 



  63:         Thread.Sleep(1000); //Wait for a second to release the thread. 



  64:     } 



  65:     if (!isNormalFinish) 



  66:     { 



  67:         //Write trace here (using custom TraceProvider in my actual version) 



  68:     } 



  69: } 






Apologies for the lengthy post – I could not attach the original code as parts were under IP, hence some bits are removed.

0 comments: