Saturday, June 26, 2010

SharePoint 2010 - Application Page runtime error "Server Error in "/" Application"

Scenario:

I have developed an application page in SharePoint 2010. After deploying it as a solution, I got the below error:

Server Error in '/' Application. 
--------------------------------

Runtime Error

<!-- Web.Config Configuration File -->

<configuration>
  <system.web>
    <customErrors mode="Off"/>
  </system.web>
</configuration> 

Notes: The current error page you are seeing can be replaced by a custom error page by modifying the "defaultRedirect" attribute of the application's <customErrors> configuration tag to point to a custom error page URL.

<!-- Web.Config Configuration File -->

<configuration>
  <system.web>
    <customErrors mode="RemoteOnly" defaultRedirect="mycustompage.htm"/>
  </system.web>
</configuration> 

So, I went in and turn customError and StackTrace option, in web.config on; and then, I got a more detailed error :). Runtime Error with the description telling me that I need to set customError and StackTrace on to see the message. This sound very recursive and very strange. Since, I could not see this error that is why I try to turn on by changing customError and StackTrace on and now they are both on, I am suddenly getting the message telling me to do so in place of the SharePoint custom error.

Explanation:

In SharePoint 2010, depending on what we do, we may need to also edit the web.config in the layouts folder of the SharePoint root:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\web.config

Tuesday, June 22, 2010

"Internet Explorer cannot display the webpage" error in SharePoint 2010 web application

While working on a SharePoint 2010 site, I suddenly encountered the infamous error "Internet Explorer cannot display the web page".

It was just me who was working on that farm at that time.

When I attempted to access the same site collection from within the server, I have been thrown the login prompt repeatedly despite providing the right credentials. All the other site collections in other web applications were behaving normally.

So at this point, it looks like it has to do some thing with that particular web application. I started to check all the web application properties, performed IISRESET but nothing worked. So I rebooted all the servers in the farm starting with the WFEs, then the SQL Server and the AD server.

Finally after handling the Loopback check issue which is covered in this KB article: http://support.microsoft.com/kb/896861, the authentication issues have been resolved.

Friday, June 4, 2010

Displaying real time SharePoint search results alphabetically using search.asmx and jQuery/JS

Scenario:
Ho to display real time SharePoint people search results alphabetically using search.asmx and js

Explanation:
If there is a requirement to display all AD users in people search in SharePoint, the list can grow long and hence it is a good idea to categorize users alphabetically.

People Search Results

Solution:
The below combination of Javascript and XSL can be used to accomplish the above. You can either use your own custom search web part or the OOB search results web part to get the results xml and apply the below XSL to the xml result

The below XSL builds a list of alphabets as links. Upon clicking each link a javascript function is called and names of people that start with the corresponding alphabet are displayed.

<xsl:attribute name="onclick">
GetContacts('<xsl:value-of select="child::node()[position()]"/>');
</xsl:attribute>

