Friday, November 28, 2008

MOSS Barcode Image Timing Out in Large Document Libraries

I'm working with a few large libraries in our SharePoint farm, one of which is well over 200,000 documents and climbing.  In these libraries, we have the barcode policy turned on, however on the DispForm.aspx page for viewing a document's properties, the barcode image never loads.  Eventually after about 5 minutes, it will timeout and show a broken link like this.

brokenbarcode

In the web front end event log, this error is recorded:

0x0234  Windows SharePoint Services  Database  8sli  Monitorable    
List item query elapsed time: 371854 milliseconds, Additional data 
(if available): Query HRESULT: 80131530 List internal name, flags, 
and URL: {GUID}, flags=0x0000000004c01088, 
URL=https://[hostname]/_layouts/barcodeimagefromitem.aspx?
doc=[DocGuid]&list=[ListGuid]

After troubleshooting, searching the web, and dealing with Microsoft support all to no gain, it was decided to break a best practice and overwrite one of Microsoft's layout pages, namely the troublesome barcodeimagefromitem.aspx. To this I want to give many props to my colleague, Jamie Burkholder, who did much of the work on this fix. This is the solution we arrived at.

If you view the source for this layout page, it's not much to look at.  It primarily links to a code behind page, that we don't have the code for, so the only option is to rename the page to barcodeimagefromitem.old.aspx so we can create our own page.

Here is the code we used to replace the original.

<%@ Page language="c#" %>

<%@ Assembly Name="Microsoft.Office.Policy, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Imaging" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.Office.RecordsManagement" %>
<%@ Import Namespace="Microsoft.Office.RecordsManagement.PolicyFeatures" %>


<script runat="server">
     
        
        #region "Event Handlers"
     
        //----------------------------------------------
        //  Event Handlers
        //----------------------------------------------
     
        void Page_Load(object sender, System.EventArgs e)
       {
           string listId = Request["List"].ToString();         // collect the list guid from URL
           string docId = Request["Doc"].ToString();                // collect the doc guid from URL
           string barCodeValue = "";
    
           Guid uidListId = new Guid(listId);
           SPList targetList = null;
           SPListItem item;
           System.Drawing.Image    barCodeImage = null;
    
           
           // get list from the guid
           targetList = SPContext.Current.Web.Lists[uidListId];
                   
           // Lookup the document
           if (targetList != null)
           {
               item = targetList.GetItemByUniqueId(new Guid(docId)); 
               
               if (item != null)
               {
                   barCodeValue = Barcode.GetBarcodeValue(item);
                   barCodeImage = Barcode.GetBarcodeImage(item);  
               }
    
               Response.ContentType = "image/jpeg";
               barCodeImage.Save(Response.OutputStream, ImageFormat.Jpeg);
           }
       }    
       
       #endregion
       
</script> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> </form> </body> </html>

While developing this code, we were able to recreate the same timeout issue as the Microsoft version.  Here is what happened.  Originally, when we were collecting the SPListItem from the SharePoint API we used this line:

item = targetList.Items[new Guid(docId)];        /* Never use this!!!!! */

This works fine until you start putting thousands of documents in the same library.  When this happens, this line will timeout and you will never get your list item.  However, Microsoft knew what they were doing and they gave us an alternative to this that performs like a champ even when there are hundreds of thousands of list items.  Here is the example:

item = targetList.GetItemByUniqueId(new Guid(docId));

This is a solid work around until Microsoft patches the bug, however remember when modifying these layout pages that Microsoft will likely come along and overwrite your work with the next patch.

Best Regards,

-Mark Gabriel

Saturday, November 8, 2008

MOSS 2007 Document Properties Link in Search Results

For over a year I was frustrated with the lack of a link to a document's properties in SharePoint search results. Currently, the out of the box results provides only a link to the document itself, not the metadata or properties. In fact, it is quit difficult for an average user to go from the search results to the document properties. An other blog had an excellent solution to this that provided a link to the containing folder of the document. From here you can simply drop down the menu for the document and select View Properties. While this works for small libraries, in the situation when you have hundreds of other files in that parent folder, this solution becomes less attractive. After a couple frustrating attempts and having to sit down and learn about XSLT and XPath functions, now I have found a solution to this. As in the example pictured above, you can create a link in the search results to get to the properties of a document. The trick is to be able to build the URL using XPath functions in XSL. Here is an example of such a URL.

