Thursday, April 14, 2016

Using ARR to enable FXM


Ever used Sitecore's Federated Experience Manager (FXM)? Effectively, it lets you use Sitecore to content manage, track, personalize and test external sites which are not hosted in Sitecore.

The motivations I often hear for using FXM are...

  • We've bought a license and plan to migrate to Sitecore later, but we want to start personalizing and gathering analytics on our site now.
  • We're moving our main site to Sitecore, but we have some related sites we just don't have time and budget to move now.
  • We want to do a demo or POC using content from a non-Sitecore site, but don’t want to re-create the content in Sitecore.

With FXM you can do that. All you need is to place a small bit of script on the external sites. Sadly, that's often not possible. Sometimes the old site is literally on a server that nobody knows how to access. Sometimes you're just doing a POC and nobody wants to edit the old site for that.

IIS's Application Request Routing (ARR) to the rescue.


IIS has features called ARR and the URL Rewrite module that amount to a reverse proxy that allows you have a “man in the middle” that can manipulate the HTML before it is returned to the browser.

We set up a IIS instance with ARR with a public-facing URL (in this example, “demo.mysite.com”), and configure ARR to do the following

  1. Take the path from the inbound request, and form a URL using the Sitecore server’s host name.
  2. Fetch the HTML from the Sitecore host.
  3. Inject the FXM beacon script into the HTML
  4. Change the URLS within the HTML for such things as images, scripts, CSS, iframes, etc, so that they will be requested from the ARR and not the Sitecore site.
  5. Strip out the “X-Frame-Settings” header (if it exists), which can interfere with FXM Experience Editor.

This results in a topology like this:



The URL structure in this example would give us a demo/POC website (“demo.mysite.com”) where we can show how a site can be tracked and manipulated with FXM. This could in theory be used for a live site by changing DNS to point www.mysite.com to the ARR, and change the hostname of the Sitecore server to something like Sitecore.mysite.com.

Setting up the Reverse Proxy

From an infrastructure perspective, setting up the proxy server is pretty simple. Install the ARR and URL Rewrite extensions, and create a new site in IIS. Set the binding up so it answers requests from the desired host (in the example above, “demo.mysite.com”). The site folder doesn’t need much; a default.htm page, and an empty web config.

The magic all happens in web.config. The URL Rewrite module is governed by rules. There are two sets, one just called “rules” which are used to route requests to the Sitecore server, and another called “outbound rules” which are used to manipulate the responses from Sitecore before they are returned to the browser. Outbound rules also allow you to define “preconditions” that allow you to restrict when an outbound rule will apply.

The IIS management console provides an interface for building up all the XML in the config file for all of this. I find that when I’m working with it, I flip between IIS and Notepad++ until I get everything just right.

The referenced articles provide good guidance for how to use the URL Rewrite module and set up rules. This example web.config could be used to implement our example.


 <?xml version="1.0" encoding="utf-8"?>  
 <configuration>  
  <system.web>  
  </system.web>  
  <system.webServer>  
   <rewrite>  
    <rules>  
     <!--  
     This rule routes requests everything to the external site.  
     The use of "HTTP_ACCEPT_ENCODING" ensures that external servers   
     will send responses in the clear (not zipped or otherwise encoded)  
     -->  
     <rule name="Route to external site" stopProcessing="true">  
      <match url="(.*)" />  
      <action type="Rewrite" url="http://www.mysite.com/{R:1}" />  
      <serverVariables>  
       <set name="HTTP_ACCEPT_ENCODING" value="" />  
      </serverVariables>  
     </rule>  
    </rules>  
    <outboundRules>  
     <!--  
     This rule converts proxied pages' urls to relative urls (so they'll be requested through the ARR server and avoid cross-domain issues)  
     -->  
     <rule name="Rewrite External Absolute Paths" preCondition="Request is for html">  
      <match filterByTags="A, Area, Base, Form, Frame, Head, IFrame, Img, Input, Link, Script" pattern="^http(s)?://www.mysite.com/(.*)" />  
      <action type="Rewrite" value="/{R:2}" />  
     </rule>  
     <!--  
     This rule removes the X_Frame_Options header, which can prevent the Experience editor from working.  
     -->  
     <rule name="Strip x-frame-options" preCondition="Request is for html" patternSyntax="ECMAScript">  
      <match serverVariable="RESPONSE_X_Frame_Options" pattern="(.+)" />  
      <action type="Rewrite" value="" />  
     </rule>  
     <!--  
     This rule removes adds "(via proxy)" to the Server header, to aid troubleshooting.  
     -->  
     <rule name="Change Server Header">  
      <match serverVariable="RESPONSE_Server" pattern="(.+)" />  
      <action type="Rewrite" value="{R:0} (via proxy)" />  
     </rule>  
     <!--  
     This rule injects the FXM script into the HTML from the external site.  
     -->  
     <rule name="Add FXM script to tb" preCondition="Request is for html" patternSyntax="ExactMatch">  
      <match filterByTags="None" pattern="&lt;/head>" />  
      <action type="Rewrite" value="&lt;script src=&quot;//sitecore.mysite.com/bundle/beacon&quot;>&lt;/script>&quot;/head>" />  
     </rule>  
     <preConditions>  
      <!--  
      This precondition allows the outbound rules to only act on html responses.  
      -->  
      <preCondition name="Request is for html">  
       <add input="{RESPONSE_CONTENT_TYPE}" pattern="text/html" />  
      </preCondition>  
     </preConditions>  
    </outboundRules>  
   </rewrite>  
  </system.webServer>  
 </configuration>  

