Monday, February 14, 2011

How to get all Variation Labels for SharePoint Site

While working with SharePoint Publishing sites we usually come across using Variations, and sometimes we want to work with them programmatically
as Variations are the outcome of SharePoint Publishing Infrastructure , so of course we need to use SharePoint Publishing APIs .. Microsoft.SharePoint.Publishing
one can use simple single line of code to get variation labels programmatically
ReadOnlyCollection<VariationLabel> _all = Variations.Current.UserAccessibleLabels;
above method gives a read only collection of VariationLabels for site , but wait ..
this method returns list of labels only when variation site hierarchy for a label is created successfully and If a requesting user has permission to access the variation site
but sometimes you might need to get all variation labels for a site even If you don’t have access to those sites or variation hierarchy of some sites is not created successfully
Well then here is something that is what I got when I looked in buddy reflector
SharePoint is platform where most of things are coming from list , where its OOB or while you do your development , and same is the case with Variations
SharePoint internally maintains a hidden list where all of these variations are kept, isn’t this sound good?
so approach is fairly simple , you just need to get the list and query and that’s all
here is sample code
note : VariationLabelEntity is custom entity class
Reference: Waldek Mastykarz


class Program
{
  private static SPList _variationsList = null;
  private static DataTable _allLabels = null;
  private static List<VariationLabelEntity> _varLabels = null;

  static void Main(string[] args)
  {
   try
   {
    using (SPSite site = new SPSite("http://YourSite"))
    {
      using (SPWeb web = site.RootWeb)
      {
       if (PublishingWeb.IsPublishingWeb(web))
       {
         string _listIdString = web.AllProperties["_VarLabelsListId"].ToString();

         if (!string.IsNullOrEmpty(_listIdString))
         {
           Guid _listId = new Guid(_listIdString);

           _variationsList = web.Lists[_listId];

           if (_variationsList != null)
           {
             SPQuery query = new SPQuery();
             query.Query = @"<Where><IsNotNull><FieldRef Name='Title' /></IsNotNull></Where>";
             query.ViewFields = "<FieldRef Name='Title'/><FieldRef Name='Language' /><FieldRef Name='Locale' /><FieldRef Name='Top_x0020_Web_x0020_URL' />";

             _allLabels = _variationsList.GetItems(query).GetDataTable();
           }

           if (_allLabels != null)
           {
             _varLabels = new List<VariationLabelEntity>();
             foreach (DataRow row in _allLabels.Rows)
             {
               string _topWebUrl = row["Top_x0020_Web_x0020_URL"].ToString();
               string[] _splits = null;
               if (_topWebUrl.Contains(','))
               {
                _splits = _topWebUrl.Split(',');
                _topWebUrl = _splits[0];
               }

                _varLabels.Add(new VariationLabelEntity
                {
                  Label = row["Title"].ToString(),
                  Language = row["Language"].ToString(),
                  Locale = row["Locale"].ToString(),
                  TopWebUrl = _topWebUrl,
                 });
                }
               }

           if (_varLabels != null && _varLabels.Count > 0)
           {
            foreach (VariationLabelEntity label in _varLabels)
            {
             Console.WriteLine(label.Label + ".." + label.Language + ".." + label.Locale + ".." + label.TopWebUrl);
            }
           }
          }
         }
         }
        }
        }
        catch (Exception ex)
        {
          Console.WriteLine(ex.Message);
        }

         Console.ReadLine();
     }
    }

public class VariationLabelEntity
{
        public string Label
        { get; set; }

        public string TopWebUrl
        { get; set; }

        public string Language
        { get; set; }

        public string Locale
        { get; set; }
}

Cross Web Application Query – SharePoint

Whenever we think of querying something in SharePoint site and showing results to users, then some intial thoughts pop ups in our mind like using Content Query web part.
no doubt that Out-of-the-box CQWP works pretty fine and does great content wrap up, but there are certain scenarios when we need to think differently.
OOB CQWP has great support for querying entire site collection or single web or a list but what will be the solution when you need to show results to users from another SharePoint web application?
Well then some options left for us like using APIs like SPSiteDataQuery and SPQuery. but there are two more options which are available to us which are made available by SharePoint publishing APIs
CrossListQueryInfo and CrossListQueryCache classes. Note that to use these you need to add reference to SharePoint Publishing assembly, Microsoft.SharePoint.Publishing
I am using these two classes and getting results from another web application’s root site, how? here is sample code
I am simply Initializing CrossListQueryInfo object and querying to pages library (server template Id=850) and used scope of query as entire site collection , after getting results I am simply adding a Content Query web part and binding results with data property of CQWP
I know there can be multiple ways to do this in SharePoint but I got this one and works fine for me