http://gabrieldev1/Docs/Documents/Forms/DispForm.aspx?ID=2

Create a Metadata Property Mapping

The first thing we need is the ID number which refers to the unique ID of the document for that library. We therefore must create a Metadata Property Mapping for this.

  • Open up the Shared Services Administration.
  • Click Search Settings -> Metadata property mappings -> New Managed Property Fill out the form like this:
  • Click OK and perform a Full Index on all relevant content sources.

You have now successfully created and populated the needed metadata property mapping, now we need to pull that value into our search results.

Add The New Metadata Property to the Core Search Results Web Part

  • In your Search Center, go to the results.aspx page.
  • Click Site Actions -> Edit Page
  • On the Core Results Webpart, click Edit -> Modify Shared Webpart
  • In the Core Results Webpart properties, click the + next to Results Query Option
  • Look for the section labeled Selected Columns, Click the "..." button next to this field to open up the XML editor
  • You will see a window that looks like this
  • Add this Column to the XML:
    <Column Name="ListItemID"/>
  • This is an example of what your XML should look like when you are done:
    <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Columns>
        <Column Name="WorkId"/>
        <Column Name="Rank"/>
        <Column Name="Title"/>
        <Column Name="Author"/>
        <Column Name="Size"/>
        <Column Name="Path"/>
        <Column Name="Description"/>
        <Column Name="Write"/>
        <Column Name="SiteName"/>
        <Column Name="CollapsingStatus"/>
        <Column Name="HitHighlightedSummary"/>
        <Column Name="HitHighlightedProperties"/>
        <Column Name="ContentClass"/>
        <Column Name="IsDocument"/>
        <Column Name="PictureThumbnailURL"/>
        <Column Name="ListItemID"/>
      </Columns>
    </root>

 

Add the Link to the Core Search Results XSL Template

You now have the ID variable available to you in the XSL Template for the Search Results. Now we need to edit this template to display the link.

  • In the Core Search Results Properties, click the XSL Editor button.
  • Another window with a bunch of poorly indented XML will appear. I recommend you take the time to copy this into visual studio or other XML text editor.
  • Scroll down until you find the remark
    <!-- This template is called for each result -->
  • Below this remark is a list of variable declarations, add sitename, listitemid, and isdocument declarations to this list so that the code looks like this:
    <!-- This template is called for each result -->
    <xsl:template match="Result">
      <xsl:variable name="id" select="id"/>
      <xsl:variable name="url" select="url"/>
      <xsl:variable name="sitename" select="sitename"/>
      <xsl:variable name="listitemid" select="listitemid"/>
      <xsl:variable name="isdocument" select="isdocument"/>
  • Now that the variables are initialized for the Template, we must construct the URL to the document properties page.
          <!-- BEGIN Add Link to document properties -->
          <xsl:choose>
            <xsl:when test="isdocument[. != 0]">
              <span class="srch-Description">
                |
              </span>
              <span class="srch-URL">
                <a href="{$sitename}/{substring-before(substring-after(substring($url, string-length($sitename)), '/'), '/')}/Forms/DispForm.aspx?ID={$listitemid}">
                  Properties
                </a>
              </span>
            </xsl:when>
          </xsl:choose>
          <!-- END Add Link to document properties -->

Here is the entire XSL template that I used in this example:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:param name="ResultsBy" />
<xsl:param name="ViewByUrl" />
<xsl:param name="ViewByValue" />
<xsl:param name="IsNoKeyword" />
<xsl:param name="IsFixedQuery" />
<xsl:param name="ShowActionLinks" />
<xsl:param name="MoreResultsText" />
<xsl:param name="MoreResultsLink" />
<xsl:param name="CollapsingStatusLink" />
<xsl:param name="CollapseDuplicatesText" />
<xsl:param name="AlertMeLink" />
<xsl:param name="AlertMeText" />
<xsl:param name="SrchRSSText" />
<xsl:param name="SrchRSSLink" />
<xsl:param name="ShowMessage" />
<xsl:param name="IsThisListScope" />
<xsl:param name="DisplayDiscoveredDefinition" select="True" />
<xsl:param name="NoFixedQuery" />
<xsl:param name="NoKeyword" />
<xsl:param name="NoResults" />
<xsl:param name="NoResults1" />
<xsl:param name="NoResults2" />
<xsl:param name="NoResults3" />
<xsl:param name="NoResults4" />
<xsl:param name="DefinitionIntro" />
 