Tuesday, February 2, 2016

Dear John...

We all go West
No I’m not writing to say I’ve left you for another CMS. But John West’s announcement today leaves me wanting to take a short detour down Memory Lane. If you came here looking for technical tidbits, I’ll be hangin’ a right back down Architecture Avenue shortly.

I had the great good fortune to work directly with John on my very first Sitecore project. It was one of the first projects to be done at scale in North America, and it was my first foray into a true enterprise-level .net CMS. When Lars Nielsen flew out to conduct our first training, John was there, both to learn and to advise. He remained tightly connected throughout the project, providing strategic advice and technical leadership (and answers to my incessant questions). John’s enthusiasm for Sitecore was infectious. His spirit of adventure set the tone for that project, and indeed for my entire Sitecore career.

John’s thought leadership has been at the bedrock of Sitecore’s growth. His quiet, unassuming tone underlies a deep passion for Sitecore. Owing to John’s example, today’s Sitecore ecosystem is infused with a sense of excitement, wonder, and a craving to learn, create and explore. His blog is a hallmark of his motivational style. John provides the signposts leading to the new and evolving capabilities of the product, while never asserting his knowledge is definitive, never assuming his observations are comprehensive, and never insisting his conclusions are absolute. Being the good teacher he is, he leaves application as an exercise for the student. And exercise we do! Many talented Sitecore professionals share valuable learnings from their Sitecore journeys. But those journeys began with John’s unspoken challenge to “Go West, young man!” (Yes, I went there.)

Over the years, as John as gone from teacher to mentor to friend, I’ve felt immense pride to be part of this dynamic community that John was so instrumental in creating. Though we have gone from speaking almost every day to interacting only sporadically, every time we see each other it seems we are picking up in mid-sentence. There has never been a “goodbye” with him, and there is not one now. Talk to you soon, friend!

(And goodbye forever, XSLT!)

Tuesday, August 4, 2015

Organizing the Language Menu's Tower of Babel

If you have a site with lots of languages, you've no doubt run into some challenges. A small -- but annoying -- one, is the ordering of the language menus. This is particularly problematic when the site has a large number of configured languages, but any given conten item may only have versions in one or two. It can be a chore slogging through the menu looking for ones that have versions.

This little tidbit allows you to re-order the language menu so that languages that actually have versions will float up to the top of that Tower of Babel.

Finding the pressure point

When I'm making changes to Sitecore's "internals" I try to be as minimally invasive as possible. Unfortunately, this solution is more like a tourniquet than a pressure point. I want to replace the code-beside for one of Sitecore's XML controls (the control that Sitecore uses for drop-down language menus in the content editor). The only way I know to do that is to replace the XML file and change the <CodeBeside> element. Anyone know a good trick to change the code-beside without replacing this file?

Worse, the decompiled code-beside doesn't seem to lend itself to a surgical strike that just changes one little part. Often, you'll find that Sitecore anticipates your need by doing things like having an overridable class fetch an object that does most of the work. You can just inherit from their class and change that one method to suit your needs.Not the case here. We're going to have to implement the whole class just to change one line of code. Such is life.

The control for this menu is located at
/Sitecore/Shell/Applications/Content Manager/Galleries/Languages/Gallery Languages.xml
What we really want to do is make a small change to the associated code-beside. First, we need to change this XML file to use our code-beside instead of Sitecore's:

    <!--<CodeBeside Type="Sitecore.Shell.Applications.ContentManager.Galleries.Languages.GalleryLanguagesForm,Sitecore.Client"/>-->  
    <CodeBeside Type="MySolution.Shell.Applications.ContentManager.Galleries.GalleryLanguagesForm,sb1"/> 

For the code-beside file, we need to copy the entire decompiled Sitecore class, although we're really just changing one line of code. So use your favorite decompiler to snag the Sitecore class (or copy the code from the end of this article), and clean up the references.

I'm going to change that one pesky line to call a new method, just to isolate the change and make it more tweakable in the future.

To fetch the list of languages to put in the menu, Sitecore just does a  GetLanguages(currentItem).


That works, but I want to take the languages for which there are actually versions, and float them to the top.


So I change their code to call my method instead of GetLanguages:

 foreach (Language language in GetLanguages(currentItem))  //currentItem.Languages)  