protected override void CreateChildControls()
{
  base.CreateChildControls();
  try
  {
    ContentByQueryWebPart _cqwp = new ContentByQueryWebPart();

    using (SPSite site = new SPSite("http://wv001945:4567"))
    {
      using (SPWeb web = site.RootWeb)
      {
        string _url = web.ServerRelativeUrl;

        //Initialize

        CrossListQueryInfo _crossListQueryInfo = new CrossListQueryInfo();
        _crossListQueryInfo.Lists = "<Lists ServerTemplate=\"850\"/>";
        _crossListQueryInfo.Webs = "<Webs Scope=\"SiteCollection\"/>";
        _crossListQueryInfo.ViewFields = "<FieldRef Name=\"Title\"/><FieldRef Name=\"FileRef\"/>";
        _crossListQueryInfo.Query = "<Where><IsNotNull><FieldRef Name='Title' /></IsNotNull></Where>";
        _crossListQueryInfo.RowLimit = 10;
        _crossListQueryInfo.WebUrl = _url;

        CrossListQueryCache _crossListQueryCache = new CrossListQueryCache(_crossListQueryInfo);
                       
  DataTable _table = _crossListQueryCache.GetSiteData(web);
                       
        if (!this.Page.IsPostBack)
        {
         if (_table != null && _table.Rows.Count > 0)
         {
           _cqwp.Data = _table;
         }
        }

      }
     }
      this.Controls.Add(_cqwp);
   }
   catch (Exception ex)
   {
     this._error = true;
     this.Controls.Clear();
     this.Controls.Add(new LiteralControl(ex.Message));
   }
  }

Unable to Insatiate Module – SharePoint Error

Have you ever come across such error?  I faced.. and solved
This error is basically related to the module element and usually comes when you try to provision a file to the SharePoint site which never exists on physical path / file system of the server
Here is the example case:
I am trying to provision a page named stylish.aspx to pages library same as default.aspx in onet.xml

<Module Name="SampleModule" Url="$Resources:cmscore,List_Pages_UrlName;" Path="">

<File Url="Default.aspx" NavBarHome="false" Type="GhostableInLibrary">
  <Property Name="Title" Value="My Default Page" />
  <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx, ~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx;" />
  <Property Name="ContentType" Value="$Resources:cmscore,contenttype_page_name;" />
</File>
     
<File Url="Stylish.aspx" NavBarHome="false" Type="GhostableInLibrary">
<Property Name="Title" Value="My Stylish Page" />
<Property Name="PublishingPageLayout"
Value="~SiteCollection/_catalogs/masterpage/ MyPageLayout.aspx, ~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx" />
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_page_name;" />
</File>
</Module>

If you try above case then you will get this error, unable to insatiate module ……
Well what’s the problem?
Yes , I was trying to add page named Stylish.aspx with virtual url Stylish.aspx which never exist
If you open any site template directory under [14]\Layouts\Template\SiteTemplate then you will find default.aspx and that’s the reason why SharePoint needs that
SharePoint searches file named default.aspx on file system and then provisions file to SharePoint site


Solution:
So above error can be solved like using a Name attribute of File Element

<Module Name="SampleModule" Url="$Resources:cmscore,List_Pages_UrlName;" Path="">

<File Url="Default.aspx" NavBarHome="false" Type="GhostableInLibrary">
  <Property Name="Title" Value="My Default Page" />
  <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx, ~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx;" />
  <Property Name="ContentType" Value="$Resources:cmscore,contenttype_page_name;" />
</File>
     
<File Name="Stylish.aspx" Url="Default.aspx" NavBarHome="false" Type="GhostableInLibrary">
<Property Name="Title" Value="My Stylish Page" />
<Property Name="PublishingPageLayout"
Value="~SiteCollection/_catalogs/masterpage/ MyPageLayout.aspx, ~SiteCollection/_catalogs/masterpage/MyPageLayout.aspx" />
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_page_name;" />
</File>
</Module>

