Programmatically provisioning Variations in SharePoint Server 2010

, , , ,

Last year I wrote an article about programmatically provisioning Variation Hierarchies in SharePoint 2007. The point of that article was that there was really no way you could provision Variations in repeatable way in a supported fashion and had to use reflection to get the job done. The situation in SharePoint 2010 has changed a little. The process of creating Variations has been made more reliable my moving it completely to a Timer Job. So a new approach, requires new code, and here it is.

Updated Aug 30, 2010: the code sample of Step 2 has been updated. There was an issue that was causing SharePoint throw exception while trying to edit Variation Labels that were created programmatically.

Programmatically provisioning Variations

The process of provisioning Variations consists of three steps:

  1. Configuring Variation Settings (Site Actions > Site Settings >Variations)
  2. Configuring Variation Labels (Site Actions > Site Settings > Variation labels)
  3. Creating Variation Hierarchies (Site Actions > Site Settings > Variation labels > Create Hierarchies)

So in order to programmatically provision Variations in a repeatable fashion, you have to cover all three steps in your code.

Before you read further keep in mind that there is still no public API for creating Variations available in SharePoint 2010. As you will notice earlier, the code examples I show in this article, use internal properties. And although you don’t have to use reflection anymore this approach is still unsupported.

Step 1: Configuring Variation Settings

Configuring Variation Settings can be done using the Site Actions > Site Settings > Variation page.

Variation Settings page in SharePoint Server 2010.

As you can see this page hasn’t really changed since MOSS 2007 and contains the same settings as previously what makes it easy for us to write the code for:

private static void ConfigureVariationsSettings(SPWeb rootWeb)
{
    Guid varRelationshipsListId = new Guid(rootWeb.AllProperties["_VarRelationshipsListId"] as string);
    SPList varRelationshipsList = rootWeb.Lists[varRelationshipsListId];
    SPFolder rootFolder = varRelationshipsList.RootFolder;
    // Automatic creation
    rootFolder.Properties["EnableAutoSpawnPropertyName"] = "true";
    // Recreate Deleted Target Page; set to false to enable recreation
    rootFolder.Properties["AutoSpawnStopAfterDeletePropertyName"] = "false";
    // Update Target Page Web Parts
    rootFolder.Properties["UpdateWebPartsPropertyName"] = "true";
    // Resources
    rootFolder.Properties["CopyResourcesPropertyName"] = "true";
    // Notification
    rootFolder.Properties["SendNotificationEmailPropertyName"] = "false";
    rootFolder.Properties["SourceVarRootWebTemplatePropertyName"] = "CMSPUBLISHING#0";
    rootFolder.Update();

    SPListItem item = null;
    if (varRelationshipsList.Items.Count > 0)
    {
        item = varRelationshipsList.Items[0];
    }
    else
    {
        item = varRelationshipsList.Items.Add();
        item["GroupGuid"] = new Guid("F68A02C8-2DCC-4894-B67D-BBAED5A066F9");
    }

    item["Deleted"] = false;
    item["ObjectID"] = rootWeb.ServerRelativeUrl;
    item["ParentAreaID"] = String.Empty;
    item.Update();
}

The Variations Setting are being stored in a hidden List in the Root Web. Because this List is provisioned dynamically, you can get a reference to it by getting the value of the _VarRelationshipsListId property. For every setting from the Variations Settings page there is a corresponding property in the Property Bag of the Root Folder of the hidden List. Probably the most confusing setting here is the Recreate Deleted Target Page setting, which has to be set to false in order to enable the recreation.

Among the properties you can also find the SourceVarRootWebTemplatePropertyName property which contains the name of the Site Definition used to provision new Variation Hierarchies. This corresponds to the Source Variation settings while configuring Variation Labels.

Source Variation settings for configuring Variation Labels.

Next to the settings from the Variations Settings page, the hidden List contains one List Item which contains the configuration of the Variation Home setting.

Using the code snippet above you can provision the Variation Settings in a repeatable way.