... and then I add the GetLanguages() method that lets me be a it more finessed about ordering:

 protected IEnumerable<Language> GetLanguages(Item {  
  return currentItem.Languages.Where(l => ItemManager.GetVersions(currentItem, l).Count > 0)  
   .Union(currentItem.Languages.Where(l => ItemManager.GetVersions(currentItem, l).Count == 0));  
 }  

And now the languages that are actually used for this item will float to the top. This might be a handy place to do other manipulation you might need for your solution, depending on the business rules for language management.

Here's the full code for the XML control and the code-beside:

 using System;  
 using System.Collections.Generic;  
 using System.Globalization;  
 using System.Linq;  
 using Sitecore;  
 using Sitecore.Configuration;  
 using Sitecore.Data;  
 using Sitecore.Data.Items;  
 using Sitecore.Data.Managers;  
 using Sitecore.Diagnostics;  
 using Sitecore.Globalization;  
 using Sitecore.Shell;  
 using Sitecore.Web;  
 using Sitecore.Web.UI.HtmlControls;  
 using Sitecore.Web.UI.Sheer;  
 using Sitecore.Web.UI.XmlControls;  
 using Control = System.Web.UI.Control;  
 namespace MySolution.Shell.Applications.ContentManager.Galleries  
 {  
   public class GalleryLanguagesForm : Sitecore.Shell.Applications.ContentManager.Galleries.GalleryForm  
   {  
     protected GalleryMenu Options;  
     protected Scrollbox Languages;  
     public GalleryLanguagesForm() : base()  
     {  
     }  
     public override void HandleMessage(Message message)  
     {  
       Assert.ArgumentNotNull((object)message, "message");  
       if (message.Name == "event:click")  
         return;  
       this.Invoke(message, true);  
     }  
     protected override void OnLoad(EventArgs e)  
     {  
       Assert.ArgumentNotNull((object)e, "e");  
       base.OnLoad(e);  
       if (Context.ClientPage.IsEvent)  
         return;  
       Item currentItem = GetCurrentItem();  
       if (currentItem == null)  
         return;  
       using (new ThreadCultureSwitcher(Context.Language.CultureInfo))  
       {  
         foreach (Language language in GetLanguages(currentItem))  //currentItem.Languages)  
         {  
           ID languageItemId = LanguageManager.GetLanguageItemId(language, currentItem.Database);  
           if (!ItemUtil.IsNull(languageItemId))  
           {  
             Item obj = currentItem.Database.GetItem(languageItemId);  
             if (obj == null || !obj.Access.CanRead() || obj.Appearance.Hidden && !UserOptions.View.ShowHiddenItems)  
               continue;  
           }  
           XmlControl xmlControl = ControlFactory.GetControl("Gallery.Languages.Option") as XmlControl;  
           Assert.IsNotNull((object)xmlControl, typeof(XmlControl));  
           Context.ClientPage.AddControl((Control)this.Languages, (Control)xmlControl);  
           Item obj1 = currentItem.Database.GetItem(currentItem.ID, language);  
           if (obj1 != null)  
           {  
             int length = obj1.Versions.GetVersionNumbers(false).Length;  
             string str1;  
             if (length != 1)  
               str1 = Translate.Text("{0} versions.", (object)length.ToString());  
             else  
               str1 = Translate.Text("1 version.");  
             string str2 = str1;  
             CultureInfo cultureInfo = language.CultureInfo;  
             xmlControl["Header"] = (object)(cultureInfo.DisplayName + " : " + cultureInfo.NativeName);  
             xmlControl["Description"] = (object)str2;  
             xmlControl["Click"] = (object)string.Format("item:load(id={0},language={1},version=0)", (object)currentItem.ID, (object)language);  
             xmlControl["ClassName"] = !language.Name.Equals(WebUtil.GetQueryString("la"), StringComparison.OrdinalIgnoreCase) ? (object)"scMenuPanelItem" : (object)"scMenuPanelItemSelected";  
           }  
         }  
       }  
       Item obj2 = Sitecore.Client.CoreDatabase.GetItem("/sitecore/content/Applications/Content Editor/Menues/Languages");  
       if (obj2 == null)  
         return;  
       this.Options.AddFromDataSource(obj2, string.Empty);  
     }  
     protected IEnumerable<Language> GetLanguages(Item currentItem)  
     {  
       return currentItem.Languages.Where(l => ItemManager.GetVersions(currentItem, l).Count > 0)  
         .Union(currentItem.Languages.Where(l => ItemManager.GetVersions(currentItem, l).Count == 0));  
     }  
     private static Item GetCurrentItem()  
     {  
       string queryString1 = WebUtil.GetQueryString("db");  
       string queryString2 = WebUtil.GetQueryString("id");  
       Language language = Language.Parse(WebUtil.GetQueryString("la"));  
       Sitecore.Data.Version version = Sitecore.Data.Version.Parse(WebUtil.GetQueryString("vs"));  
       Database database = Factory.GetDatabase(queryString1);  
       Assert.IsNotNull((object)database, queryString1);  
       return database.GetItem(queryString2, language, version);  
     }  
   }  
 }  


 <?xml version="1.0" encoding="utf-8" ?>  
 <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense" xmlns:shell="http://www.sitecore.net/shell">  
  <Gallery.Languages>  
   <Gallery>  
    <!--<CodeBeside Type="Sitecore.Shell.Applications.ContentManager.Galleries.Languages.GalleryLanguagesForm,Sitecore.Client"/>-->  
    <CodeBeside Type="sb1.Shell.Applications.ContentManager.Galleries.GalleryLanguagesForm,sb1"/>  
    <Script>  
     window.onload = function() {  
     var activeLanguage = document.querySelector('.scMenuPanelItemSelected');  
     activeLanguage.scrollIntoView(false);  
     }  
    </Script>  
    <Stylesheet Key="GalleryLanguages">  
     .scMenuPanelItem, .scMenuPanelItem_Hover, .scMenuPanelItemSelected_Hover, .scMenuPanelItemSelected {  
     padding-left: 0;  
     padding-right: 0;  
     padding-top: 8px;  
     padding-bottom: 8px;  
     }  
     .scGalleryGrip {  
     position: absolute;  
     bottom: 1px;  
     left: 1px;  
     right: 1px;  
     height: 10px;  
     }  
     .scLanguagesGalleryMenu {  
     overflow: hidden;  
     vertical-align: top;  
     border-bottom: 12px solid transparent;  
     -moz-box-sizing: border-box;  
     box-sizing: border-box;  
     width: 100%;  
     height: 100%;  
     border-collapse: separate;  
     }  
     div#Languages img {  
     display: none;  
     }  
    </Stylesheet>  
    <Border Width="100%" Height="100%">  
     <GalleryMenu ID="Options" Class="scLanguagesGalleryMenu">  
      <MenuPanel Height="100%">  
       <Scrollbox ID="Languages" Class="scScrollbox scFixSize scFixWidthInsideGallery" style="padding-top:0 !important;" Height="100%" Width="100%" />  
      </MenuPanel>  
     </GalleryMenu>  
     <Gallery.Grip />  
    </Border>  
   </Gallery>  
  </Gallery.Languages>  
 </control>  