Lets get started..

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:output method="html" encoding="UTF-8"/>
  <xsl:template name="Alphabets">
    <Alpha>A</Alpha>
    <Alpha>B</Alpha>
    <Alpha>C</Alpha>
    <Alpha>D</Alpha>
    <Alpha>E</Alpha>
    <Alpha>F</Alpha>
    <Alpha>G</Alpha>
    <Alpha>H</Alpha>
    <Alpha>I</Alpha>
    <Alpha>J</Alpha>
    <Alpha>K</Alpha>
    <Alpha>L</Alpha>
    <Alpha>M</Alpha>
    <Alpha>N</Alpha>
    <Alpha>O</Alpha>
    <Alpha>P</Alpha>
    <Alpha>Q</Alpha>
    <Alpha>R</Alpha>
    <Alpha>S</Alpha>
    <Alpha>T</Alpha>
    <Alpha>U</Alpha>
    <Alpha>V</Alpha>
    <Alpha>W</Alpha>
    <Alpha>X</Alpha>
    <Alpha>Y</Alpha>
    <Alpha>Z</Alpha>
  </xsl:template>
  <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'"/>
  <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
  <xsl:key name="keyTitle" match="Result" use="translate(substring(lastname,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
  <xsl:template match="/">
    <xsl:variable name="Rows" select="/Search_Results/All_Results/Result"/>
    <div id="assocDirResultBox" sizset="115" sizcache="16">
      <xsl:variable name="AlphabetNodes">
        <xsl:call-template name="Alphabets"/>
      </xsl:variable>
      <xsl:variable name="AlphabetName">
        <xsl:apply-templates select="msxsl:node-set($AlphabetNodes)/Alpha">
          <xsl:sort select="." order="ascending"/>
        </xsl:apply-templates>
      </xsl:variable>
      <xsl:variable name="TitleNames">
        <xsl:for-each select="$Rows[generate-id(.) = generate-id(key('keyTitle', translate(substring(lastname,1,1),$smallcase,$uppercase))[1])]">
          <xsl:value-of select="translate(substring(lastname,1,1),$smallcase,$uppercase)"/>
        </xsl:for-each>
      </xsl:variable>
      <a name="top_AssosiatesAToZ"></a>
      <div class="alphaBrowseNavBox" sizcache="16" sizset="111">
        <div class="alphaBrowseNav" sizcache="16" sizset="111">
          <xsl:for-each select="msxsl:node-set($AlphabetNodes)/Alpha">
            <xsl:if test="position() &lt;= count(msxsl:node-set($AlphabetNodes)/Alpha)">
              <xsl:variable name="char" select="child::node()[position()]"/>
              <xsl:choose>
                <xsl:when test="contains($TitleNames,$char)">
                  <a>
                    <xsl:attribute name="href">
                      #<xsl:value-of select="child::node()[position()]"/>
                    </xsl:attribute>
                    <xsl:attribute name="onclick">
                      GetContacts('<xsl:value-of select="child::node()[position()]"/>');
                    </xsl:attribute>
                    <xsl:value-of select="child::node()[position()]"/>
                  </a>
                </xsl:when>
                <xsl:otherwise>
                  <span>
                    <xsl:value-of select="child::node()[position()]"/>
                  </span>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:if>
          </xsl:for-each>
        </div>
      </div>
      <div id="searchResults">
      </div>
    </div>
  </xsl:template>
  <!-- A generic function that can be used to replace strings in a given text. Similar to String.Replace of C# -->
  <xsl:template name="ReplaceAllCharsInString">
    <xsl:param name="text"/>
    <xsl:param name="replace"/>
    <xsl:param name="by"/>
    <xsl:choose>
      <xsl:when test="contains($text, $replace)">
        <xsl:value-of select="substring-before($text,$replace)"/>
        <xsl:value-of select="$by"/>
        <xsl:call-template name="ReplaceAllCharsInString">
          <xsl:with-param name="text" select="substring-after($text,$replace)"/>
          <xsl:with-param name="replace" select="$replace"/>
          <xsl:with-param name="by" select="$by"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