Step 2: Configuring Variation Labels

Configuring Variation Labels in SharePoint Server 2010 is exactly the same as in MOSS 2007. You need to create a Variation Label for each variation in your site and once you’re done, you have to create the Variation Hierarchies.

Variation Labels can be provisioned using the following code snippet:

private static VariationLabel[] labels = {
    new VariationLabel
    {
        Title = "nl-nl",
        FlagControlDisplayName = "Nederlands",
        Language = "nl-NL",
        Locale = 1043,
        HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages,
        IsSource = true
    },
    new VariationLabel
    {
        Title = "en-us",
        FlagControlDisplayName = "English",
        Language = "en-US",
        Locale = 1033,
        HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages
    }
};

private static void CreateVariations(SPWeb rootWeb)
{
    Guid varListId = new Guid(rootWeb.AllProperties["_VarLabelsListId"] as string);
    SPList varList = rootWeb.Lists[varListId];

    foreach (VariationLabel label in labels)
    {
        SPListItem item = varList.Items.Add();
        item[SPBuiltInFieldId.Title] = label.Title;
        item["Description"] = label.Description;
        item["Flag Control Display Name"] = label.FlagControlDisplayName;
        item["Language"] = label.Language;
        item["Locale"] = label.Locale.ToString();
        item["Hierarchy Creation Mode"] = label.HierarchyCreationMode;
        item["Is Source"] = label.IsSource.ToString();
        item["Hierarchy Is Created"] = false;
        item.Update();
    }
}

Similar to MOSS 2007, configuration of each Variation Label is stored as a List Item in a hidden list (different than the Variations Settings), so for every Variation Label you need to in fact create a List Item and fill it with the required information.

Running the code snippet above will create two Variation Labels in your Site Collection:

Dutch and English Variation Labels created using the code snippet above.

Step 3: Creating Variation Hierarchies

This is the piece that has changed in SharePoint Server 2010 making creating Variation Hierarchies more stable and reliable. In MOSS 2007 Variation Hierarchies were created using a Long Running Operation in the w3wp.exe process. There were scenario’s when, if something went wrong, you had to perform IIS reset. As you can expect this would only break the whole process even further. In SharePoint Server 2010 creating Variation Hierarchies has been moved to a Timer Job which runs separately from the w3wp.exe process, which makes the whole thing more stable.

As the approach of creating Variation Hierarchies changed, the code that we used before became useless. The great thing about the new approach is, that because it relies on a Timer Job, it is easier to start and to control. You can programmatically create Variation Hierarchies using the following code snippet:

private static void CreateHierarchies(SPSite site, SPWeb rootWeb)
{
    site.AddWorkItem(Guid.Empty, DateTime.Now.ToUniversalTime(), new Guid("e7496be8-22a8-45bf-843a-d1bd83aceb25"), rootWeb.ID, site.ID, 1, false, Guid.Empty, Guid.Empty, rootWeb.CurrentUser.ID, null, String.Empty, Guid.Empty, false);
    SPWebApplication webApplication = site.WebApplication;
    SPJobDefinition variationsJob = (from SPJobDefinition job in
                                     webApplication.JobDefinitions
                                     where job.Name == "VariationsCreateHierarchies"
                                     select job).FirstOrDefault();

    if (variationsJob != null)
    {
        DateTime startTime = DateTime.Now.ToUniversalTime();
        variationsJob.RunNow();

        // wait until the job is finished
        while ((from SPJobHistory j in webApplication.JobHistoryEntries where j.JobDefinitionId == variationsJob.Id && j.StartTime > startTime select j).Any() == false)
        {
            Thread.Sleep(100);
        }
    }
}

The first thing that needs to be done is to add a work item. This is needed as the Timer Job responsible for creating Variation Hierarchies is a work-item timer job. Once it’s in place, you can start the job. To do that you need a reference to the Timer Job which is scoped to Web Application.