Wednesday, May 28, 2014

Size Doesn’t Matter: Suppressing Size Attributes in Image Tags



On a recent, massively-responsive project, our front-end developer asked us to (well, he actually threatened to hold his breath until he turned blue unless we would) remove the height and width attributes from the image tags in our Sitecore site. He’s one of the best front-end guys I've ever worked with, so instead of just dismissing this as “front-end guys will be front-end guys”, I decided to see if we could indulge him.

It makes sense, actually. Client-side code can deal manipulating height and width easily enough when rendering on different devices. But it you want the thrill of waving around the resize handle and watching all that responsiveness a’responding, then it’d be better if the height and width attributes weren't there in the first place.

By and large, there are two ways an image tag finds its way into a page generated by Sitecore. It may come from an image field, or from an embedded image in an HTML field. So we should be able to tackle this in the renderField pipeline, assuming we've been good boys and girls and used field renderers everywhere (and if you haven’t, then go to your room and think about what you've done).

The two cases (image fields and html fields) pose different challenges, so let’s look at them separately. Or if you don’t want to dig in, then you can stop reading now, download the module package or source code (zip file, GitHub, or Sitecore Marketplace) and have at it.

Image Fields

We've got images in image fields, and we’re using field renderers to generate image tags at runtime. Sitecore uses Sitecore.Pipelines.RenderField.GetImageFieldValue to do this. When we take a look under the hood, we see it in turn is using a Sitecore.Xml.Xsl.ImageRenderer. Luckily, GetImageFieldValue uses a virtual method CreateRenderer, so we can shim in our own class that inherits from GetImageFieldValue and override CreateRenderer to substitute our own handy-dandy ImageRenderer class.

Now, Sitecore's ImageRenderer class is pretty bulky, and it has more stuff that deals with dimensions than a cartographer’s workshop. I’d like to just inherit from theirs and find a good pressure point to slap down those size attributes. Taking a good look at the Render method in Sitecore’s ImageRenderer, it looks like someone anticipated our need. The last thing it does to determine the size is to call a virtual method called AdjustImageSize. All we need to do is override that and set the height and width properties to zero. The existing Sitecore code is already set up to suppress the height and width attributes if these properties are zero.

So we need two pretty lightweight classes and an override in a config file. First, the config file. We need to tell Sitecore to replace its GetImageFieldValue processor with ours.

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">  
  <sitecore>  
   <pipelines>  
    <renderField>  
     <processor   
      type="DimensionlessImages.Pipelines.RenderField.GetImageFieldValue,   
         DimensionlessImages"  
      patch:instead="*[@type='Sitecore.Pipelines.RenderField.GetImageFieldValue,  
         Sitecore.Kernel']"  
      />  

Then, there’s our own GetImageFieldValue processor, which inherits from Sitecore’s and overrides the CreateReplacer method.

 namespace DimensionlessImages.Pipelines.RenderField  
 {  
  using Sitecore.Xml.Xsl;  
  public class GetImageFieldValue : Sitecore.Pipelines.RenderField.GetImageFieldValue  
  {  
   protected override ImageRenderer CreateRenderer()  
   {  
    return new DimensionlessImages.ImageRenderer();  
   }   
  }  
 }  