<!-- When there is keywory to issue the search -->
<xsl:template name="dvt_1.noKeyword">
  <span class="srch-description">
  <xsl:choose>
  <xsl:when test="$IsFixedQuery">
      <xsl:value-of select="$NoFixedQuery" />
  </xsl:when>
   <xsl:otherwise>
      <xsl:value-of select="$NoKeyword" />
   </xsl:otherwise>
  </xsl:choose>
  </span>       
</xsl:template>
 
 
<!-- When empty result set is returned from search -->
<xsl:template name="dvt_1.empty">
 <div class="srch-sort">
  <xsl:if test="$AlertMeLink and $ShowActionLinks">  
    <span class="srch-alertme" > <a href ="{$AlertMeLink}" id="CSR_AM1" title="{$AlertMeText}"><img style="vertical-align: middle;" src="/_layouts/images/bell.gif" alt="" border="0"/><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="$AlertMeText" /></a>
    </span>
  </xsl:if>
 
  <xsl:if test="string-length($SrchRSSLink) &gt; 0 and $ShowActionLinks">     
   <xsl:if test="$AlertMeLink">  
    |
   </xsl:if>
   <a type="application/rss+xml" href ="{$SrchRSSLink}" title="{$SrchRSSText}" id="SRCHRSSL"><img style="vertical-align: middle;" border="0" src="/_layouts/images/rss.gif" alt=""/><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="$SrchRSSText"/></a>
  </xsl:if>
 </div>
 <br/> <br/>
 
  <span class="srch-description" id="CSR_NO_RESULTS">
   <xsl:value-of select="$NoResults" />
 
    <ol>
    <li><xsl:value-of select="$NoResults1" /></li>
    <li><xsl:value-of select="$NoResults2" /></li>
    <li><xsl:value-of select="$NoResults3" /></li>
    <li><xsl:value-of select="$NoResults4" /></li>
    </ol>
  </span>
</xsl:template>
 
 
<!-- Main body template. Sets the Results view (Relevance or date) options -->
<xsl:template name="dvt_1.body">
  <div class="srch-results">
  <xsl:if test="$ShowActionLinks">
  <div class="srch-sort"> <xsl:value-of select="$ResultsBy" /> 
   <xsl:if test="$ViewByUrl">  
     | 
    <a href ="{$ViewByUrl}" id="CSR_RV" title="{$ViewByValue}">              
     <xsl:value-of select="$ViewByValue" />
    </a>  
   </xsl:if>
   <xsl:if test="$AlertMeLink">  
     | 
    <span class="srch-alertme" > <a href ="{$AlertMeLink}" id="CSR_AM2" title="{$AlertMeText}"><img style="vertical-align: middle;" src="/_layouts/images/bell.gif" alt="" border="0"/><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="$AlertMeText" /></a>
    </span>
   </xsl:if>
   <xsl:if test="string-length($SrchRSSLink) &gt; 0">  
     | 
     <a type="application/rss+xml" href ="{$SrchRSSLink}" title="{$SrchRSSText}" id="SRCHRSSL"><img style="vertical-align: middle;" border="0" src="/_layouts/images/rss.gif" alt=""/><xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text><xsl:value-of select="$SrchRSSText"/></a>
   </xsl:if>
  </div>
  <br /><br />
  </xsl:if>
  <xsl:apply-templates />
 
  </div>  
  <xsl:call-template name="DisplayMoreResultsAnchor" />