In the code sample above I use a pause routine that waits until the job finishes. This is important in scenarios when provisioning Variation Hierarchies is not the last step and there are some other steps in the queue which depend on the Variations. You are free to skip this part if you don’t need it in your scenario.

And that’s it. After running this last piece of code the Variation Hierarchies have been created:

Variation Hierarchies created using the code snippet above.

Summary

Variations are a great mechanism for delivering multilingual solutions. Provisioning Variations is a part of the configuration process and you should try to include it in your structure and repeatable deployment process. Although Variations in SharePoint Server 2010 have been improved and made more reliable, there is still no public API available for programmatically configuring and provisioning Variations. Although you don’t need to use reflection anymore in SharePoint Server 2010, you have to use hidden properties, which Variations depend upon, what makes any solution for programmatically provisioning Variations unsupported.


Possibly related posts

50 Responses to “Programmatically provisioning Variations in SharePoint Server 2010”

  1. Stephan Says:

    Nice post.

    You mention that you access internal properties.

    How do you manage to access the VariationLabel-constructor? And the 'FlagControlDisplayName' property for instance? My compile throws an error. What is the trick to be able to access these private/internal members?

    Thankyou in advance.

  2. Waldek Mastykarz Says:

    @Stephan: I don't. Instead of using the internal classes I write the information directly to the hidden Lists that store the Variations Configuration. You shouldn't get any errors when using the code I presented above.

  3. Stephan Says:

    Hi Waldek

    Thankyou for answering this fast.

    I have a reference to Webdanmark.Sharepoint.Publishing. When I create a VariationLabel with a linve like this:

    var v = new VariationLabel() i get a compiler error saying, that the empty constructor of VariationLabel is private.

    In step 2 you create instances of VariationLabel. Do we use different versions of the Sharepoint API maybe?

  4. Stephan Says:

    Hello again Waldek.

    After restarting visual studio it seems that my compiler errors have disapeared. All you code compiles now.

    Sorry for poluting your comments section with this :)

    This is a great post – without it, no full automatic deploy process can be made in our case. Thanks.

  5. Waldek Mastykarz Says:

    @Stephan: sorry for that. My bad. The VariationLabel is a wrapper class that I created myself:

    public class VariationLabel
    {
    public string Title { get; set; }
    public string Description { get; set; }
    public string FlagControlDisplayName { get; set; }
    ///

    /// eg. en-US
    ///

    public string Language { get; set; }
    ///

    /// eg. 1033
    ///

    public uint Locale { get; set; }
    public string HierarchyCreationMode { get; set; }
    public bool IsSource { get; set; }
    public string SourceVarRootWebTemplate { get; set; }
    }

    It simplifies storing the configuration and it's coincidentally called the same as a class that is a part of SharePoint Server 2010.

  6. Stephan Says:

    Okay thankyou. :)

  7. Stephan Says:

    Did you make the constants class 'CreationMode' too?

  8. Waldek Mastykarz Says:

    @Stephan: yes:

    public static class CreationMode
    {
    public const string PublishingSitesAndAllPages = "Publishing Sites and All Pages";
    public const string PublishingSitesOnly = "Publishing Sites Only";
    public const string RootSitesOnly = "Root Sites Only";
    }

    I really appreciate your feedback. Thank you for being such a careful reader and helping make the code sample complete.

  9. Stephan Says:

    No problem, glad to help :)

    I get this error message when accessing the variation root page:

    "The Site Welcome Page is set to VariationsRoot but there are no Variation Sites. Site Administrator can reset the Site Welcome Page if necessary."

    I have run the above code with the same parameters as in the example and I installed these 5 labels:

    VariationInfoHolder[] labels = {
    new VariationInfoHolder{
    Title = "da",
    FlagControlDisplayName = "Dansk",
    Language = "da-DK",
    Locale = 1030,
    HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages,
    IsSource = true
    },
    new VariationInfoHolder{
    Title = "en",
    FlagControlDisplayName = "Engelsk",
    Language = "en-GB",
    Locale = 2057,
    HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages
    },
    new VariationInfoHolder{
    Title = "de",
    FlagControlDisplayName = "Tysk",
    Language = "de-DE",
    Locale = 1031,
    HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages
    },
    new VariationInfoHolder{
    Title = "fr",
    FlagControlDisplayName = "French",
    Language = "fr-FR",
    Locale = 1036,
    HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages
    },
    new VariationInfoHolder{
    Title = "cs",
    FlagControlDisplayName = "Tjekkisk",
    Language = "cs-CZ",
    Locale = 1036,
    HierarchyCreationMode = CreationMode.PublishingSitesAndAllPages
    }
    };

  10. Stephan Says:

    I can inform, that the code was run on a web application with a root sotecollection just added. No other stuff has been made to the sate before I run the code.

    I tried manually to run alle jobs in the central administration with "variation" in the name to ensure that everything has been run, but the error remains.

  11. Waldek Mastykarz Says:

    @Stephan: can you check if the Variation Labels have been correctly created and if the Timer Job responsible for creating Variation Hierarchies have run?

  12. Stephan Says:

    Yes. My labels have been created correctly and the job has been run.

    Sorry for the delay. :)

  13. Waldek Mastykarz Says:

    @Stephan: have you tried recreating the Site Collection and rerunning the whole process again?

  14. Stephan Says:

    Yes. I recreated my webapplication and my site collection and ran it again. Still getting the error.

  15. Stephan Says:

    I figured, that I do actually get an error when I click a variation label in the SP GUI after creating them. This is the corresponding error logged at the time:

    System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.SharePoint.Publishing.Internal.CodeBehind.VariationLabelPage.OnLoad(EventArgs args) at System.Web.UI.Control.LoadRecursive() at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

  16. Waldek Mastykarz Says:

    @Stephn: I will check that out for you and will get back to you as soon as I have any more information.

  17. Stephan Says:

    Thankyou very much. Your help is more than appreciated :)

  18. Waldek Mastykarz Says:

    @Stephan: although I've solved the bug with an exception being thrown while trying to edit the Variation Labels, I couldn't confirm your issue with the VariationLandingRoot. It's working okay here. Sure everything works correctly if you do the same manually?

  19. Stephan Says:

    Hi Waldek.

    How did you fix the bug? Any code changes in the above?

  20. Waldek Mastykarz Says:

    @Stephan: yes, I've added line #36 in Step 2.

  21. Stephan Says:

    Im trying to create a running version of the code looking the excact same as yours. The variable 'startTime' in step 3 is not defined anywhere. I instantiated it in the start of the method and set it to DateTime.Now. Is this wrong?

  22. Stephan Says:

    I have done some monkey business and tried to create everything manually. Worked correctly.

    Then I tried using step 1 in code and do step 2 and 3 manually. Worked correctly.

    Then I tried step 1 and 2 in code and created the hierarchy manually. Crashed.

    I created the exact 2 labels as you have included in your code. I guess step 2 is what breaks my app.

    I removed the web application after each test and rolled it back on. The webapplication is created using an automatic powershell script so it should be created the same way each time.

  23. Stephan Says:

    I have made a test, running step 1 and step 2 in code.

    Before I manually create the hierarchies as step 3, I go to the GUI and click each of the two labels. Then I click OK to save "changes" though I havent made any changes. After creating the hierarchies then everything works fine.

    I guess saving the label using the GUI sets something which the code in step 2 doesn't. Just some assumptions.

  24. Waldek Mastykarz Says:

    @Stephan: that's really weird, especially since the whole process works correctly here. Have you checked your event log if there are any errors?

  25. David Martos Says:

    Thanks Waldek. This is a very interesting and useful article. It worked perfectly for me. Just a comment: regarding the startTime property that it's undefined, I declared it as:

    var startTime = DateTime.UtcNow;

    at the begining of the method CreateHierarchies. Is that correct?

  26. Waldek Mastykarz Says:

    @David: initiating the startTime variable somehow got removed when I was copying the code. I've added it in sample 3 line #12. Thanks for the feedback.

  27. Stephan Says:

    @Waldek: I have looked in the log after creating the variations. This error seems to be the problem:

    CreateVariationSourceTopWeb for web '/' on source label 'nl-nl' catches SPException. Microsoft.SharePoint.SPException: The language is not supported on the server. —> System.Runtime.InteropServices.COMException (0x8102005E): The language is not supported on the server…..

    I get the same error above when trying to create a danish label.

    Where i create en-US as the only label and set it as the source, everything works fine.

    Do you know how to configure the languages supported?

    Thanks

  28. Waldek Mastykarz Says:

    @Stephan: Did you install the Danish Language Pack? While you can use all locales out of the box you need Language Packs to use specific languages.

  29. Stephan Says:

    Well, every locale will only be shown in a single language. Would it be an ugly solution creating the different locales with en-US as language for all? This way it is not required to installe a language pack on the server each time a new label is created.

    I heard there is a built in logic in the variations logic sniffing the browsers data to determine which variation to show the user? Will this still work when using different locales but the same language?

  30. Waldek Mastykarz Says:

    @Stephan: I think it all depends on your requirements. If it's one and the same group of people who will be maintaining all variations it's definitely easier for them to use the interface in one language on all variation sites. If you however have different group of people being responsible for different language, it might be better to provide them with UI in their native language.

    As for the auto redirect, it uses locale information and not the language so it should work correctly no matter the language.

  31. Stephan Says:

    Okay, well the administration interface will be kept in english. Im very pleased that the variations can now be created manually.

    Thanks again for this post and for the intense support you have given!

  32. Waldek Mastykarz Says:

    @Stephan: no problem. You're welcome :)

  33. abdessamad Says:

    First thanx for the interesting topic,
    but as beginner i don't know how to deploy this code to sharepoint

    thanx

  34. abdessamad Says:

    should i just create a class library project and drag&drop the dll into the GAC??

  35. Waldek Mastykarz Says:

    @abdessamad: it all depends on what you want to do. You can use the above code in any kind of project: from a Console Application to a Web Part as long as the code is deployed on a SharePoint Server.

  36. André Says:

    Hello Waldek,

    First of all congrats, this article is great! I have a question about running the jobdefinition, when I the feature try to execute teh method RunNow() occurs an exception:

    System.Security.SecurityException was unhandled by user code
    Message=Access Denied.
    Source=Microsoft.SharePoint
    StackTrace:
    at Microsoft.SharePoint.Administration.SPPersistedObject.BaseUpdate()
    at Microsoft.SharePoint.Administration.SPJobDefinition.Update()
    at Microsoft.SharePoint.Administration.SPJobDefinition.RunNow()
    at Redecard.Portal.Aberto.Variations.VariationEventReceiver.c__DisplayClass3.b__0()
    at Microsoft.SharePoint.SPSecurity.c__DisplayClass4.b__2()
    at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)
    InnerException:

    Have you seen this problem? I'm activating this feature with System Account and this user is the same of the application pool.

    Thanks!

  37. Waldek Mastykarz Says:

    @André: In order to update a Persisted Object the user must be a Farm Administrator. A message stating that should be logged in the ULS log after this exception being thrown.

  38. Venkatesh Says:

    Great Article. Helped a lot on understanding Variation Labels. Thanks a lot.

    I wrote this code in SharePoint Application page and faced Security Exception like Andre while invoking "CreateHierarchies" function.

    Then I went to "Central Admin -> Monitoring -> Check job status" and executed the below jobs manualy. [This job scheduled to run daily.]
    1. Variations Create Hierarchies Job Definition

    Not sure about below Jobs. But executed..
    1. Variations Create Site Job Definition
    2. Variations Propagate Site Job Definition

    Now my questions is when I upload a translation .resx file via "Site Settings -> Site Administration -> Import Translations", where the content getting stored? How can I access those content as Labels and It's value?
    Please Suggest. Thanks.

    Environment Details:
    Language Pack for SharePoint Foundation 2010 – Dutch/Nederlands
    Language Pack for SharePoint Foundation 2010 – French/Français
    Language Pack for SharePoint, Project Server and Office Web Apps 2010 – Dutch/Nederlands
    Language Pack for SharePoint, Project Server and Office Web Apps 2010 – French/Français
    Microsoft SharePoint Server 2010

    Dev UI : Visual Studio 2010 Ultimate

    Regards,
    Venkatesh R

  39. Waldek Mastykarz Says:

    @Venkatesh: The contents of the uploaded translation file are being stored in the Content Database. Although I haven't tried it myself it seems like you should be able to access the customized properties using the SPWeb.UserResources property (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.userresources.aspx).

  40. Caspar Says:

    What does the GUID "e7496be8-22a8-45bf-843a-d1bd83aceb25" stand for?

    I get the variation labels created but the CreateHierarchies runs the timer job but the timer job does nothing. Only after manually pressing the button "Create Hierarchy" and starting the Timer Job manually the variations get created.

    Any help is appreciated.

  41. Waldek Mastykarz Says:

    @Caspar: It's the ID of the Create Variations Work Item that's being picked up by the Timer Job. It's a constant value so it's the same on every environment. Have you checked if you got all the steps correctly and if you're getting any errors?

  42. Caspar Says:

    Ok, clear.
    About the hierarchy creation, there are no error messages. The timer job runs but doesn't seem to see there is anything that needs to be done.

  43. Waldek Mastykarz Says:

    @Caspar: Have you checked both Event Viewer and ULS for anything odd?

  44. Bhushan Says:

    Hi Waldek,
    Nice Post..
    just a small question..

    how can I check that , for a perticular web / site , wheather variation hierarchy has been already created or not?
    this is becuase I need to set my custom variation landing page as welcome page of root site If Variation Hierarchies are created

  45. Thomas Says:

    Thanks for a great article. I've read the comments and since you seem very helpfull I thought I might try my luck :)
    I'm basically having the same problem as Casper. The timer job executes successfully and there is nothing of note in the log other than time spent which is 00.0170010 seconds which seem a tad short to me. A colleague of mine has run into the same problem, but he simply activated it manually.

    If you have any tips it would be greatly appreciated :)

  46. Waldek Mastykarz Says:

    @Bhushan: As you can see in step two, there is a field called "Hierarchy Is Created". I would assume that reading its value would allow you to check if the variation hierarchy has been created or not.

  47. MP Says:

    I am tryting to run this code under powershell script and getting errors on this statement
    SPJobDefinition variationsJob = (from SPJobDefinition job in webApplication.JobDefinitions where job.Name == "VariationsCreateHierarchies" select job).FirstOrDefault();

    Add-Type : c:\Users\spservice\AppData\Local\Temp\1\laalxmgd.0.cs(106) : ) expec
    ted
    c:\Users\spservice\AppData\Local\Temp\1\laalxmgd.0.cs(105) : SPWebApplic
    ation webApplication = site.WebApplication;
    c:\Users\spservice\AppData\Local\Temp\1\laalxmgd.0.cs(106) : >>> SPJobD
    efinition variationsJob = (from SPJobDefinition job in webApplication.JobDefini
    tions where job.Name == "VariationsCreateHierarchies" select job).FirstOrDefaul
    t();
    c:\Users\spservice\AppData\Local\Temp\1\laalxmgd.0.cs(107) :

  48. Waldek Mastykarz Says:

    @MP: the code snippet is a C# snippet not PowerShell

  49. Naveen Says:

    Nice post, Thanks for the comments,

    Can we have powershell script to automate the "create variation hierarchies" action.

  50. Waldek Mastykarz Says:

    @Naveen: Having the code above it shouldn't be too difficult to translate it to PowerShell, or are you stuck at some point?

Leave a Reply

Security Code:

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS
Copyright © 2007 - 2012 Waldek Mastykarz

Creative Commons License