Custom Search Results in KnowledgeLake Imaging for SharePoint - Part 3

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 User Control

The project will use the Silverlight Toolkit’s DataGrid for displaying our Search Results to the end user. Add a new user control to the KLSearchExtensions project, and name it CustomQueryReults. This name should be used exactly as shown otherwise KnowledgeLake Imaging for SharePoint will not recognize the user’s custom control.

XAML

<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"

             x:Class="KLSearchExtensions.CustomQueryResults"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

             xmlns:viewModel="clr-namespace:KLSearchExtensions.ViewModel"

             xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"

             mc:Ignorable="d"

             d:DesignHeight="480"

             d:DesignWidth="640">

    <UserControl.DataContext>

        <viewModel:CustomQueryResultsViewModel x:Name="Model"

                                               PropertyChanged="Model_PropertyChanged" />

    </UserControl.DataContext>

    <Grid x:Name="LayoutRoot">

        <controlsToolkit:BusyIndicator IsBusy="{Binding IsBusy}">

            <sdk:DataGrid x:Name="dataGrid"

                          AutoGenerateColumns="False"

                          HorizontalAlignment="Stretch"

                          VerticalAlignment="Stretch"

                          Margin="4" />

        </controlsToolkit:BusyIndicator>

    </Grid>

</UserControl>

 

DataContext

The User Control’s data context is an instance of our ViewModel. In XAML, the User Control should look similar to the following, where the XML namespace viewModel points to your ViewModel’s project namespace (in this case it is KLSearchExtensions.ViewModel):

LayoutRoot

The User Control’s root element is a Grid. Add the Silverlight Toolkit’s BusyIndicator to the Grid. The BusyIndicator control hosts the actual content of our control, but allows the user to display an information message during loading or searching. Add the Silverlight Toolkit’s DataGrid to the BusyIndicator control. Be sure to set the AutoGenerateColumns property to false, the Search Results will be dynamically built for the user to bind to the grid.

Code Behind

A Strongly Typed ViewModel

The user will benefit from creating a convenience property to get an instance of the ViewModel from the User Control. Create a new private property named TypedModel. This property will encapsulate a strongly typed instance of the ViewModel for our User Control. In other words, the property returns the User Control’s DataContext property as an instance of our ViewModel. The code should look like the following:

 

Strongly Typed ViewModel Code Sample

        private CustomQueryResultsViewModel TypedModel

        {

            get

            {

                return this.DataContext as CustomQueryResultsViewModel;

            }

        }

IQueryResults Interface

Implement the IQueryResults interface from the namespace KnowledgeLake.Silverlight.Search.Contracts. After implementing the interface, the Error event will be defined. This event is raised by KnowledgeLake Imaging for SharePoint when an error occurs during the loading of the Search Result extension. The user should not raise this event, but the user can handle this event. It is important to note, since this is a Silverlight application the user will have to log to IsolatedStorage on the client’s machine or call a webservice to record the error. However, handling this error is beyond the scope of this post.

 

The IQueryResults interface members OpenExecuteSearch (method) and Query (property) will be implemented as wrappers around the User Control’s ViewModel members. The ExecuteQueryStringSearch method is beyond the scope of this post and will not be implemented. The interface members’ implementations should look similar to the following:

 

IQueryResults Interface Code Sample

        public event EventHandler<KnowledgeLake.Silverlight.EventArgs<Exception>> Error;

 

        public void ExecuteQueryStringSearch()

        {

            throw new NotImplementedException();

        }

 

        public void OpenExecuteSearch(string keywords)

        {

            this.GetTypedModel().OpenExecuteSearch(keywords);

        }

 

        public string Query

        {

            get { return this.GetTypedModel().Query; }

            set { this.GetTypedModel().Query = value; }

        }

ViewModel PropertyChanged Event

The PropertyChanged event is declared in the User Control’s XAML and wired up to an event handler called Model_PropertyChanged, this handler must respond to the Query and SearchResultSource properties. The method should look like the following:

ViewModel Property Changed Code Sample

        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)

        {

            var model = sender as CustomQueryResultsViewModel;

 

            switch (e.PropertyName.ToUpperInvariant())

            {

                case "QUERY":

                    model.ExecuteQuery();

                    break;

 

                case "SEARCHRESULTSOURCE":

                    this.RenderResults(model.SearchResultSource);

                    break;

 

                case "MESSAGE":

                    this.DisplayMessage(model.Message);

                    break;

            }

        }

Displaying Search Results

Create a method called RenderResults which takes a parameter of type SearchResultSource. This method will render the search results into the DataGrid. If the user has added or removed columns to the query since it was last executed, the UpdateColumns property will evaluate to true. In this scenario, it is necessary to clear the grid of all columns and rebuild the grid.

 

The SearchResultSource.ViewColumns items are all represented as a property of each item in the SearchResultSource.Results collection. The SearchResultSource.Results property is dynamically built to contain the corresponding ViewColumn’s value when bound to the DataGrid.ItemsSource property. In other words, these are the rows of the grid with the column values included.

 

Displaying Search Results Code Sample

        private void RenderResults(SearchResultSource result)

        {

            if (result.ViewColumns != null)

            {

                if (result.UpdateColumns) //user changed result columns on the query builder

                {

                    this.dataGrid.Columns.Clear();

 

                    foreach (ViewColumn column in result.ViewColumns)

                    {

                        var item = new DataGridTextColumn();

                        item.IsReadOnly = true;

                        item.Header = column.FriendlyName;

                        item.Binding = new Binding(column.InternalName);

 

                        this.dataGrid.Columns.Add(item);

                    }

                }

 

                this.dataGrid.ItemsSource = result.Results;

            }

        }

 