The javascript/jQuery functions:
function GetContacts(alpha) 
  {
   var qry = "SELECT Department,WorkPhone,OfficeNumber,AboutMe,PictureURL,WorkEmail,WebSite,BaseOfficeLocation,LastName,FirstName,Company,MobilePhone,PreferredName,AccountName,UserProfile_GUID, JobTitle,Size, Rank, Path, Title, Description, Write  FROM SCOPE() WHERE (\"scope\"='Associates') AND (\"DAV:contentclass\"='urn:content-class:SPSPeople') AND (\"LastName\" LIKE '" + alpha + "%') ORDER BY LastName";

   var queryXML = 
    <![CDATA["<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'> \
    <Query domain='QDomain'> \
     <SupportedFormats><Format>urn:Microsoft.Search.Response.Document.Document</Format></SupportedFormats> \
     <Context> \
      <QueryText type='MSSQLFT' language='en-us'>#qry#</QueryText> \
     </Context> \
    <SortByProperties><SortByProperty name='Rank' direction='Descending' order='1'/></SortByProperties> \
     <Range><StartAt>1</StartAt><Count>2000</Count></Range> \
     <EnableStemming>false</EnableStemming> \
     <TrimDuplicates>true</TrimDuplicates> \
     <IgnoreAllNoiseQuery>true</IgnoreAllNoiseQuery> \
     <ImplicitAndBehavior>true</ImplicitAndBehavior> \
     <IncludeRelevanceResults>true</IncludeRelevanceResults> \
     <IncludeSpecialTermResults>true</IncludeSpecialTermResults> \
     <IncludeHighConfidenceResults>true</IncludeHighConfidenceResults> \
    </Query></QueryPacket>"]]>;
   
   queryXML = queryXML.replace('#qry#', qry);
   
   var soapEnv =
    <![CDATA["<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'> \
      <soap:Body> \
     <Query xmlns='urn:Microsoft.Search'> \
       <queryXml>" + escapeHTML(queryXML) + "</queryXml> \
     </Query> \
      </soap:Body> \
    </soap:Envelope>"]]>;   

   $.blockUI({ message: '<p>Please wait while the contacts are being loaded...</p>' });
    
   $.ajax({
    url: window.location.protocol + "//" + window.location.host + "/_vti_bin/search.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=\"utf-8\""
   });
   
   function processResult(xData, status) {
    var html = "";
    html += "&lt;div class='alphaBrowseNavHeader'&gt;" + alpha + "&lt;/div&gt;";
    html += "&lt;div class='rightActionsWrapper'&gt;";
    $(xData.responseXML).find("QueryResult").each(function() {
     var x = $("<xml>" + $(this).text() + "</xml>");

     /* Find value of each property */ 
     x.find("Document").each(function() {
      var department = '', workPhone = '', pictureUrl = '', workEmail = '', location = '', lastName = '', firstName = '', company = '', mobilePhone = '', preferredname = '', accountName = '', accountNameEncoded = '', jobTitle = '', responsibility = '';
      $(this).find("Property").each(function() {
       switch($(this).find("Name").text())
       {
        case 'DEPARTMENT':
         department = $(this).find("Value").text();
         break;
        case 'WORKPHONE':
         workPhone = $(this).find("Value").text();
         break;          
        case 'PICTUREURL':
         pictureUrl = $(this).find("Value").text();
         break;          
        case 'WORKEMAIL':
         workEmail = $(this).find("Value").text();
         break;          
        case 'BASEOFFICELOCATION':
         location = $(this).find("Value").text();
         break;          
        case 'LASTNAME':
         lastName = $(this).find("Value").text();
         break;          
        case 'FIRSTNAME':
         firstName = $(this).find("Value").text();
         break;          
        case 'COMPANY':
         company = $(this).find("Value").text();
         break;          
        case 'MOBILEPHONE':
         mobilePhone = $(this).find("Value").text();
         break;          
        case 'PREFERREDNAME':
         preferredname = $(this).find("Value").text();
         break;          
        case 'ACCOUNTNAME':
         accountName = $(this).find("Value").text();
         accountNameEncoded = accountName.replace('\\','%5C');
         break;          
        case 'JOBTITLE':
         jobTitle = $(this).find("Value").text();
         break;  
        case 'RESPONSIBILITY':
         responsibility = $(this).find("Value").text();
         break;  
        default:
         break;
       }
      })
      
      /* If no picture is available, display the temp place holder image */
      if(pictureUrl == '')
      {
       pictureUrl = '/Style%20Library/Images/temp_placeholder.gif';
      }          
      
      var title = $("Title", $(this)).text();
      var url = $("Action>LinkUrl", $(this)).text();
      var description = $("Description", $(this)).text()
       
      html += 
      <![CDATA["<div class='rightActionsBox'> \
       <a href='"+ url +"'><img alt='Photo of " + preferredname + "' border='0' width='158' class='rightActionsBoxImage' src='"+pictureUrl+"' /></a> \
       <div class='rightActionsCopy' sizset='122' sizcache='16'> \
        <a href='"+ url +"'><span class='name'>" + preferredname + "</span></a> \
        <table class='assocDirResultTable'> \
         <tbody> \
          <tr> \
           <td> \
            <span class='property title'>&#160;#jobTitle#</span> \
            <span class='property'><label>Work:</label>&#160;#workPhone#</span> \
            <span class='property'><label>Mobile:</label>&#160;#mobilePhone#</span> \
            <span class='property' sizset='122' sizcache='16'>#accountLink#</span> \
           </td> \
           <td> \
            <span class='property'><label>Business:</label>&#160;#company#</span> \
            <span class='property'><label>Department:</label>&#160;#department#</span> \
            <span class='property'><label>Location:</label>&#160;#location#</span> \
           </td> \
           <td class='last'><label>Ask me about</label> \
            <span class='property'>#responsibility#</span> \
           </td> \
          </tr> \
         </tbody> \
        </table> \
       </div> \
       <ul class='rightActions' sizset='123' sizcache='16'> \
        <li class='iconCal' sizset='123' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#AddToColleageCall#, null, AddToColleaguesDialogCallback);return false;'>Add As Colleague</a> \
        </li> \
        <li class='iconPeople' sizset='124' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#ShowOrganizationHierarchyCall#, null, ShowOrganizationHierarchyDialogCallback);return false;'>Browse In Organization Chart</a> \
        </li> \
        <li class='iconFlagGreen' sizset='125' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#ViewRecentContentCall#, null, ViewRecentContentDialogCallback);return false;'>View Recent Content</a> \
        </li> \
       </ul> \
      </div>"]]>
      html = html.replace('#jobTitle#', jobTitle).replace('#workPhone#', workPhone).replace('#mobilePhone#', mobilePhone).replace('#company#', company).replace('#department#', department).replace('#location#', location).replace('#responsibility#', responsibility).replace('#AddToColleageCall#','"' + 'http://[Site URL]/my/_layouts/QuickLinksDialogForm.aspx?Mode=Person&amp;NTName=' + accountNameEncoded + '&amp;IsDlg=1' + '"').replace('#ShowOrganizationHierarchyCall#','"' + 'http://[site url]/my/OrganizationView.aspx?ProfileType=User&amp;accountname=' + accountNameEncoded + '"').replace('#ViewRecentContentCall#','"' + 'http://[site URL]/my/personcontent.aspx?accountname=' + accountNameEncoded + '"').replace('#workEmail#',workEmail);
      if(workEmail != '')
      {
       html = html.replace('#accountLink#','&lt;a href=\'mailto:#workEmail#\'&gt;#accountName#&lt;/a&gt;');
       html = html.replace('#workEmail#', workEmail);
       html = html.replace('#accountName#', accountName);
      }
      else 
       html = html.replace('#accountLink#',accountName);
     });
    });
    html += "&lt;/div&gt;"; 
    $("#searchResults").empty().append(html);
   }
  } 