</xsl:template><!-- This template is called for each result -->
<xsl:template match="Result"> 
  <xsl:variable name="id" select="id"/>
  <xsl:variable name="url" select="url"/>
  <xsl:variable name="sitename" select="sitename"/>
  <xsl:variable name="listitemid" select="listitemid"/>
  <xsl:variable name="isdocument" select="isdocument"/>
  <span class="srch-Icon"> 
   <a href="{$url}" id="{concat('CSR_IMG_',$id)}" title="{$url}">
   <img align="absmiddle" src="{imageurl}" border="0" alt="{imageurl/@imageurldescription}" />
   </a>
  </span>
  <span class="srch-Title">
   <a href="{$url}" id="{concat('CSR_',$id)}" title="{$url}">
    <xsl:choose>
     <xsl:when test="hithighlightedproperties/HHTitle[. != '']">
         <xsl:call-template name="HitHighlighting">
          <xsl:with-param name="hh" select="hithighlightedproperties/HHTitle" /> 
         </xsl:call-template>   
     </xsl:when>
     <xsl:otherwise><xsl:value-of select="title"/></xsl:otherwise> 
    </xsl:choose>
   </a>
    <br/> 
   </span>
 
   <xsl:choose>
     <xsl:when test="$IsThisListScope = 'True' and contentclass[. = 'STS_ListItem_PictureLibrary'] and picturethumbnailurl[. != '']">
       <div style="padding-top: 2px; padding-bottom: 2px;">
        <a href="{$url}" id="{concat('CSR_P',$id)}" title="{title}">
          <img src="{picturethumbnailurl}" alt="" />
        </a>
       </div>
     </xsl:when>
   </xsl:choose>
   <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 >
    <p class="srch-Metadata">
    <span class="srch-URL">
      <!-- BEGIN Add Link to document properties -->
      <xsl:choose>
        <xsl:when test="isdocument[. != 0]">
          <span class="srch-URL">
            <a href="{$sitename}/{substring-before(substring-after(substring($url, string-length($sitename)), '/'), '/')}/Forms/DispForm.aspx?ID={$listitemid}">
              Properties
            </a>
          </span>
          <span class="srch-Description">
            |
          </span>
        </xsl:when>
      </xsl:choose>
      <!-- END Add Link to document properties -->
     <a href="{$url}" id="{concat('CSR_U_',$id)}" title="{$url}" dir="ltr">      
      <xsl:choose>
        <xsl:when test="hithighlightedproperties/HHUrl[. != '']">
           <xsl:call-template name="HitHighlighting">
              <xsl:with-param name="hh" select="hithighlightedproperties/HHUrl" /> 
           </xsl:call-template> 
        </xsl:when>
       <xsl:otherwise><xsl:value-of select="url"/></xsl:otherwise> 
      </xsl:choose>
     </a>
    </span>           
     <xsl:call-template name="DisplaySize">
      <xsl:with-param name="size" select="size" />
     </xsl:call-template>     
     <xsl:call-template name="DisplayString">
      <xsl:with-param name="str" select="author" /> 
     </xsl:call-template>  
     <xsl:call-template name="DisplayString">
      <xsl:with-param name="str" select="write" />
     </xsl:call-template>     
     <xsl:call-template name="DisplayCollapsingStatusLink">
        <xsl:with-param name="status" select="collapsingstatus"/>
        <xsl:with-param name="urlEncoded" select="urlEncoded"/>
        <xsl:with-param name="id" select="concat('CSR_CS_',$id)"/>
     </xsl:call-template>        
    </p>
</xsl:template>
 
<xsl:template name="HitHighlighting"> 
 <xsl:param name="hh" /> 
 <xsl:apply-templates select="$hh"/> 
</xsl:template>
 
<xsl:template match="ddd"> 
  &#8230;  
</xsl:template> 
<xsl:template match="c0"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c1"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c2"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c3"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c4"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c5"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c6"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c7"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c8"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
<xsl:template match="c9"> 
    <b><xsl:value-of select="."/></b>
</xsl:template> 
 
 
<!-- The size attribute for each result is prepared here -->
<xsl:template name="DisplaySize">
  <xsl:param name="size" /> 
  <xsl:if test='string-length($size) &gt; 0'>       
   <xsl:if test="number($size) &gt; 0">
   -
    <xsl:choose>
     <xsl:when test="round($size div 1024) &lt; 1"><xsl:value-of select="$size" /> Bytes</xsl:when>
     <xsl:when test="round($size div (1024 *1024)) &lt; 1"><xsl:value-of select="round($size div 1024)" />KB</xsl:when>
     <xsl:otherwise><xsl:value-of select="round($size div (1024 * 1024))"/>MB</xsl:otherwise>
    </xsl:choose>    
   </xsl:if>
  </xsl:if>
