Thursday, November 14, 2013

Problem with SPContextPageInfo.IsWebWelcomePage on imported Sharepoint sites

I wrote several articles already about problems with SPExport/SPImport API of Sharepoint (see Using SPExport/SPImport for copying content with managed metadata and Retain object identity during export and import of subsites in Sharepoint into different site collection hierarchy location). In this post I will describe one more interesting problem which you may face with when work with mentioned API. Starting from Sharepoint 2010 there is SPContextPageInfo.IsWebWelcomePage global property, which returns true when current page is site’s welcome page. It is still not so widely used: more often developers continue to use PublishingWeb.DefaultPage and compare it with current page’s url or id, which was available from Sharepoint 2007. However the last method requires opening of PublishingWeb first, which may impact performance depending on the context where you use it, so in general I would prefer to use SPContextPageInfo.IsWebWelcomePage now.

It is not so well-known fact that OTB Sharepoint AspMenu also uses this property for determining selected menu item (it adds “selected” css class to the menu item which corresponds to the current page as you probably know). So if this property won’t work as expected, your navigation may be broken. The problem is that if site was imported from backup of other site using SPExport/SPImport API, SPContextPageInfo.IsWebWelcomePage may always return false.

This problem comes from Sharepoint internals. In order to avoid it you may use the following workaround: create custom http module which will check whether current request goes to the site’s welcome page and if yes, set it to true. Before you will use it, pay attention to the several important things:

  1. http modules are executed for all requests, including images, css and js. Keep it in mind, because it may significantly affect performance;
  2. it is not possible to change SPContextPageInfo.IsWebWelcomePage using standard object model, because it has internal setter, so reflection will be needed;
  3. the following code will work for English publishing sites only and only when they have welcome page always set to default.aspx. In your case neither of these can be true: in this case you should modify the code for your needs. The more proper way to define welcome page would be to use method with PublishingWeb.DefaultPage, but remember that it should have very good performance. So at least add caching if you will do it this way.

Here is the code of http module:

   1:  public class FixNavigationModule : IHttpModule
   2:  {
   3:      private HttpApplication app;
   4:   
   5:      public void Dispose()
   6:      {
   7:      }
   8:   
   9:      public void Init(HttpApplication application)
  10:      {
  11:              this.app = application;
  12:              this.app.PreRequestHandlerExecute += preRequestHandlerExecute;
  13:      }
  14:   
  15:      private void preRequestHandlerExecute(object sender, EventArgs eventArgs)
  16:      {
  17:          try
  18:          {
  19:              string url = this.app.Request.Url.AbsoluteUri.ToLower();
  20:              // module is executed for all file types including css and js. In order to keep good performance
  21:              // we should check only those requests which are coming to aspx pages
  22:              if (!url.Contains(".aspx"))
  23:              {
  24:                  return;
  25:              }
  26:   
  27:              if (url.Contains("/pages/default.aspx") && !SPContext.Current.ContextPageInfo.IsWebWelcomePage)
  28:              {
  29:                  // setter for SPContextPageInfo.IsWebWelcomePage is internal, so we have to use reflection here
  30:                  this.setIsWebWelcomePage(true);
  31:              }
  32:          }
  33:          catch (Exception x)
  34:          {
  35:                          // log
  36:          }
  37:      }
  38:   
  39:      private void setIsWebWelcomePage(bool isWebWelcomePage)
  40:      {
  41:          var obj = SPContext.Current.ContextPageInfo;
  42:   
  43:          BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
  44:          MethodInfo mi = obj.GetType().FindMembers(MemberTypes.Method, bf, Type.FilterName, "set_IsWebWelcomePage")[0] as MethodInfo;
  45:          mi.Invoke(obj, new object[]{ isWebWelcomePage });
  46:      }
  47:  }

After that add this http module to your web.config file on all WFE servers:

   1:  <add name="FixNavigationModule" type="MyNamespace.FixNavigationModule, MyAssembly" />

It will fix problem with SPContextPageInfo.IsWebWelcomePage property on imported site. Hope that it will help someone. If you know better solution please share it in comments.

No comments:

Post a Comment