Saturday, March 20, 2010

Set locale of current thread for publishing pages in Sharepoint

In my previous post I described how Sharepoint configures CurrentUICulture and CurrentCulture properties of current thread based on SPWeb.Language. I also wrote that described algorithm is used only for application layouts page (pages which located in 12/template/layouts folder), but not for publishing pages (or content or site pages). And I said that I will write another blog post about setting  of locale of current thread if request goes to publishing pages. And I keep my promise :)

Publishing functionality is available on sites with activated Publishing Infrastructure feature (there are several publishing features actually). By default it is activated on sites created with Publishing Site or Collaboration Site templates under Publishing category on site or site collection creation page. I would separate 2 types of pages in publishing infrastructure:

  • publishing page layouts. They are provisioned into http://example.com/_catalogs/masterpage. You can find them in Site Settings > Master pages and page layouts
  • publishing page instances – i.e. publishing pages itself. These are the pages which were created based on some publishing page layout. Most often publishing page instances are created in OTB Pages doclib (actually they should be created there because Sharepoint has several assumptions when handle publishing page instances. One of the assumption is that publishing page instance is located in Pages doclib. But this is not necessary, i.e. you can create publishing page instances in custom doclib also. In this case additional actions are required in order to avoid limitation of Sharepoint – this is out of scope of current blog post. I will write another post about it)

I.e. there is analogy with OOP: publishing page layouts are kind of “classes” of pages, and publishing page instances are “objects”. Often Sharepoint uses the following classes for mentioned pages:

  • Microsoft.SharePoint.Publishing.PublishingLayoutPage for publishing page layout pages
  • Microsoft.SharePoint.Publishing.TemplateRedirectionPage for publishing page instances

Both classes inherit PublishingCachablePage class which in turn inherits WebPartPage class. Lets see constructor of WebPartPage:

   1: public WebPartPage()
   2: {
   3:     this._exclusion = false;
   4:     HttpContext current = HttpContext.Current;
   5:     if ((current != null) &&
   6:        (SPRequestModuleData.GetRequestData(current, null, false) == null))
   7:     {
   8:         this._exclusion = true;
   9:     }
  10:     if (this._exclusion)
  11:     {
  12:         SPWeb contextWeb = SPControl.GetContextWeb(HttpContext.Current);
  13:         if (contextWeb != null)
  14:         {
  15:             CultureInfo locale = contextWeb.Locale;
  16:             CultureInfo languageCulture = contextWeb.LanguageCulture;
  17:             Utility.SetThreadCulture(locale, languageCulture, false);
  18:         }
  19:     }
  20: }

So if SPRequestModuleData.GetRequestData(…) method returns null for publishing page (in this case publishign page is treated as “exclusion” as you can see in code above) locale of current thread will be initialized from SPWeb.Locale and SPWeb.LanguageCulture of current web. But what if SPRequestModuleData.GetRequestData(…)  method will return non-null result:

   1: internal static SPRequestModuleData GetRequestData(HttpContext context,
   2:     string virtualPath, bool allowCreate)
   3: {
   4:     SPRequestModuleData data = null;
   5:     if (context != null)
   6:     {
   7:         data = (SPRequestModuleData)
   8:             context.Items[typeof(SPRequestModuleData)];
   9:     }
  10:     if (allowCreate && (data == null))
  11:     {
  12:         ...
  13:     }
  14:     else
  15:     {
  16:         return data;
  17:     }
  18:     return null;
  19: }
  20:  

As you can see in our case it GetRequestData can return non-null result if HttpContext.Items collection contains instance of SPRequestModuleData (as 3rd parameter allowCreate is false). I will not deeply ananlyze in what cases Sharepoint returns null or not-nulls from GetRequestData. For this article it is enough that MOSS treats this situation as “exclusion”, i.e. I can assume that most of publishing pages should have non-null value returned from GetRequestData in constructor. Even more, if such situation occured, i.e. if GetRequestData returned null, see that last param “force” in Utility.SetThreadCulture is false also:

   1: Utility.SetThreadCulture(locale, languageCulture, false);

I already showed code of Utility.SetThreadCulture(), but it is important for this post also, so I will show it again:

   1: internal static void SetThreadCulture(CultureInfo culture,
   2:     CultureInfo uiCulture, bool force)
   3: {
   4:     HttpContext current = HttpContext.Current;
   5:     if ((force || (current == null))
   6:         || (current.Items[IsThreadCultureSet] == null))
   7:     {
   8:         if (!Thread.CurrentThread.CurrentCulture.Equals(culture))
   9:         {
  10:             Thread.CurrentThread.CurrentCulture = culture;
  11:         }
  12:         if (!Thread.CurrentThread.CurrentUICulture.Equals(uiCulture))
  13:         {
  14:             Thread.CurrentThread.CurrentUICulture = uiCulture;
  15:         }
  16:         if (!force && (current != null))
  17:         {
  18:             current.Items[IsThreadCultureSet] = true;
  19:         }
  20:     }
  21: }

I.e. even if publishing page is treated as exclusion – locale of current thread will be set only if HttpContext.Current = null (I think it is non-common situation) or when thread culture has not been initialized yet (HttpContext.Current.Items[IsThreadCultureSet] == null). So there should be a place where language is initialized for most of publishing pages (and this is not SPRequest.PreRequestExecuteAppHandler).

After analysis of all places where SetThreadCulture methods of SPUtility and WebPartPages.Utility classes are called, the most earlier place in pipeline where SetThreadCulture is called is SPWeb.InitWeb() method:

   1: private void InitWeb()
   2: {
   3:     if (!this.m_bInited)
   4:     {
   5:         ...
   6:         this.InitLocaleAndLanguageCulture();
   7:         Utility.SetThreadCulture(this.m_Locale, this.m_LanguageCulture, false);
   8:     }
   9: }
  10:  

This method is called from constructor of SPWeb. So when current SPWeb is initialized for SPContext 1st time, Sharepoint sets locale of current thread (but with force = false in opposite to SPRequest.PreRequestExecuteAppHandler Sharepoint which calls this method with force = true. Actually there are no so many places where Sharepoint calls SetThreadCulture with force = true. I found only ThreadCultureScope class and mentioned SPRequest.PreRequestExecuteAppHandler). I.e. once SetThreadCulture  method is called Sharepoint will use locale which was used in 1st call until next call with force = true. So for publishing pages mechanism a bit different. If for _layouts pages Sharepoint sets locale of current thread on later step explicitly (SPRequest.PreRequestExecuteAppHandler), for publishing pages Sharepoint sets locale of current thread on earlier steps in constructor of context SPWeb.

This is the internal mechanism of current thread locale initialization for publishing pages in Sharepoint.

4 comments:

  1. Hi,
    great post.

    patrick
    --------------------------------------------------
    http://patricklamber.blogspot.com (SharePoint, ASP.NET)

    ReplyDelete
  2. hello Patrick,
    glad that it became helpful

    ReplyDelete
  3. Very interesting post.

    I'm looking for modify current locale depending on current language.
    My goal is to associate a locale of my choice to a language. I want to have a coherence between current language and date format for example.
    Sharepoint 2010 doesn't offer this functionnality.

    Do you think that is possible ? And how can i do this ?

    Thanks for your help

    Paul

    ReplyDelete
  4. Paul hello,
    what you mean when you say "current language" ? Language of current SPWeb ? If yes MOSS sets locale and language using the same culture in most cases. Or you mean something else when say about current language?
    Also you can check my another post: http://sadomovalex.blogspot.com/2010/04/change-current-ui-locale-using-query.html where I showed how to change current UI locale dynamically based on query string parameter

    ReplyDelete