And lastly, there’s our own ImageRenderer, which overrides the AdjustImageSize method.

 namespace DimensionlessImages  
 {  
  using Sitecore.Data.Fields;  
  public class ImageRenderer : Sitecore.Xml.Xsl.ImageRenderer  
  {  
   protected override void AdjustImageSize(ImageField imageField, float imageScale, int imageMaxWidth, int imageMaxHeight, ref int w, ref int h)  
   {  
    w = 0;  
    h = 0;  
   }  
  }  
 }  

Piece of cake, that.

HTML Fields

HTML fields are a different animal. We've got a hunk of existing html, not just some data we’ll use to form HTML at runtime.

When a media library image is inserted in the rich text editor, Sitecore adds the height and width attributes to the img tag. So between that, and the possibility that content people might edit HTML manually (bless their little programmer-wannabe hearts), we’re going to have lots of height and width attributes in our HTML fields.

We can use the HtmlAgilityPack to strip these attributes off the img tags in in the renderField pipeline. Although the HtmlAgilityPack is wicked fast, we could start to see a performance hit of there are lots of HTML fields on complex pages. It’d better to do it in a save handler or a publishItem processor, but that could present problems with the page editor or if there is already a lot of existing content. I’m seeing times of less than 0.1ms per HTML field to strip the tags at runtime, so to make this code more bullet-proof (well, ok, to let me be lazy) I’m going to do it in renderField. If your solution permits, by all means move this processing to a save handler.

Like before, we’ll start with the config changes…

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">  
  <sitecore>  
   <renderField>  
    <processor type="DimensionlessImages.Pipelines.RenderField.GetFieldValue, DimensionlessImages"  
      patch:instead="*[@type='Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel']"  
    />  

We’ll use a custom GetFieldValue processor. It is actually simpler than the image field case, because all we have to do is catch “rich text” fields and strip the height and width attributes.

 namespace DimensionlessImages.Pipelines.RenderField  
 {  
  using Sitecore.Pipelines.RenderField;  
  public class GetFieldValue : Sitecore.Pipelines.RenderField.GetFieldValue  
  {  
   public new void Process(RenderFieldArgs args)  
   {  
    base.Process(args);  
    if (args.FieldTypeKey == "rich text")  
    {  
     Sitecore.Diagnostics.Profiler.StartOperation("Stripping image tags from field: " + args.FieldName);  
     args.Result.FirstPart = HtmlUtil.StripDimensions(args.Result.FirstPart);  
     Sitecore.Diagnostics.Profiler.EndOperation();  
    }  
   }  
  }  
 }  

Finally, we need a helper method uses the HtmlAgilityPack to strip the dimension attributes…

 namespace DimensionlessImages  
 {  
  using System;  
  using HtmlAgilityPack;  
  public class HtmlUtil  
  {  
   public static string StripDimensions(string text)  
   {  
    if (string.IsNullOrWhiteSpace(text))  
    {  
     return text;  
    }  
    string outText = text;  
    try  
    {  
     var doc = new HtmlDocument();  
     doc.LoadHtml(outText);  
     StripAttribute(doc, "width");  
     StripAttribute(doc, "height");  
     outText = doc.DocumentNode.WriteContentTo();  
    }  
    catch (Exception)  
    {}  
    return outText;  
   }  
   private static void StripAttribute(HtmlDocument doc, string attribute)  
   {  
    // For reasons surpassing all understanding, HtmeAgilityPack returns null instead of an empty collection  
    // when the query finds no results.  
    HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes(String.Format("//img[@{0}]", attribute));  
    if (nodes == null || nodes.Count.Equals(0))  
    {  
     return;  
    }  
    foreach (HtmlNode node in nodes)  
    {  
     node.Attributes[attribute].Remove();  
    }  
   }  
  }  
 }  

- - -

This code addresses the most likely cases that emit image tags to the browser. A given solution may have other cases, like stuff being generated by custom code or copied from static files. For cases like that, we've provided the static method that custom code can use to “play ball” with the rest of this code.

Download: Module (package only) or source code (zip file, GitHub, or Sitecore Marketplace).






Friday, November 29, 2013

Sitecore Profiling and Tracing

It's been a while since I've posted. I have a couple of bigger subjects on the spike, but for now here's a quick word about Sitecore profiling and tracing.

Have you ever had to debug a problem with a Sitecore page and you wind up slogging through the log, trying to find the related error? Or have you ever wondered which of your renderings or sublayouts might be causing performance issues? Or maybe you have custom logic in a pipeline processor, and you wish there was an easy way to emit debugging or performance information?

There are a couple of underutilized features in Sitecore that can be a real time saver in situations like this: Sitecore.Diagnostics.Tracer and Sitecore.Diagnostics.Profiler. These classes are sitting right next to the much-used Sitecore.Diagnostics.Log class, but are often overlooked. Both of these classes allow you to emit information directly to the page when in debug mode. This is a far more convenient and useful way to do debugging than writing to the Sitecore log.

The Profiler class is useful for tracking the progress of the page rendering cycle, and for gathering performance data about the components and code being executed. It has the usual Info, Warning and Error static methods you are probably familiar with from the Log class, but the real gems here are the StartOperation and EndOperation methods. With them, you can gather valuable performance data about parts of the rendering process, including both large chunks of a process and nested inner pieces of the process. For example, the Arke MetaTag Manager  module emits quite a bit of information about it's internal workings to the debugger.