Complete Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
 <xsl:output method="html" encoding="UTF-8"/>
 <xsl:template name="Alphabets">
  <Alpha>A</Alpha>
  <Alpha>B</Alpha>
  <Alpha>C</Alpha>
  <Alpha>D</Alpha>
  <Alpha>E</Alpha>
  <Alpha>F</Alpha>
  <Alpha>G</Alpha>
  <Alpha>H</Alpha>
  <Alpha>I</Alpha>
  <Alpha>J</Alpha>
  <Alpha>K</Alpha>
  <Alpha>L</Alpha>
  <Alpha>M</Alpha>
  <Alpha>N</Alpha>
  <Alpha>O</Alpha>
  <Alpha>P</Alpha>
  <Alpha>Q</Alpha>
  <Alpha>R</Alpha>
  <Alpha>S</Alpha>
  <Alpha>T</Alpha>
  <Alpha>U</Alpha>
  <Alpha>V</Alpha>
  <Alpha>W</Alpha>
  <Alpha>X</Alpha>
  <Alpha>Y</Alpha>
  <Alpha>Z</Alpha>
 </xsl:template>
 <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'"/>
 <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
 <xsl:key name="keyTitle" match="Result" use="translate(substring(lastname,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
 <xsl:template match="/">
  <script type="text/javascript">
  function AddToColleaguesDialogCallback(dialogResult, returnValue)
  {
   if (dialogResult == 1)
   {
    alert('Contact has been added as your colleague.');
   }
  }

  function ShowOrganizationHierarchyDialogCallback(dialogResult, returnValue)
  {
  }
  
  function ViewRecentContentDialogCallback(dialogResult, returnValue)
  {
  }
  
  function ULSX84(){var o=new Object;o.ULSTeamName="SharePoint Portal Server";o.ULSFileName="portal.js";return o;}
  
  function ShowDialog(e,b,c)
  {
   ULSX84:;
   var a=[];
   if(b!=null)
    a[0]=b;
   else 
    a=null;
   var d={width:750,height:500};
   SP.UI.ModalDialog.commonModalDialogOpen(e,d,c,a)
  }
 
  $(document).ajaxStop($.unblockUI); 
 
  $(document).ready(function(){
   GetContacts('A');
  });
 
  function GetContacts(alpha) 
  {
   var qry = "SELECT Department,WorkPhone,OfficeNumber,AboutMe,PictureURL,WorkEmail,WebSite,BaseOfficeLocation,LastName,FirstName,Company,MobilePhone,PreferredName,AccountName,UserProfile_GUID, JobTitle,Size, Rank, Path, Title, Description, Write  FROM SCOPE() WHERE (\"scope\"='Associates') AND (\"DAV:contentclass\"='urn:content-class:SPSPeople') AND (\"LastName\" LIKE '" + alpha + "%') ORDER BY LastName";

   var queryXML = 
    <![CDATA["<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'> \
    <Query domain='QDomain'> \
     <SupportedFormats><Format>urn:Microsoft.Search.Response.Document.Document</Format></SupportedFormats> \
     <Context> \
      <QueryText type='MSSQLFT' language='en-us'>#qry#</QueryText> \
     </Context> \
    <SortByProperties><SortByProperty name='Rank' direction='Descending' order='1'/></SortByProperties> \
     <Range><StartAt>1</StartAt><Count>2000</Count></Range> \
     <EnableStemming>false</EnableStemming> \
     <TrimDuplicates>true</TrimDuplicates> \
     <IgnoreAllNoiseQuery>true</IgnoreAllNoiseQuery> \
     <ImplicitAndBehavior>true</ImplicitAndBehavior> \
     <IncludeRelevanceResults>true</IncludeRelevanceResults> \
     <IncludeSpecialTermResults>true</IncludeSpecialTermResults> \
     <IncludeHighConfidenceResults>true</IncludeHighConfidenceResults> \
    </Query></QueryPacket>"]]>;
   
   queryXML = queryXML.replace('#qry#', qry);
   
   var soapEnv =
    <![CDATA["<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'> \
      <soap:Body> \
     <Query xmlns='urn:Microsoft.Search'> \
       <queryXml>" + escapeHTML(queryXML) + "</queryXml> \
     </Query> \
      </soap:Body> \
    </soap:Envelope>"]]>;   

   $.blockUI({ message: '<p>Please wait while the contacts are being loaded...</p>' });
    
   $.ajax({
    url: window.location.protocol + "//" + window.location.host + "/_vti_bin/search.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=\"utf-8\""
   });
   
   function processResult(xData, status) {
    var html = "";
    html += "&lt;div class='alphaBrowseNavHeader'&gt;" + alpha + "&lt;/div&gt;";
    html += "&lt;div class='rightActionsWrapper'&gt;";
    $(xData.responseXML).find("QueryResult").each(function() {
     var x = $("<xml>" + $(this).text() + "</xml>");

     /* Find value of each property */ 
     x.find("Document").each(function() {
      var department = '', workPhone = '', pictureUrl = '', workEmail = '', location = '', lastName = '', firstName = '', company = '', mobilePhone = '', preferredname = '', accountName = '', accountNameEncoded = '', jobTitle = '', responsibility = '';
      $(this).find("Property").each(function() {
       switch($(this).find("Name").text())
       {
        case 'DEPARTMENT':
         department = $(this).find("Value").text();
         break;
        case 'WORKPHONE':
         workPhone = $(this).find("Value").text();
         break;          
        case 'PICTUREURL':
         pictureUrl = $(this).find("Value").text();
         break;          
        case 'WORKEMAIL':
         workEmail = $(this).find("Value").text();
         break;          
        case 'BASEOFFICELOCATION':
         location = $(this).find("Value").text();
         break;          
        case 'LASTNAME':
         lastName = $(this).find("Value").text();
         break;          
        case 'FIRSTNAME':
         firstName = $(this).find("Value").text();
         break;          
        case 'COMPANY':
         company = $(this).find("Value").text();
         break;          
        case 'MOBILEPHONE':
         mobilePhone = $(this).find("Value").text();
         break;          
        case 'PREFERREDNAME':
         preferredname = $(this).find("Value").text();
         break;          
        case 'ACCOUNTNAME':
         accountName = $(this).find("Value").text();
         accountNameEncoded = accountName.replace('\\','%5C');
         break;          
        case 'JOBTITLE':
         jobTitle = $(this).find("Value").text();
         break;  
        case 'RESPONSIBILITY':
         responsibility = $(this).find("Value").text();
         break;  
        default:
         break;
       }
      })
      
      /* If no picture is available, display the temp place holder image */
      if(pictureUrl == '')
      {
       pictureUrl = '/Style%20Library/Images/temp_placeholder.gif';
      }          
      
      var title = $("Title", $(this)).text();
      var url = $("Action>LinkUrl", $(this)).text();
      var description = $("Description", $(this)).text()
       
      html += 
      <![CDATA["<div class='rightActionsBox'> \
       <a href='"+ url +"'><img alt='Photo of " + preferredname + "' border='0' width='158' class='rightActionsBoxImage' src='"+pictureUrl+"' /></a> \
       <div class='rightActionsCopy' sizset='122' sizcache='16'> \
        <a href='"+ url +"'><span class='name'>" + preferredname + "</span></a> \
        <table class='assocDirResultTable'> \
         <tbody> \
          <tr> \
           <td> \
            <span class='property title'>&#160;#jobTitle#</span> \
            <span class='property'><label>Work:</label>&#160;#workPhone#</span> \
            <span class='property'><label>Mobile:</label>&#160;#mobilePhone#</span> \
            <span class='property' sizset='122' sizcache='16'>#accountLink#</span> \
           </td> \
           <td> \
            <span class='property'><label>Business:</label>&#160;#company#</span> \
            <span class='property'><label>Department:</label>&#160;#department#</span> \
            <span class='property'><label>Location:</label>&#160;#location#</span> \
           </td> \
           <td class='last'><label>Ask me about</label> \
            <span class='property'>#responsibility#</span> \
           </td> \
          </tr> \
         </tbody> \
        </table> \
       </div> \
       <ul class='rightActions' sizset='123' sizcache='16'> \
        <li class='iconCal' sizset='123' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#AddToColleageCall#, null, AddToColleaguesDialogCallback);return false;'>Add As Colleague</a> \
        </li> \
        <li class='iconPeople' sizset='124' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#ShowOrganizationHierarchyCall#, null, ShowOrganizationHierarchyDialogCallback);return false;'>Browse In Organization Chart</a> \
        </li> \
        <li class='iconFlagGreen' sizset='125' sizcache='16'> \
         <a href='#' onclick='ShowDialog(#ViewRecentContentCall#, null, ViewRecentContentDialogCallback);return false;'>View Recent Content</a> \
        </li> \
       </ul> \
      </div>"]]>
      html = html.replace('#jobTitle#', jobTitle).replace('#workPhone#', workPhone).replace('#mobilePhone#', mobilePhone).replace('#company#', company).replace('#department#', department).replace('#location#', location).replace('#responsibility#', responsibility).replace('#AddToColleageCall#','"' + 'http://[Site URL]/my/_layouts/QuickLinksDialogForm.aspx?Mode=Person&amp;NTName=' + accountNameEncoded + '&amp;IsDlg=1' + '"').replace('#ShowOrganizationHierarchyCall#','"' + 'http://[site url]/my/OrganizationView.aspx?ProfileType=User&amp;accountname=' + accountNameEncoded + '"').replace('#ViewRecentContentCall#','"' + 'http://[site URL]/my/personcontent.aspx?accountname=' + accountNameEncoded + '"').replace('#workEmail#',workEmail);
      if(workEmail != '')
      {
       html = html.replace('#accountLink#','&lt;a href=\'mailto:#workEmail#\'&gt;#accountName#&lt;/a&gt;');
       html = html.replace('#workEmail#', workEmail);
       html = html.replace('#accountName#', accountName);
      }
      else 
       html = html.replace('#accountLink#',accountName);
     });
    });
    html += "&lt;/div&gt;"; 
    $("#searchResults").empty().append(html);
   }
  } 
  
  <![CDATA[
  function escapeHTML (str) {
   return str.replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }
  ]]>
  </script>
  <xsl:variable name="Rows" select="/Search_Results/All_Results/Result"/>
  <div id="assocDirResultBox" sizset="115" sizcache="16">
   <xsl:variable name="AlphabetNodes">
    <xsl:call-template name="Alphabets"/>
   </xsl:variable>
   <xsl:variable name="AlphabetName">
    <xsl:apply-templates select="msxsl:node-set($AlphabetNodes)/Alpha">
     <xsl:sort select="." order="ascending"/>
    </xsl:apply-templates>
   </xsl:variable>
   <xsl:variable name="TitleNames">
    <xsl:for-each select="$Rows[generate-id(.) = generate-id(key('keyTitle', translate(substring(lastname,1,1),$smallcase,$uppercase))[1])]">
     <xsl:value-of select="translate(substring(lastname,1,1),$smallcase,$uppercase)"/>
    </xsl:for-each>
   </xsl:variable>
   <a name="top_AssosiatesAToZ"></a>
   <div class="alphaBrowseNavBox" sizcache="16" sizset="111">
    <div class="alphaBrowseNav" sizcache="16" sizset="111">
     <xsl:for-each select="msxsl:node-set($AlphabetNodes)/Alpha">
      <xsl:if test="position() &lt;= count(msxsl:node-set($AlphabetNodes)/Alpha)">
       <xsl:variable name="char" select="child::node()[position()]"/>
       <xsl:choose>
        <xsl:when test="contains($TitleNames,$char)">
         <a>
          <xsl:attribute name="href">
           #<xsl:value-of select="child::node()[position()]"/>
          </xsl:attribute>
          <xsl:attribute name="onclick">
           GetContacts('<xsl:value-of select="child::node()[position()]"/>');
          </xsl:attribute>          
          <xsl:value-of select="child::node()[position()]"/>
         </a>
        </xsl:when>
        <xsl:otherwise>
         <span>
          <xsl:value-of select="child::node()[position()]"/>
         </span>
        </xsl:otherwise>
       </xsl:choose>
      </xsl:if>
     </xsl:for-each>
    </div>
   </div>
   <div id="searchResults">
   </div>
  </div>
 </xsl:template>
 <!-- A generic function that can be used to replace strings in a given text. Similar to String.Replace of C# -->
 <xsl:template name="ReplaceAllCharsInString">
  <xsl:param name="text"/>
  <xsl:param name="replace"/>
  <xsl:param name="by"/>
  <xsl:choose>
   <xsl:when test="contains($text, $replace)">
    <xsl:value-of select="substring-before($text,$replace)"/>
    <xsl:value-of select="$by"/>
    <xsl:call-template name="ReplaceAllCharsInString">
     <xsl:with-param name="text" select="substring-after($text,$replace)"/>
     <xsl:with-param name="replace" select="$replace"/>
     <xsl:with-param name="by" select="$by"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$text"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

