Custom Search Results in KnowledgeLake Imaging for SharePoint - Part 2

by Scosby Friday, December 10, 2010

Introduction

This post uses Imaging for SharePoint version 4.1 and requires the SDK. Contact KnowledgeLake to learn more about Imaging for SharePoint or the SDK. Contact KnowledgeLake Support to obtain the latest SDK if your company already has a license.

This post will demonstrate how to create a Silverlight Search Results control in a SharePoint Solution. This post will use the DataGrid control available in Microsoft’s Silverlight Control Toolkit. When doing any development work, one should always test in a staging/testing environment before going live to a production server. Class files are available for download at the end of the post.

The ViewModel

Create a new folder in the KLSearchExtensions project named ViewModel. Next, add a new class named CustomQueryResultsViewModel to the folder. This class will be responsible for managing the service layer which retrieves search results. Additionally, the User Control will bind to the ViewModel for display of results and status information.

Service Layer

The ViewModel’s constructor should instantiate an instance of the SearchService class, located in the KnowledgeLake.Silverlight.Imaging.Search.Services namespace, into a private member field. The constructor should also wire up the SearchService.FacetSearchCompleted event to a handler that will process the results of our query. In order to use the SearchService class, the Silverlight application needs to have specific bindings in its ServiceReferences.ClientConfig file. The SearchService class will dynamically construct the client proxy with its current URL, thus the client endpoint addresses can be omitted. A downloadable ClientConfig file is included in this post. It should look like the following:

 

<configuration>

  <system.serviceModel>

    <bindings>

      <basicHttpBinding>

        <binding name="FacetQuerySearchSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="LoggerSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="FileInformationSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="WorkflowServiceSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="TaxonomywebserviceSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="RecordsRepositorySoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

        <binding name="IndexWebServiceSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

          <security mode="None" />

        </binding>

      </basicHttpBinding>

    </bindings>

    <client>

      <endpoint binding="basicHttpBinding" bindingConfiguration="FacetQuerySearchSoap" contract="SearchClient.FacetQuerySearchSoap" name="FacetQuerySearchSoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="LoggerSoap" contract="LoggingClient.LoggerSoap" name="LoggerSoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="FileInformationSoap" contract="ViewClient.FileInformationSoap" name="FileInformationSoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="WorkflowServiceSoap" contract="WorkflowClient.WorkflowServiceSoap" name="WorkflowServiceSoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="TaxonomywebserviceSoap" contract="TaxonomyClient.TaxonomywebserviceSoap" name="TaxonomywebserviceSoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="RecordsRepositorySoap" contract="OfficialFileClient.RecordsRepositorySoap" name="RecordsRepositorySoap" />

      <endpoint binding="basicHttpBinding" bindingConfiguration="IndexWebServiceSoap" contract="IndexClient.IndexWebServiceSoap" name="IndexWebServiceSoap" />

    </client>

    <extensions />

  </system.serviceModel>

</configuration>

 

ViewModel Constructor Code Sample

        public CustomQueryResultsViewModel()

        {

            this.searchService = new SearchService();

            this.searchService.FacetSearchCompleted += new EventHandler<FacetSearchEventArgs>(searchService_FacetSearchCompleted);

        }

 

        private void searchService_FacetSearchCompleted(object sender, FacetSearchEventArgs e)

        {

            this.IsBusy = false;

 

            if (e.Results != null && e.Error == null)

            {

                SearchResultSource results = SearchProcessor.ProcessNewSearchResult(e.Results);

 

                results.UpdateColumns = this.UpdateResultColumns(results);

 

                this.SearchResultSource = results;

            }

            else

            {

                this.Message = "Service error getting search!";

            }

        }

 

In the FacetSearchCompleted handler notice two things:

  1. There are a few properties used in the method. The IsBusy and Message properties notify the UI to display certain information to the user, such as a busy indicator or an error message. The SearchResultSource property holds the processed results of our query for later use.
  2. We pass the SearchResults (e.Results) to the SearchProcessor class and the UpdateResultColumns method on the ViewModel. 

The SearchProcessor class is responsible for greatly simplifying the task of binding dynamic results to a data grid. This method is now publically accessible as of KnowledgeLake Imaging for SharePoint 4.0.0.1 (hotfix 1) or higher. The return value from the ProcessNewSearchResult method is of type SearchResultSource. The SearchResultSource class contains an IEnumerable collection of the search results in its Results property.

Detecting New Results Columns

When the Search Service is processing the results, it is important for the grid to know if the end user has added or removed a column so it can rebuild the columns. Thus, the ViewModel needs a method to determine if the results have been updated by the client since a previous query was executed. Create a method named UpdateResultColumns that returns a Boolean and has a parameter of type SearchResultSource. The method should look similar to the following:

 

Detecting New Results Columns Code Sample

        private bool UpdateResultColumns(SearchResultSource results)

        {

            bool updateColumns = true;

 

            if (this.SearchResultSource != null)

            {

                //We need to determine if the user added/removed columns if SearchResultSource is not null.

                if (this.SearchResultSource.ViewColumns.Count < results.ViewColumns.Count)

                {

                    var uniqueColumns = results.ViewColumns.Except(this.SearchResultSource.ViewColumns, new ViewColumnComparer());

 

                    updateColumns = uniqueColumns != null && uniqueColumns.Count() > 0;

                }

                else

                {

                    var uniqueColumns = this.SearchResultSource.ViewColumns.Except(results.ViewColumns, new ViewColumnComparer());

 

                    updateColumns = uniqueColumns != null && uniqueColumns.Count() > 0;

                }

            }

 

            return updateColumns;

        }

 

The UpdateResultColumns method checks to see if the user added or removed columns to the query. If the SearchResultSource parameter has more columns than the model property, the user added columns to the query. In either case, if there are any columns that do not belong the method returns true. The result of the method is used in the FacetSearchCompleted handler to flag the model’s SearchResultSource instance to update the columns. This value is assigned to the SearchResultSource.UpdateColumns property, informing the UI to render the results.

 

Query Layer

The ViewModel should expose two public methods, to be called by the IQueryResults interface from the User Control, named ExecuteQuery and OpenExecuteSearch. The methods should look similar to the following:

Query Layer Code Sample

        public void ExecuteQuery()

        {

            if (!string.IsNullOrWhiteSpace(this.Query))

            {

                this.IsBusy = true;

 

                this.searchService.FacetSearch(this.Query);

            }

        }

 

        public void OpenExecuteSearch(string keywords)

        {

            if (!string.IsNullOrWhiteSpace(keywords))

            {

                string query = QueryManager.GetSearchQuery(keywords, "And", false);

 

                this.Query = query;

            }

        }

 

The OpenExecuteSearch method is called by the KnowledgeLake Search Center’s implementation of the OpenSearch protocol. Visit OpenSearch.org for more information. Review the KnowledgeLake Imaging for SharePoint to learn more about how the Search Center leverages the OpenSearch protocol.

The QueryManager class is responsible for constructing a query into a format used by KnowledgeLake Search for performing a SharePoint Search query. The user should never attempt to manipulate this string; again, the QueryManager class encapsulates all necessary logic to construct the query. The User Control will be notified, via a BindingExpression, of the updated Query property value and call the ExecuteQuery method to begin retrieving search results.

Part three of this series will look at the User Control for the Custom Search Results.

Download Files: CustomQueryResults.zip

Blog Posts In This Series

Tags: , , , ,

IT | Programming

blog comments powered by Disqus