The information provided not only helps us understand the performance impact of this process, but also helps us troubleshoot when we suspect that a tag is not being gathered properly.

To output this information, it's a simple matter of adding a two lines of code. For example, this is the Process method used for the CustomTag pipeline processor:


public void Process(InjectMetaTagsPipelineArgs args)
{

  Sitecore.Diagnostics.Profiler.StartOperation(
    "Adding custom tag '" + GetSignatureName() + "'"
    );

  try
  {
    Assert.ArgumentNotNullOrEmpty(
      TypeSignature, 
      "Class not supplied"
      );
    args.MetaTags.Add(ReflectionUtil.GetCustomTag(TypeSignature));
  }
  catch (Exception ex)
  {
    Sitecore.Diagnostics.Tracer.Error(
      "CustomTag failed.", 
      ex
      );
    Sitecore.Diagnostics.Log.Error(
      "CustomTag failed.", 
      ex, 
      "CustomTag"
      );
  }
  finally
  {
    Sitecore.Diagnostics.Profiler.EndOperation();
  }
}


Sitecore will nest sets of profiling blocks, which makes it easy to trace through a process to see where a problem or performance hit may be.

The other handy class for "in-page" debugging is the Tracer class. Again , this class has the familiar Info, Warning and Error static methods, but the output from these methods is written to the "Trace" section of the page debugger. I use these methods for writing out error or info strings much the way I would to the Sitecore Log, but they're written to the page itself, where troubleshooting is much easier. It's always nice to just be able to turn on the debugger to see this info instead of having to search through the log file.

Again, looking at the sample code above, note that the "catch" statements include a line of code to write the error to the tracer, which would yield this:

That beats searching for the error on the log file ... or worse, throwing a "yellow screen".

Happy tracing!


Wednesday, July 3, 2013

Hiding fields in the Content Editor



The source code, package and documentation for this module are available from the Sitecore Marketplace.
Anyone who has worked much with the Sitecore Content Editor knows that there are a bunch of "standard" fields that Sitecore usually keeps hidden. These fields are either system-managed, or are rarely used and typically only changed by developers or administrators. With proper permissions, you can see these fields (View > Standard Fields), but they are usually turned off to reduce clutter.

Sometimes I want to hide some of the fields that are created as part of my solutions. This may include:
  • Fields that are intended only for administration. To avoid clutter, these fields should be hidden unless the content editor is showing standard (system) fields.
  • Fields that are populated by handlers. This could include fields that are populated by a save handler and should not be edited.
  • Fields that are populated by a publishing processor, to aggregate information or provide hints or shortcuts to the CD, to improve performance or to pass along other data.
  • Fields that are populated or reconciled by a data importer, which should not be edited.

I've created a module (available from the Sitecore Marketplace) to do just that.

This module extends Sitecore to allow developers to designate when a field should be hidden from the content editor. The options are to (1) always show the field, (2) always hide the field, or (3) only show the field when the content editor is showing standard (system) fields.

The module extends the “GetContentEditorFields” pipeline and also extends the Template field template that Sitecore uses for managing template fields in the content editor. A description of the techniques underlying this are included a bit farther down.

Installing the module

There are three key pieces required for this module: the assembly, the config file and Sitecore content items. These are all included in the package Arke.Templates.zip. To install this module, simply upload this package to Sitecore and install it normally.

If security prevents the package installer from deploying the assembly (.dll) file or the Arke.Templates.config file, these can be extracted from the zip file and deployed manually.


Using the module

Once the module is installed, you can manage the visibility of any field by changing the “Show In Content Editor” setting on any field item. The options are…
  • Always: Always show this field.
  • Never: Never show this field.
  • When showing standard fields: Show this field when Standard Fields is turned on in the ribbon (under View).


Configuration options

The Arke.Templates.Config file allows the system administrator to set how the module behaves. “HideEmptySections” governs how to handle content editor sections when all of the fields within that section have been hidden:

<!-- Hide empty sections
Should the template section be hidden entirely if all of the fields
within it are hidden
-->
<setting name="Arke.Templates.HideEmptySections" value="true" />


How the module works

The module works by extending the GetContentEditorFields pipeline.

Like many other Sitecore pipelines, GetContentEditorFields maintains a collection in its PipelineArgs class that is populated by a series of processor items. When Sitecore is rendering the content panel, it uses this pipeline to gather all of the fields and sections that it needs to display. As each processor runs, it adds fields and sections to a collection, which Sitecore uses at the end of the pipeline to form the editing panel.

When developing a pipeline processor in a situation like this, it is tempting to replace Sitecore’s processor with your own, in order to control what data is being added to the args object. However, this often requires extensive duplication of Sitecore’s code, as many of the classes in the Sitecore namespace are not public or not static, and so must be replicated in the custom processor. It also means the custom processor is less “future-proof”. If a future Sitecore release changes the processor you replaced, you will have to re-create your custom processor to reflect Sitecore’s changes.

The architecture of this module leaves Sitecore’s GetContentEditorFields pipeline processors intact, and adds a new custom processor to the end of the pipeline (the namespaces below have been truncated for readability):

