May 25 / Michiel

FullTextSqlQuery Exception from HRESULT: 0x80040E60

After upgrading a webpart from SharePoint 2007 to 2010 I got a exception from de search engine about the query format. I used the following code:

var sqlQuery = new FullTextSqlQuery(SPContext.Current.Site);
sqlQuery.ResultTypes = ResultType.RelevantResults;
sqlQuery.TrimDuplicates = false;
sqlQuery.QueryText = "SELECT Path, Title FROM scope() ORDER BY Title";

// Return the search results to a ResultTableCollection.
var results = sqlQuery.Execute();

When I debugged the code I found out that the ORDER BY clause was causing the problem. When I used a integer or date managed property in the order by clause the problem disappeared. I started investigating the options for text managed properties, in the properties for a managed property I found a new option:

image

The explanation for this option is:

To reduce storage requirements, new text properties are automatically treated as a hash which limits comparisons (including sorting) to equality/inequality.  Unselect this option to enable other types of comparisons (less than, greater than, order by).

When I enabled this option and started a full-crawl the problems are gone. In my opinion the error message should have been a bit more clearer.

May 25 / Michiel

FieldRefs not working with XML comments

Today I was working on a SharePoint 2010 project where the custom fields and content types where provisioned using a feature. After the feature was activated the custom fields and content types where provisioned in SharePoint, except the content type contained no fields. The element manifest file contained the following xml:

  <!-- Base Page -->
<ContentType ID="0x010100C568DB52D9D0A1...84E219954237AF3901"
              Name="Basepage"
              Description="Basepage"
              Group="Publishing"
              Sealed="TRUE"
              Inherits="TRUE"
              Version="1">
  <FieldRefs>
    <!-- Originating from MOSS -->
    <FieldRef ID="{b66e9b50-a28e-469b-b1a0-af0e45486874}"
            Name="Keywords" />
    <!-- Custom fields -->
    <FieldRef ID="{30607e4e-a3e1-4662-9766-56e1faedb875}"
            Name="AdminComments" />
  </FieldRefs>
</ContentType>

The ULS logs showed no errors and I couldn’t figure out what the problem was because the same element manifest file was used in SharePoint 2007. After the stripping the file I figured out that the xml comments were causing the problem. Now my element manifest contains the following:

<ContentType ID="0x010100C568DB52D9D0A1...84E219954237AF3901"
              Name="Basepage"
              Description="Basepage"
              Group="Publishing"
              Sealed="TRUE"
              Inherits="TRUE"
              Version="1">
  <FieldRefs>
    <FieldRef ID="{b66e9b50-a28e-469b-b1a0-af0e45486874}"
            Name="Keywords" />
    <FieldRef ID="{30607e4e-a3e1-4662-9766-56e1faedb875}"
            Name="AdminComments" />
  </FieldRefs>
</ContentType>

The content is the same except the comments are missing.

Jan 28 / Michiel

SearchBox changed in October Cumulative Update

The problem

With the introduction of the October Cumulative Update for SharePoint 2007 I encountered a problem with the search box on multilingual sites. After the installation of the update the scopes dropdown list always uses the same scope display group. After investigation I found that the language of the root web is used to determine the scope display group.

The cause

This behavior was not present in the control prior to the October update, in the description of the update (KB975731) we found:

"All sites," "People," and "This site" search scopes are available only for English language sites. Sites that are written in other languages show only the "This site" search scope.

This explains that the control has been changed in this update.

The reason

Before the October Update the property ScopeDisplayGroupName is set in the constructor of the SearchBoxEx control:

this._ScopeDisplayGroupName =
    SearchCommon.GetLocResourceString(
        LocStringId.ScopeDisplayGroup_SearchDropdown_Name);

In the October Update this line of code has been changed to:

this._ScopeDisplayGroupName = GetScopeDisplayGroupNameDefaultValue();

This function uses the language from the root web is to determine the value for the ScopeDisplayGroupName property:

private static string GetScopeDisplayGroupNameDefaultValue()
{
    if (SPContext.Current != null)
    {
        while (SPContext.Current.Site != null)
        {
            CultureInfo siteCollectionCulture = null;
            SPSecurity.RunWithElevatedPrivileges(delegate {
                siteCollectionCulture = new CultureInfo(
                    (int) SPContext.Current.Site.RootWeb.Language);
            });
            return StringResourceManager.GetString(
                LocStringId.ScopeDisplayGroup_SearchDropdown_Name,
                    siteCollectionCulture);
        }
    }

    return SearchCommon.GetLocResourceString(
            LocStringId.ScopeDisplayGroup_SearchDropdown_Name);
}

The solution

For this website I want the value for the ScopeDisplayGroupName property to be based on the culture of the current web. To re-enable this functionality we updated our custom master page and added the following to the SearchBoxEx control:

<sharepoint:searchboxex runat="server" ...
 ScopeDisplayGroupName="<%$Resources:searchfix,ScopeDisplayGroup_SearchDropdown_Name%>" />

In the App_GlobalResources I added custom resource files with the correct translations for the resource key ScopeDisplayGroup_SearchDropdown_Name.

Oct 16 / Michiel

Customize SearchBoxEx

A external company made a design for a Internet facing website, in the design the search control was made up in to rows. So my first idea was to create a new controltemplate for the SmallSearchInputBox, but then I realized that in moss the search-control isn’t a controltemplate anymore (in WSS it is). The searchbox in MOSS is a control called SearchBoxEx, and this has some properties for the layout of the control. But is always renders the output as a table with 1 row, so my problem was how to render is in 2 rows using divs. Google didn’t provide me with an answer, so is started coding myself (I don’t want to create a new control with all logic, I just want reuse all the logic of SearchBoxEx and provide new render logic).

I finally found a way to do this, I just create an new control that inherits from SearchBoxEx. My custom look-and-feel can be done used an override of the CreateChildControls:

protected override void CreateChildControls( )
{
    base.CreateChildControls( );

    if ( Controls.Count < 2 )
    {
        return;
    }

    HyperLinkLoc hlink = null;
    if ( Controls[ 1 ].GetType( ) == typeof ( Table ) )
    {
        Table tabel = ( Table ) Controls[ 1 ];

        // Loop through cells to find the go button
        foreach ( TableCell cell in tabel.Rows[ 0 ].Cells )
        {
            if ( cell.Controls.Count > 0 )
            {
                if ( cell.Controls[ 0 ].GetType( ) == typeof ( HyperLinkLoc ) )
                {
                    hlink = ( HyperLinkLoc ) cell.Controls[ 0 ];
                }
            }
        }
        tabel.Visible = false;

I first call the base.CreateChildControls to have SearchBoxEx create all the controls based on the properties set. Then I start looping through the tablecells to find the Go-button, the other controls (textbox & dropdownlist) are available as protected members. After the go-button has been found, I set the table to invisible. Now that I have a reference to the controls I can start building my own look-and-feel:

        if ( m_searchKeyWordTextBox != null )
        {
            HtmlGenericControl div = new HtmlGenericControl( "div" );
            div.Attributes.Add( "class", "ms-sbcell" );
            m_searchKeyWordTextBox.Style.Clear( ); // remove the width style, done in css
            div.Controls.Add( m_searchKeyWordTextBox );
            Controls.Add( div );
        }

        if ( hlink != null )
        {
            HtmlGenericControl div3 = new HtmlGenericControl( "div" );
            div3.Attributes.Add( "class", "ms-sbgo ms-sbcell" );
            div3.Controls.Add( hlink );
            Controls.Add( div3 );
        }

        if ( m_ddlScopes != null )
        {
            HtmlGenericControl div2 = new HtmlGenericControl( "div" );
            div2.Attributes.Add( "class", "ms-sbscopes ms-sbcell" );
            div2.Controls.Add( m_ddlScopes );
            Controls.Add( div2 );
        }
    }
}

Download source: searchbox

Oct 14 / Michiel

VariationSettings Import/Export Utility on Codeplex

I have completed my application for import/export of variations, I have uploaded everything to codeplex so have a look there:

http://www.codeplex.com/variationsettings

A neat little command utility that lets you do three things:
- Export the current variationsettings and labels to xml
- Import the variationsettings and label from xml
- EXPERIMENTAL: create the variation hierachies

Background
This application was created to extend our daily buildprocess for a public internet website

More Information
Check out Michiel Lankamp’s blog entry: http://www.sharepointblog.nl/2007/10/14/variationsettings-importexport-utility-on-codeplex/