</xsl:template>
 
 
 
<!-- A generic template to display string with non 0 string length (used for author and lastmodified time -->
<xsl:template name="DisplayString">
  <xsl:param name="str" />
  <xsl:if test='string-length($str) &gt; 0'>   
   - 
   <xsl:value-of select="$str" /> 
  </xsl:if>
</xsl:template>
 
<!-- document collapsing link setup -->
<xsl:template name="DisplayCollapsingStatusLink">
    <xsl:param name="status"/>
    <xsl:param name="urlEncoded"/>
    <xsl:param name="id"/>
    <xsl:if test="$CollapsingStatusLink">
      <xsl:choose>
          <xsl:when test="$status=1">
              <br/>
              <xsl:variable name="CollapsingStatusHref" select="concat(substring-before($CollapsingStatusLink, '$$COLLAPSE_PARAM$$'), 'duplicates:&quot;', $urlEncoded, '&quot;', substring-after($CollapsingStatusLink, '$$COLLAPSE_PARAM$$'))"/>
              <span class="srch-dup">
              [<a href="{$CollapsingStatusHref}" id="$id" title="{$CollapseDuplicatesText}">
              <xsl:value-of select="$CollapseDuplicatesText"/>
              </a>]
              </span>
          </xsl:when>
      </xsl:choose>
    </xsl:if>
</xsl:template><!-- The "view more results" for fixed query -->
<xsl:template name="DisplayMoreResultsAnchor">
  <xsl:if test="$MoreResultsLink">
   <a href="{$MoreResultsLink}" id="CSR_MRL">
    <xsl:value-of select="$MoreResultsText"/> 
    </a>
   </xsl:if>
</xsl:template>
 
<xsl:template match="All_Results/DiscoveredDefinitions">
  <xsl:variable name="FoundIn" select="DDFoundIn" />
  <xsl:variable name="DDSearchTerm" select="DDSearchTerm" />
  <xsl:if test="$DisplayDiscoveredDefinition = 'True' and string-length($DDSearchTerm) &gt; 0">
    <script language="javascript">
     
          function ToggleDefinitionSelection()
          {
            var selection = document.getElementById("definitionSelection");
            if (selection.style.display == "none")
            {
              selection.style.display = "inline";
            }
            else
            {
             selection.style.display = "none";
            }
         }
       
</script>
    <div>
      <a href="#" onclick="ToggleDefinitionSelection(); return false;">
        <xsl:value-of select="$DefinitionIntro" /><b><xsl:value-of select="$DDSearchTerm"/></b></a>
      <div id="definitionSelection" class="srch-Description" style="display:none;">
        <xsl:for-each select="DDefinitions/DDefinition">
          <br/>
          <xsl:variable name="DDUrl" select="DDUrl" />
          <xsl:value-of select="DDStart"/>
          <b>
            <xsl:value-of select="DDBold"/>
          </b>
          <xsl:value-of select="DDEnd"/>
          <br/>
          <xsl:value-of select="$FoundIn"/>
          <a href="{$DDUrl}">
          <xsl:value-of select="DDTitle"/> 
          </a>
        </xsl:for-each>
      </div>
    </div>
  </xsl:if>   
</xsl:template>
 
<!-- XSL transformation starts here -->
<xsl:template match="/">
  <xsl:if test="$AlertMeLink">  
   <input type="hidden" name="P_Query" />
   <input type="hidden" name="P_LastNotificationTime" />
  </xsl:if>
  <xsl:choose>
   <xsl:when test="$IsNoKeyword = 'True'" >
    <xsl:call-template name="dvt_1.noKeyword" />
   </xsl:when>
   <xsl:when test="$ShowMessage = 'True'">
     <xsl:call-template name="dvt_1.empty" />
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="dvt_1.body"/>      
   </xsl:otherwise>
  </xsl:choose>
</xsl:template> 
 
<!-- End of Stylesheet -->
</xsl:stylesheet>

Hopefully someone else out there will find this as useful as I have, or someone will laugh and comment on how it would have been so much easier to do it a different way! If this is the case, please post it!!

Until next time.

-Mark Gabriel