<pipelines>
  <getContentEditorFields>
    <processor
      type="Arke…TrimHiddenFields, Arke.SharedSource.Templates"
      patch:after="processor[@type='Sitecore...GetExplicitFields, Sitecore.Client']
    />
  </getContentEditorFields>
</pipelines>

When the default processors have completed, the args class contains all of the fields and sections gathered by the standard processors. The TrimHiddenFields processor then iterates over all of the content sections, examining all the fields to see if their “hide” box is checked, and removing them as appropriate. It them removes the section if it no longer has visible fields.

public void Process(GetContentEditorFieldsArgs args)
{
  Assert.ArgumentNotNull(args, "args");
  if (args.Sections == null || args.Sections.Count == 0)
  {
    return;
  }
  if (Settings.HidingCheckedFields == HideCheckedField.Never)
  {
    return;
  }
  this.args = args;
  for (int sectionIndex = 0; sectionIndex < args.Sections.Count; sectionIndex++)
  {
    Editor.Section section = args.Sections[sectionIndex];
    TrimFields(section);      
    if (Settings.HidingEmptySections && section.Fields.Count == 0)
    {
      args.Sections.RemoveAt(sectionIndex);
      sectionIndex--;
    }
  }
}


private void TrimFields(Editor.Section section)
{
  for (int fieldIndex = 0; fieldIndex < section.Fields.Count; fieldIndex++)
  {
    if (FieldIsHidden(section.Fields[fieldIndex]))
    {
      section.Fields.RemoveAt(fieldIndex);
      fieldIndex--;
    }
  }
}


private bool FieldIsHidden(Editor.Field field)
{
  try
  {
    Item templateFieldItem = GetTemplateFieldItem(field, args.Item.Database);
    if (templateFieldItem == null)
    {
      return false;
    }
    bool isHiddenChecked = false;
    try
    {
      CheckboxField fld =
        (CheckboxField)templateFieldItem.Fields[Settings.HideCheckboxFieldName];
      isHiddenChecked = fld.Checked;
    }
    catch { }
    switch (Settings.HidingCheckedFields)
    {
      case HideCheckedField.Never:
        return false;
        break;
      case HideCheckedField.Always:
        return isHiddenChecked;
        break;
      case HideCheckedField.WhenHidingStandardFields:
        return isHiddenChecked && (!UserOptions.ContentEditor.ShowSystemFields);
        break;
    }
  }
  catch { }
  return false;
}
 
private Item GetTemplateFieldItem(Editor.Field field, Database database)
{
  try
  {
    return database.GetItem(field.TemplateField.ID);
  }
  catch { }
  return null;
}


It may seem wasteful to “undo” some of the work down by previous processors. However, the CPU overhead for this is pretty light, and any item data the custom processor needs (like field definition items) will have already been read (and presumably cached) by previous steps. This slight extra overhead is a small price to pay to keep this additional functionality self-contained, and to avoid replacing any of Sitecore’s default processors. Also bear in mind that this activity only happens in the content management server, where I regard performance as somewhat less critical than the content delivery instance.

More resources...




Tuesday, March 12, 2013

The Heart of the Meta

This article describes Arke System's Meta Tag Manager shared source module, available for free download from the Sitecore Marketplace.




Update 27-March-2013: Several changes have been made to this module, including adding new tag types (properties), adding support for all tag types in the pipeline, and creating "Custom Tags" to replace the less intuitive "Dynamic Processors"). See the documentation file in the Sitecore Marketplace for details.
It's almost inevitable that at some point in a solution's lifespan, there comes a need to add various types of meta tags to the head of your pages. Sometimes it's site-wide tags like turning off the silly IE image toolbar (thankfully a legacy issue now). Sometimes it's something you may need at the page level, like instructing robots not to crawl the page. Sometimes the tag values are static, like a robot directive  Sometimes they're dynamic, like page type (template), GUID or other info needed by crawlers or page script. Sometimes the need is simple, sometimes there are very complex tagging requirements for analytics or search crawling.

I've found that over time, solutions wind up with meta tags being handled in dozens of ways ... sometimes in markup, sometimes in code-behind, sometimes in base layouts, sometimes in renderings, sometimes in handlers. Discovering the providence of a tag you find in the final HTML can be challenging.

Recently, a client was concerned about the S.E.O. ramifications of having identical pages at more than one URL on their Sitecore site (for example, with and without "/en" in the path). Having pages like this (or worse, internal links formed differently) can decrease search ranking. If the same page is at two URLs, and there are links to both, the "link juice" is diluted across those pages.

One way to overcome this is to issue a "canonical URL" meta tag. If you emit a meta tag with the same canonical URL on both pages, it informs the search engine which URL is "authoritative" for that page. The search engine will (theoretically) award all of the link juice to the canonical URL.

If you are using a common base layout class, the simplest thing to do would be to have the code-behind of that class hang the canonical meta tag in the page header. But in this case, the client did not have a common base layout.

This kind of thing has come up often enough that I was motivated to create a MetaTag management module. Different chunks of code need to create meta tags, and it’s not always convenient or even possible to limit the timing of meta tag generation to the layout.