Custom Variation Landing page


If you have gone through part 1 of this series then you will understand how Microsoft has implemented their variation landing page and logic
We had a requirement to implement custom variation landing page, I used word custom because we didn’t want user to get redirected using browser’s culture, instead we were using one language property in user’s profile (SharePoint User Profiles) and using that user should get redirected to actual variation site
Example: suppose a user has his language set as German, and then user should get redirected to German Variation site
Approach:
Obviously we needed to do some customization but how? we went through msdn link and got three choices as mentioned in link to create custom landing page , but out of those all we didn’t follow approach to edit OOB VariationRootLanding user control , because If you do so then you need to do those customizations on each web front end and If any service pack may override your changes
So, we decided to follow how Microsoft done , like creating custom user control , creating a custom page layout on which our user control will be and a custom publishing page using our page layout , and after all of this , set this page as welcome page of root site
see how:

Creating Custom User Control – CustomVariationLandingRoot user control


Microsoft has followed inline coding approach while they implemented their control I don’t know why
We created   a user control and added this code to code behind
What this code basically does is, maintains a Dictionary object consisting of VaraitionLabel’s language as key and VariationLabels’s web url as value, and then tries to get user’s language and uses as key to do search in dictionary object, If found then we get variation web url as value and If not then we are assigning redirection web url as source label
I will try to demonstrate this code in sample webpart you can add this code to your user control’s code behind with some changes
in code I have used hardcoding for user’s language as German , you can write your own code to get user’s language from user profile’s or according to changes you want

private ReadOnlyCollection<VariationLabel> _allLabels = null;
private Dictionary<string, string> _languageToUrl = null;
private string _language = string.Empty;
string _sourceUrl = string.Empty;
string _redirectUrl = string.Empty;

protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e);
  try
  {
   using (SPSite site = new SPSite(SPContext.Current.Site.ID))
   {
     using (SPWeb web = site.RootWeb)
     {
      // This Tries to Get all User Accessible Labels
      //and for which Variation Hierarchies have been created succesfully

      _allLabels = Variations.Current.UserAccessibleLabels;

      if (_allLabels != null && _allLabels.Count > 0)
      {

       _languageToUrl = new Dictionary<string, string>();

       //Dictionary Initialization

       foreach (VariationLabel _label in _allLabels)
       {
         //Check If Source Label , If yes then set source
         //web url to variable
         if (_label.IsSource)
         {
           _sourceUrl = _label.TopWebUrl;
         }

         //Check whether key exists or not If no then add
         if (!_languageToUrl.ContainsKey(_label.Language))
         {
           //Create CultureInfo From Label

           CultureInfo _cultureInfo = CreateCulture(_label);

           if (_cultureInfo != null)
           {
             string[] _displayLanguage = _cultureInfo.EnglishName.Split('(');
                                           _languageToUrl.Add(_displayLanguage[0].Trim(), _label.TopWebUrl);

           }
          }
         }

         //Searching for Key

         _language = "German";

          _redirectUrl = _sourceUrl;

          if (!string.IsNullOrEmpty(_language))
          {
           if (_languageToUrl.ContainsKey(_language))
           {
             _redirectUrl = _languageToUrl[_language];
           }
          }

           //Standard SP Redirection
           SPUtility.Redirect(_redirectUrl, SPRedirectFlags.Trusted, HttpContext.Current);
          }

          }
         }
        }

        catch (Exception ex)
        {
          this.Controls.Add(new LiteralControl(ex.Message));
        }
        }

//Private Function to get Culture Info from Label
        private CultureInfo CreateCulture(VariationLabel label)
        {
            CultureInfo _ci = null;

            if (label != null)
            {
                string _localeId = label.Locale;

                if (!string.IsNullOrEmpty(_localeId))
                {
                     _ci = new CultureInfo(Convert.ToInt32(_localeId), false);
                }
            }

            return _ci;
        }

after all code is done , we created a custom page layout and placed this user control directly , then final step was to create a custom publishing page using page layout we made , after that we just need to set this newly created page as welcome page of root site
How to set welcome page of site, I already posted on this so you can refer this link
and that’s all .. I hope this helps someone