Programmatically provisioning Variations in SharePoint Server 2010
Deployment, Inconvenient SharePoint, SharePoint 2010, Structured and repeatable deployment, WCMLast 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:
- Configuring Variation Settings (Site Actions > Site Settings >Variations)
- Configuring Variation Labels (Site Actions > Site Settings > Variation labels)
- 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.
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.
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:
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:
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.

















August 26th, 2010 at 9:31 am
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.
August 26th, 2010 at 9:44 am
@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.
August 26th, 2010 at 11:11 am
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?
August 26th, 2010 at 11:30 am
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.
August 26th, 2010 at 11:30 am
@Stephan: sorry for that. My bad. The VariationLabel is a wrapper class that I created myself:
public class VariationLabel
/// eg. en-US
{
public string Title { get; set; }
public string Description { get; set; }
public string FlagControlDisplayName { get; set; }
///
///
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.
August 26th, 2010 at 11:35 am
Okay thankyou. :)
August 26th, 2010 at 11:38 am
Did you make the constants class 'CreationMode' too?
August 26th, 2010 at 11:43 am
@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.
August 26th, 2010 at 1:36 pm
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
}
};
August 26th, 2010 at 2:05 pm
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.
August 26th, 2010 at 2:41 pm
@Stephan: can you check if the Variation Labels have been correctly created and if the Timer Job responsible for creating Variation Hierarchies have run?
August 30th, 2010 at 8:00 am
Yes. My labels have been created correctly and the job has been run.
Sorry for the delay. :)
August 30th, 2010 at 8:11 am
@Stephan: have you tried recreating the Site Collection and rerunning the whole process again?
August 30th, 2010 at 8:15 am
Yes. I recreated my webapplication and my site collection and ran it again. Still getting the error.
August 30th, 2010 at 8:25 am
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)
August 30th, 2010 at 8:30 am
@Stephn: I will check that out for you and will get back to you as soon as I have any more information.
August 30th, 2010 at 8:31 am
Thankyou very much. Your help is more than appreciated :)
August 30th, 2010 at 7:05 pm
@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?
August 31st, 2010 at 10:00 am
Hi Waldek.
How did you fix the bug? Any code changes in the above?
August 31st, 2010 at 10:03 am
@Stephan: yes, I've added line #36 in Step 2.
August 31st, 2010 at 12:05 pm
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?
August 31st, 2010 at 1:06 pm
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.
August 31st, 2010 at 1:23 pm
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.
September 1st, 2010 at 6:16 am
@Stephan: that's really weird, especially since the whole process works correctly here. Have you checked your event log if there are any errors?
September 2nd, 2010 at 11:04 am
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?
September 2nd, 2010 at 3:22 pm
@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.
September 6th, 2010 at 10:03 am
@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
September 6th, 2010 at 10:29 am
@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.
September 6th, 2010 at 10:33 am
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?
September 6th, 2010 at 10:41 am
@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.
September 6th, 2010 at 11:42 am
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!
September 6th, 2010 at 1:57 pm
@Stephan: no problem. You're welcome :)
October 11th, 2010 at 6:00 pm
First thanx for the interesting topic,
but as beginner i don't know how to deploy this code to sharepoint
thanx
October 11th, 2010 at 6:09 pm
should i just create a class library project and drag&drop the dll into the GAC??
October 12th, 2010 at 6:13 am
@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.
October 25th, 2010 at 9:44 pm
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!
October 26th, 2010 at 7:50 am
@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.
November 3rd, 2010 at 7:06 am
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
November 3rd, 2010 at 7:41 am
@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).
February 1st, 2011 at 11:30 am
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.
February 1st, 2011 at 4:49 pm
@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?
February 2nd, 2011 at 8:37 am
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.
February 2nd, 2011 at 9:07 am
@Caspar: Have you checked both Event Viewer and ULS for anything odd?
February 3rd, 2011 at 10:57 am
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
April 8th, 2011 at 11:32 am
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 :)
April 8th, 2011 at 12:57 pm
@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.
August 11th, 2011 at 8:52 pm
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) :
August 12th, 2011 at 8:13 am
@MP: the code snippet is a C# snippet not PowerShell
November 1st, 2011 at 3:10 pm
Nice post, Thanks for the comments,
Can we have powershell script to automate the "create variation hierarchies" action.
November 1st, 2011 at 5:24 pm
@Naveen: Having the code above it shouldn't be too difficult to translate it to PowerShell, or are you stuck at some point?