Lessons learned from first SharePoint 2010 implementation

Just wanted to list down the lessons learnt from our first SharePoint 2010 implementation. While some of them are quite simple ones, some were disasterous.

  1. Attempted to save a blog site as a template. SharePoint has succesfully created a sandbox wsp solution in the solutions gallery of the site collection. But it could not be activated. ULS Logs told that there were some errors in the xml. So we cleaned up the xml and rebuild the wsp (Visual Studio 2010 has a option to open wsp files and reverse engineer them). Uploaded the solution into solutions gallery. Activated the solution. At first it looked as if every thing went smooth. All fine. But later we have noticed that it corrupted the whole site collection taxanomy. We have noticed that the Site Columns and Content Types galleries were emptied during the activation. So to fix this, we had to restore from an old backup
  2. SharePoint 2010 has closed the back door ability to save publishing site as a template.
  3. SharePoint Designer 2010 often poses problems while working with page layouts. This is especially true with connected web parts and few OOB web parts such as the Note Board Web Part (comments web part). In case of connected web parts, SPD interferes and alters the connection IDs to 0000-0000-0000-000. In the case of NoteBoard Web Part, SPD does not let us to shange the title. Resolution for the Note Board Web Part is to change the properties in design mode.
  4. It is not an easy and straight process to set certain properties like Picture URL in User Profile Properties. I have written a blog explaining how to map them to AD correctly Setting up the PictureURL User Profile Property using Forefront Synchronization Service Manager in SharePoint 2010
  5. Forefront Server may cause lot of troubles in case of a single server farm
  6. Content Query Web Part's styles do not work correctly untill they are fixed. Fellow colleague Sandeep Nahta wrote a blog about fixing the issue here
  7. Microsoft has not yet provided options to automatically take backups. We still have to write scripts and use them with a automater like Task Scheduler
  8. The infamous COLD, SLOW requests that baffles users still remains. We have used SPWakeUp with Task Scheduler to overcome this.
  9. If you take a backup of a particular list using the SharePoint 2010 UI's new granular backup interface and restore it to the same location where it has been backed up from, do not expect the list to have the same list ID. It generates a new ID for the list. This can pose a problem especially if this list is being used by a lookup column and if the list gets corrupted for any reason.