Want the code?
Get it from http://www.codeplex.com/variationsettings/SourceControl/ListDownloadableCommits.aspx

Want the build?
Get it from http://www.codeplex.com/variationsettings/Release/ProjectReleases.aspx

Oct 14 / Michiel

Enable variations programmatically

Last week I was trying to enable variations using a feature in SharePoint, but I soon realized that I had a problem because Microsoft decided to make all the variation-class internal. So that shouldn’t be a problem, why not use reflection. That didn’t work either, so back to the good old Lutz Roeder’s Reflector to see what’s going on. I soon found out that variations are dependent on 2 hidden lists (VariationSettings, VariationLabels), both lists can be found using the AllProperties of the root web:

using (SPSite site = new SPSite("http://localhost"))
{
    using (SPWeb rootweb = site.RootWeb)
    {
        string _VarRelationshipsListId = rootweb.AllProperties["_VarRelationshipsListId"].ToString();
        string _VarLabelsListId = rootweb.AllProperties["_VarLabelsListId"].ToString();

        SPList VariationLabels = rootweb.Lists[new Guid(_VarLabelsListId)];
        SPList VariationSettings = rootweb.Lists[new Guid(_VarRelationshipsListId)];
    }
}

The variationlabels list contains a listitem for each variation that is available on the site, just add a new listitem to the list (make sure there is only one listitem that has the field “Is Source” set to true):

SPListItem variation = VariationLabels.Items.Add();
variation["Hierarchy Is Created"] = false;
variation[SPBuiltInFieldId.Title] = "EN";
variation["Description"] = "English";
variation["Flag Control Display Name"] = "English";
variation["Language"] = "en—US";
variation["Locale"] = 1033;
variation["Hierarchy Creation Mode"] = "Publishing Sites and All Pages";
variation["Is Source"] = false;
variation.Update();

The variationsettings are a little bit different, the settings are saved to the propertybag of the rootfolder of the list:

VariationSettings.RootFolder.Properties["SourceVarRootWebTemplatePropertyName"] = “sitetemplate#0”;
VariationSettings.RootFolder.Properties["EnableAutoSpawnPropertyName"] = true;
VariationSettings.RootFolder.Properties["AutoSpawnStopAfterDeletePropertyName"] = true;
VariationSettings.RootFolder.Properties["UpdateWebPartsPropertyName"] = true;
VariationSettings.RootFolder.Properties["CopyResourcesPropertyName"] = true;
VariationSettings.RootFolder.Properties["SendNotificationEmailPropertyName"] = true;
VariationSettings.Update();

These settings where easy, but then came the hard part, how do I create the hierarchies since all logic is internally scoped. Then I had a brainwave, why not try the good old webclient… It works:

using (WebClient webClient = new WebClient())
{
    byte[] response;
    webClient.UseDefaultCredentials = true;
    response = webClient.DownloadData(input.Url + "/_Layouts/VariationLabels.aspx");

    //get viewstate and requestdigest from page
    string viewstate = ExtractHiddenValue("__VIEWSTATE", Encoding.ASCII.GetString(response));
    string requestdigest = ExtractHiddenValue("__REQUESTDIGEST", Encoding.ASCII.GetString(response));

    //format the postdata
    string postData =
        String.Format("__VIEWSTATE={0}&__REQUESTDIGEST={1}&__EVENTTARGET={2}&__EVENTARGUMENT={3}",
                      viewstate, requestdigest,
                      "ctl00$PlaceHolderMain$ctl00$RptControls$variationLabelsCreateHierarchiesToolBarButton",
                      "");

    //send postdata to server
    webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
    webClient.UploadData(input.Url + "/_Layouts/VariationLabels.aspx", "POST",
                         Encoding.ASCII.GetBytes(postData));
}

Now I was finally able to create the hierachies in my build-script.

Oct 9 / Michiel

First publication in .NET Magazine #18

I finally did it…

I wrote my first article (dutch) together with my colleague Willem Boeré for Microsoft .NET Magazine. The article is about building SharePoint Solution with features and custom site-templates.
And finally I will start blogging about SharePoint, I have been doing MOSS 2007 projects for more than a year now (started with beta 1), so I will have quite a lot of stuff to blog about.

Hope to see you back again…