Tag: Search

Populating the internal search report in Sitecore

Out the box Sitecore ships with a number of reports pre-configured. Some of these will show data without you doing anything. e.g. The pages report will automatically start showing the top entry and exit pages as a page view is something Sitecore can track.

Other's like the internal search report will just show a message of no data to display, which can be confusing/frustrating for your users. Particularly when they've just spent money on a license fee to get great analytics data only to see a blank report.

The reason it doesn't show any information is relatively straight forward. Sitecore doesn't know how your site search is going to work and therefore it can't do the data capture part of the process. That part of the process however is actually quite simple to do.

Sitecore has a set of page events that can be registered in the analytics tracker. Some of these like Page Visited will be handled by Sitecore. In this instance the one we are interested in is Search and will we have to register it manually.

To register the search event use some code like this (note, there is a constant that references the item id of the search event). The query parameter should be populated with the search term the user entered.

using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using SitecoreItemIds;

namespace SitecoreServices
{
  public class SiteSearch
  {
      public static void TrackSiteSearch(Item pageEventItem, string query)
      {
          Assert.ArgumentNotNull(pageEventItem, nameof(pageEventItem));
          Assert.IsNotNull(pageEventItem, $"Cannot find page event: {pageEventItem}");

          if (Tracker.IsActive)
          {
              var pageEventData = new PageEventData("Search", ContentItemIds.Search)
              {
                  ItemId = pageEventItem.ID.ToGuid(),
                  Data = query,
                  DataKey = query,
                  Text = query
              };
              var interaction = Tracker.Current.Session.Interaction;
              if (interaction != null)
              {
                  interaction.CurrentPage.Register(pageEventData);
              }
          }
      }
  }
}

Now after triggering the code to be called a few times, your internal search report should start to be populated like this.

Sitecore Search and Indexing: Creating a simple search

With Sitecore 7, Sitecore introduced the new Sitecore.ContentSearch API which out of the box can query Lucene and SOLR based index's.

Searching the index's has been made easier through Linq to Sitecore that allows you to construct a query using Linq, the same as you would use with things like Entity Framework or Linq to SQL.

To do a query you first need a search context. Here I'm getting the a context on one of the default index's:

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext()) { ... }

Next a simple query would look like this. Here I'm doing a where parameter on the "body" field:

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
  IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item["body"] == “Sitecore”)
}

But what if you want to add a search to your site. Typically you would want to filter on more than one field, what the user enters may be a collection of words rather than an exact phrase and you'd also like some intelligent ordering to your results.

Here I am splitting the search term on spaces and then building a predicate that has an "or" between each of its conditions. For each condition rather than doing a .Contains on a specific field, I'm doing it on a content field that will contain data for all fields in the item.

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
  IQueryable<SearchResultItem> query = context.GetQueryable<SearchResultItem>();

  var predicate = PredicateBuilder.True<SearchResultItem>();

  foreach (string term in criteria.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
  {
      predicate = predicate.Or(p => p.Content.Contains(searchTerm.Trim()));
  }

  SearchResults<SearchResultItem> searchResults = query.Where(predicate).GetResults();

  results = (from hit in searchResults.Hits
                      select hit.Document).ToList();
}

The intelligent ordering of results you will get for free based on what was search for.

Introduction to Real Time Search

The Real Time Search this article relates to is a dll that comes included in the Web Client Software Factory one of Microsoft's patterns and practices downloads. What it can do and what this example will demonstrate is adding the ability cause a post back that will refresh an update panel as the users types into a search box. This can give a great effect and make a web app really user friendly in scenarios like searching photo's, emails or any general list.

First late me state though that this is in no way the most optimal way program. In most scenarios you could built a better result using something like JSON as their will be a lot less data transfer, which is also a general reason to avoid update panels. However this is also very quick and very easy to implement, not to mention if you've ever used update panels before you already know 90% of what's needed. This can also only work in situations where you have a good search that is going to return the result quickly, rather than leaving the user sitting there trying to work out why nothing's happening and where the search button has gone.

Implementing Real Time Search

For this example I will be filtering a table from a DB based on the search criteria and refreshing a Grid View with the results. I will be using a normal C# Web Site project with the Adventure Works sample DB from Microsoft. DB connection will be done using LINQ to EntityFramework, however there is no need to use this it is just my preference for the example.

First off set up you're website and db and make sure both are working with no problems. As results will be displayed in an Update Panel, get one of these along with a script manager added to your page, so it looks something like this:

<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate></ContentTemplate>
</asp:UpdatePanel>
</div>
</form>

Next let's get the search working in the normal method, so I'm going to create my Entity Model and add a textbox and gridview to show the results. Again you can connect and show your results however you want. You should now have something like this in your aspx file:

<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

<asp:TextBox ID="txtSearch" runat="server" OnTextChanged="TextChanged" Text=""></asp:TextBox>

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>

<asp:LinqDataSource ID="LinqDataSource1" runat="server" onselecting="LinqDataSource1_Selecting">
</asp:LinqDataSource>

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="LinqDataSource1">
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ProductID"
ReadOnly="True" SortExpression="ProductID" />
<asp:BoundField DataField="Name" HeaderText="Name"
ReadOnly="True" SortExpression="Name" />
<asp:BoundField DataField="ProductNumber" HeaderText="ProductNumber" ReadOnly="True" SortExpression="ProductNumber" />
<asp:BoundField DataField="Color" HeaderText="Color"
ReadOnly="True" SortExpression="Color" />
<asp:BoundField DataField="SafetyStockLevel" HeaderText="SafetyStockLevel"
ReadOnly="True" SortExpression="SafetyStockLevel" />
<asp:BoundField DataField="ReorderPoint" HeaderText="ReorderPoint"
ReadOnly="True" SortExpression="ReorderPoint" />
</Columns>
</asp:GridView>
</ContentTemplate>

</asp:UpdatePanel>
</div>
</form>

And this in your code behind:

protected void LinqDataSource1_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
Model.AdventureWorks2008Entities AdventureWorkds = new Model.AdventureWorks2008Entities();
var products = from p in AdventureWorkds.Product
where p.Name.Contains(txtSearch.Text)
select p;
e.Result = products;
}

Next its time to add the Real Time Search. Make sure you have the dll downloaded (you may need to compile the download to get it) and add it to your bin folder. Add the following to your page in the relevant places:

<%@ Register Assembly="RealTimeSearch" Namespace="RealTimeSearch" TagPrefix="cc1" %>

<cc1:RealTimeSearchMonitor ID="RealTimeSearchMonitor1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
<ControlsToMonitor>
<cc1:ControlMonitorParameter EventName="TextChanged" TargetID="txtSearch" />
</ControlsToMonitor>
</cc1:RealTimeSearchMonitor>

Important things to notice here are the AssociatedUpdatePanelId which tells the control what it has to refresh and the controls to monitor section which sets what the control to watch is called and the event name that will be fired when the post back is created. You will now need to corresponding control in your code behind like so:

protected void TextChanged(object sender, EventArgs e)
{
GridView1.DataBind();
}

Run the site and  you should find that the grid view now updates as you type (all be it with a slight delay).

To improve you can add other controls like update progress to show something happening which will help with any delays in displaying the results.