This article only covers some highlight from the module. For more detail, see the full documentation at the Sitecore Marketplace.

Overview



Tags can be added four ways:

  1. By adding processors to the InjectMetaTags pipeline.
  2. In the content editor, by adding tag definitions to a GlobalTags folder (these tags appear site-wide).
  3. In the content editor, by selecting pre-defined tags in a field that can be added to any template (these tags appear on individual pages)
  4. In code, at any point in the page lifecycle. This allows code in layouts, sublayouts and renderings to add meta tags “ad hoc”.



Several tag "types" are support to simplify creation of tags in code or configuration:


  1. Static tags: Tags where both the name and value are static, such a “robots noindex”.
  2. Custom tags: Tags where either the name, value or both are generated by custom logic.
  3. Method tags: Tags where the name is static, and the value is derived from an existing static method.
  4. Property tags: Tags where the name is static, and the value is derived from an existing static property.

Method and Property tags allow creation of dynamic tags entirely in configuration, with no coding. These tags derive their values at runttime from existing methods or properties.


There seems to be two subspecies of meta tag markup, those that use a “property” attribute and those that use “name”. Both are still effectively name/value pairs. The “name” style tag seems to be more common, but notable sets of commonly-used tags (like OpenGraph tags) use the “property” structure. This module allows both styles.

Architecture


The module uses a collection of tag description objects (not markup) that persist through the page lifecycle. Tags are added to this collection in various ways, and are flushed to the head section of the page after pre-render. To flush the tags to the page, each is formed into a custom MetaTag control. 

No modifications to the solution’s code or markup are required; all of the artifacts and event hooks are created using pipeline processors added to the httpRequestBegin and insertRenderings pipelines.

Adding Tags From Code


Coders can add tags to the page in any point in the page lifecycle, using provided convenience methods.

    PageTags.AddTag(MetaTagType.name, "MyTag", "This is my tag.");

Managing Tags in the Content Editor


Site administrators can create site-wide tags by adding "StaticMetaTag" and "DynamicMetaTag" items to the appropriate folder in the Modules section of the content tree. Items added to the "GlobalTags" folder are emitted on every page; items added to the "OptionalTags" folder are available for content authors to use selectively in their pages (see below).


The definition of a StaticMeta tag is simple; just specify the tag type ("name" or "property"), and the key and value.


To define a dynamic tag, you must first create a simple "Dynamic Processor" class that exposes properties for TagType, Key and Value. Sample Dynamic Processors are provided that emit tags for the Item ID, Template Key and Machine Name. Once this class is defined, you can add it by creating a DynamicMetaTag item and supplying the class signature for the dynamic processor.


namespace Arke.SharedSource.MetaTags.DynamicProcessors
{
  public class ItemID : IDynamicProcessor
  {
    public MetaTagType TagType { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }
 
    public ItemID()
    {
      TagType = MetaTagType.name;
      Key = "ItemID";
      Value = Sitecore.Context.Item.ID.ToString();
    }
  }
}


A base template is provided, which can be added to the base templates of any site template. This gives the content author an opportunity to include the pre-defined tags (those defined n the OptionalTags folder) in any given page.



Managing Tags in the InjectMetaTags Pipeline


The process for gathering, forming and injecting tags is itself managed with a custom pipeline. Developers can add custom tags by inserting processors into this pipeline. They can also change the behavior of the process by replacing existing processors.
For more information on how to manage processes in your solutions by creating custom pipelines, see "Put That in Your Pipe and Process It"

In addition to the processors that gather the meta tags defined in content (global and page-scoped), there are  other "demonstrator" processor included in the module. One shows how to implement a fully custom meta tag processor, using the "canonical" meta tag as an example.

There are also processors that allow you to leverage your "Dynamic Processors" or any static method (including any Sitecore static method) to add a global meta, entirely in configuration, without writing a single line of code. This is accomplished by adding processor nodes that have child nodes the define class signatures and names of the processor or static method to be used. 

Here's and example of a meta tag pipeline in config (I've shortened the type names for readability):

<Arke.MetaTags.InjectMetaTags>
  <processor type="Arke...CheckContextItem, Arke.SharedSource.MetaTags" />
  <processor type="Arke...CheckHeader, Arke.SharedSource.MetaTags" />
  <processor type="Arke...CanonialUrl, Arke.SharedSource.MetaTags" />
  <processor type="Arke...MethodTag, Arke.SharedSource.MetaTags">
    <TypeSignature>Sitecore.Context, Sitecore.Kernel</TypeSignature>
    <MethodName>GetSiteName</MethodName>
    <TagName>sc_site</TagName>
    <TagType>name</TagType>
  </processor>
  <processor type="Arke...GlobalTags, Arke.SharedSource.MetaTags" />
  <processor type="Arke...ItemTags, Arke.SharedSource.MetaTags" />
  <processor type="Arke...FlushMetaTags, Arke.SharedSource.MetaTags" />
</Arke.MetaTags.InjectMetaTags>






The source code for this module is available at the Sitecore Marketplace. You can also download the documentation file.