--- More to come ---

Making a SharePoint 2010 site externally available (Alternate Access Mappings, Host Header Bindings)

Scenario:
How to make a SharePoint 2010 site externally available?

Explanation:
This is a very fundamental scenario that many SharePoint developers may encounter in the SharePoint world.

In order to make a SharePoint site externally available, we need to follow the below steps

Point the desired host name (i.e. portal.sitename.com for example) to the public ip address of the SharePoint server (may it be the ip of the server in a single server farm or the load balancer in a multi server farm). Typically the network administrator does this. Once that has been set, all the requests for http://portal.sitename.com will be directed to the specified SharePoint Server. But the SharePoint server needs to know which site to serve for such requests. Therefore we need to configure host name bindings in IIS.

Open up IIS Manager and select the desired sharepoint site and choose "Edit Bindings"




In the resulting screen, click edit




Next, provide the host name (portal.sitename.com in this case)



Now its time to configure Alternate Access Mappings in SharePoint.
Go to Central Admin > Application Management > Alternate Access Mappings.


 
Click on "Edit Public URLs" and then choose the appropriate Alternate Access Mapping Collection.



All looks fine now. At this point, if you create a new site collection and try to access the host (http://portal.sitename.com) from a different machine than the server, it works. But if you try to access the host from within the server, you may get a 404 page not found error.

While we are aware of the problems that SharePoint can encounter related to the Windows Server loopback check issue and host headers, I just wanted to reiterate that while it is OK to outright disable the check in dev/qa environments, we should instead be specifying a list of acceptable host names in the registry for production environments.

Completely disabling the check is a security hole that would likely be picked up should one of our production environments be audited.

Both techniques for handling the Loopback check issue are covered in this KB article: http://support.microsoft.com/kb/896861

Thanks a lot to our Practice Lead David Perkinson for helping me resolve this issue.

Specify host names (Preferred method if NTLM authentication is desired)
To specify the host names that are mapped to the loopback address and can connect to Web sites on your computer, follow these steps:
  1. Set the DisableStrictNameChecking registry entry to 1. For more information about how to do this, click the following article number to view the article in the Microsoft Knowledge Base: 281308 (http://support.microsoft.com/kb/281308/ ) Connecting to SMB share on a Windows 2000-based computer or a Windows Server 2003-based computer may not work with an alias name
  2. Click Start, click Run, type regedit, and then click OK.
  3. In Registry Editor, locate and then click the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
  4. Right-click MSV1_0, point to New, and then click Multi-String Value.
  5. Type BackConnectionHostNames, and then press ENTER.
  6. Right-click BackConnectionHostNames, and then click Modify.
  7. In the Value data box, type the host name or the host names for the sites that are on the local computer, and then click OK.
  8. Quit Registry Editor, and then restart the IISAdmin service.
Now we should be able to access the site using the host name both internally as well as externally.