If the end user added or removed columns to the query, the method clears the grid’s columns and rebuilds them from the SearchResultSource.ViewColumns collection. As the user iterates the ViewColumns collection, be sure to create a new Binding for the column using the ViewColumn.InternalName property. This binding enables the grid to display the appropriate column value for each item in the SearchResultSource.Results collection. This is powerful functionality provided by KnowledgeLake Imaging for SharePoint. Essentially, the user does not need to worry about what columns the user chose to include in the query and how to bind those columns to the Search Results.

 

 

 

 

Download Files: CustomQueryResults.zip

Blog Posts In This Series

View full size...

Finished Custom Control

Tags: , , , ,

IT | Programming

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

Custom Search Results in KnowledgeLake Imaging for SharePoint - Part 1

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.

Getting Started

Extending KnowledgeLake Imaging for SharePoint

KnowledgeLake Imaging for SharePoint allows for the extension of the Search Results control in the search center and the web part with a custom Silverlight 4 control. This is a powerful feature because it gives users the ability to customize the display of KnowledgeLake Search Results in a variety of ways. However, any custom Search Results control will replace the default KnowledgeLake control in both the search center and the web part. There is no way to add functionality to the default KnowledgeLake Search Results control. In fact, a custom control will not inherit any functionality from the default control. All the functionality of a custom control must be implemented by the developer from the ground up. See figure 1 below for a picture of the default control in the search center, it would look similar in the web part.

Figure 1 - Default Search Results Control

View full size...

Setting Up the SharePoint Solution Project

Create a new Silverlight 4 application and name it KLSearchExtensions. The Silverlight application does not need to be hosted in a new web site. Uncheck the “Host the Silverlight application in a new Web site” box in the New Silverlight Application dialog, it should now appear similar to Figure 2 below.

Figure 2 - Create a New Silverlight Application
View full size...

In my previous post on Creating SharePoint 2010 Solutions for Silverlight Applications I described how to easily deploy Silverlight applications to SharePoint 2010. This approach allows the developer to hit F5 to build, deploy, and debug the Silverlight extension. Pretty slick stuff! Create a new SharePoint Solution and name it ExtensionSolution, as shown in Figure 3 below.

Figure 3 - Create a New SharePoint Solution

 View full size...

The extension must be placed in the %SharePointRoot%\Template\Layouts\KLClientBin directory, thus set the Deployment Location’s Path property appropriately. This is shown in Figure 7 below. Read more about project output references in my previous post mentioned above.

Figure 4 - Set Deployment Location Path

View full size...

Creating the Extension Project

The extension will be designed to use the Model-View-ViewModel (MVVM) pattern. Add to the project the following references from the KnowledgeLake Imaging for SharePoint SDK and Silverlight Control Toolkit:

·         KnowledgeLake.Silverlight.dll

·         KnowledgeLake.Silverlight.Imaging.Search.dll

·         KnowledgeLake.Silverlight.Search.Contracts.dll

·         System.Windows.Controls.Input.Toolkit.dll

·         System.Windows.Controls.Toolkit.dll

KnowledgeLake Imaging for SharePoint requires a user control to be specifically named and implement a specific interface. Add a new User Control to the KLSearchExtensions project and name it CustomQueryResults, if the control is not exactly named this way the extension won’t be recognized.

Part two of this series will look at the ViewModel for the Custom Search Results.

Download Files: CustomQueryResults.zip (3.46 kb)

Blog Posts In This Series

Tags: , , , ,

IT | Programming

Creating SharePoint 2010 Solutions for Silverlight Applications

by Scosby Tuesday, March 9, 2010

The goal of this post is to explain the process of creating a SharePoint Solution that deploys a Silverlight application. At the time this post was written, I was using Visual Studio 2010 RC and MSS 2010 "beta 2".

There are two primary benefits for using the SharePoint Solution. First, we can initially develop the SL application using our SharePoint Solution and then have an easier transition to production. Last, but not least, we can have our SL app compiled and added to the SharePoint Solution during the build process. This is probably the most beneficial reason for using this technique. Admittedly, there are other ways of deploying your SL app but this one makes a lot of sense when you consider that you'll probably want to have a custom Site Definition for hosting your SL app anyways. But enough background information already, on with the show!

  1. Create a new site definition project, even if you’re not creating a site definition. This creates the special “Site Definition” folder that you cannot add later. So it’s better to have it and not use it, than to need it and not have it.

  2. Enter your development machine’s url

  3. Close the newly created onet.xml file for the site definition. Unless, of course, you are creating a new site definition.
  4. Set your new SharePoint Solution as the Startup Project.
  5. Add your Silverlight Application to your solution.
  6. In order to dynamically compile the Silverlight Application project’s output into our SharePoint solution, we need to use a Module. Add a new module to the project.

  7. Close the newly created Elements.xml file and then delete the Sample.txt file underneath your module.
  8. Click on the Module in Solution Explorer. Open the Project Output References property window.


    You should see a screen similar to the following:

  9. Click add and then change the Deployment Type to TemplateFile. Choose your Silverlight project in the Project Name property drop down. Next, expand the Deployment Location property and then change the Path to whatever location you want. See Developing SharePoint solutions for a more comprehensive chart of the Deployment Type file locations.

  10. A Module is deployed with a Feature. If you have not already defined one, a default Feature will be created to host the module.
  11. Press F5 to deploy the solution and begin debugging.

Tags: , , ,

IT | Programming