The Simplest Way to Show Page Descriptions in SharePoint Search Results

As I mentioned in a previous post, some customers prefer to show page descriptions in search results instead of the HitHighlightedSummary rendered by default in Microsoft Office SharePoint Server (MOSS) 2007.

After all, if content authors diligently make the effort to provide a clear and concise paragraph about each page on your site, then this description may very well be more helpful than the summary automatically generated by SharePoint (or any search engine, for that matter) with search terms highlighted; or perhaps you want to show the page descriptions in addition to the default HitHighlightedSummary. ["It's your thing...do what you wanna do..."]

So, now that we have a scenario describing our business requirement, the next step is to actually make it reality.

If you've read some of my previous posts -- or if you've worked with me personally -- then you know how much I love simplicity. As a patent clerk once said:

Everything should be as simple as possible, but not simpler.

-- Albert Einstein

If you look at the default XSL for the Search Core Results Web Part in MOSS 2007, you will find the following:

     <div class="srch-Description">
      <xsl:choose>
        <xsl:when test="hithighlightedsummary[. != '']">
          <xsl:call-template name="HitHighlighting">
            <xsl:with-param name="hh" select="hithighlightedsummary" />
          </xsl:call-template>
        </xsl:when>
        <xsl:when test="description[. != '']">
          <xsl:value-of select="description"/>
        </xsl:when>
      </xsl:choose>
    </div >

Well, that certainly makes this a trivial exercise...all I need to do is reverse the order so that if description is specified, then it takes precedence over hithighlightedsummary, right?

     <div class="srch-Description">
      <xsl:choose>
        <xsl:when test="description[. != '']">
          <xsl:value-of select="description"/>
        </xsl:when>
        <xsl:when test="hithighlightedsummary[. != '']">
          <xsl:call-template name="HitHighlighting">
            <xsl:with-param name="hh" select="hithighlightedsummary" />
          </xsl:call-template>
        </xsl:when>
      </xsl:choose>
    </div >

Well, not quite.

The fact is that description will likely never be set in your environment -- even if you enter a description when creating a page. This is especially true if you used one of the out-of-the-box master pages -- like BlueBand.master --as a starting point for creating your own master page.

To understand why, let's look at the out-of-the-box Description managed property. By default, this managed property is set to Include values from a single crawled property based on the order specified and is mapped to the following crawled properties:

  • Office:6(Text)
  • DESCRIPTION(Text)
  • ows_Description(Text)
  • ows_SPSDescription(Text)
  • ows_SiteDescription(Text)
  • People:AboutMe(Text)

Surely one of these is the one we want...how about ows_Description? That seems probable. After all, anyone familiar with SharePoint knows that pages are simply stored as list items with various columns (a.k.a. fields) and when we add a custom column, such as Product, to a content type then that field is subsequently discovered as a crawled property in the SharePoint crawled property category (00130329-0000-0130-c000-000000131346) and consequently prefixed with "ows_" (for example, ows_Product).

The problem is that ows_Description does not correspond to Microsoft.SharePoint.Publishing.PublishingPage.Description. This is counterintuitive, so let's start by looking at the out-of-the-box content type hierarchy:

Figure 1: MOSS 2007 Publishing - content type hierarchy

See full-sized image.

Note that Description is inherited from the System Page content type. Here is the definition for that content type:

 <ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2"
    Name="System Page"
    Group="_Hidden"
    Version="0">
  <FieldRefs>
    <FieldRef ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" Name="ContentType" />
    <FieldRef ID="{5f47e085-2150-41dc-b661-442f3027f552}" Name="SelectFilename" />
    <FieldRef ID="{8553196d-ec8d-4564-9861-3dbe931050c8}" Name="FileLeafRef" />
    <FieldRef ID="{8c06beca-0777-48f7-91c7-6da68bc07b69}" Name="Created" />
    <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" />
    <FieldRef ID="{28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f}" Name="Modified" />
    <FieldRef ID="{822c78e3-1ea9-4943-b449-57863ad33ca9}" Name="Modified_x0020_By" />
    <FieldRef ID="{4dd7e525-8d6b-4cb4-9d3e-44ee25f973eb}" Name="Created_x0020_By" />
    <FieldRef ID="{9da97a8a-1da5-4a77-98d3-4bc10456e700}" Name="Comments" />
    <FieldRef ID="{51d39414-03dc-4bd0-b777-d3e20cb350f7}" Name="PublishingStartDate" />
    ...
  </FieldRefs>
</ContentType>

The Description field is actually Comments! This explains why the default crawled property mappings for the Description managed property fail to populate the values in search results. While you could try modifying the crawled property mappings for Description to add ows_Comments, I don't recommend it. There's a better way to accomplish what we are trying to achieve.

Recall that the second crawled property mapped to the Description managed property is DESCRIPTION(Text) -- which is from the Web crawled property category (d1b5d3f0-c0b3-11cf-9a92-00a0c908dbf1). This corresponds to meta tags in HTML pages (e.g. <meta name="description" content="..." />). Knowing this, getting the page descriptions is simply a matter of ensuring that our pages include this meta tag and populate it with the Description -- er, I mean Comments -- for each page.

Some of you may be thinking you need some custom MetaTag WebControl to do this -- based on various posts around the Internet -- but this isn't explicitly required. Just use the out-of-the-box FieldValue control instead. In your master pages, modify your <head> element to include the following:

 <asp:Literal runat="server"
      Text="&lt;meta name=&quot;description&quot; content=&quot;" /><SharePoint:FieldValue runat="server"
        FieldName="Comments" /><asp:Literal runat="server" Text="&quot;&gt;" />

[Updated (2009-08-26): Note that I originally specifed the following, but it looks like this was deemed to be a bug of some sort. Consequently, a patch -- released sometime after I originally validated the approach -- results in the first "<" character being escaped, thus it does emit the raw server tags (well, almost).]

     <meta name="description"
        content="<SharePoint:FieldValue FieldName='Comments' runat='server'/>" />

While the color syntax highlighting may lead you to think this will just emit the raw server tags, I can assure you it works (inserting the page description -- assuming the page specifies one).

Once you have this specified in your master page, start a full crawl, modify your XSL to give preference to description over hithighlightedsummary (or to show them both), and you'll be all set!