THIS IS ARCHIVED DOCUMENTATION

LINQ QueryResults Example: Adding Facets

Coveo for Sitecore (July 2016) Coveo for Sitecore 4.1 (November 2018)

This section contains information on how to add facets to your search page.

It uses the basic search page presented in LINQ QueryResults Example - Create a Basic Search Page.

Add a New Field in the Model

The example uses the TemplateName field to include in the facets results, so you need to add it to the model:

LinqBasedPageExample_4.aspx.cs

public class SearchResult {
    public DateTime Date { get; set; }
    public string Title { get; set; }
    public string PrintableURI { get; set; }
    // IndexField tells the queryable to map the "_templatename" to the TemplateName property.
    [IndexField("_templatename")]
    public string TemplateName { get; set; }
}

Add the Facets to the Query

To compute facet values, you need to add an extension to your query.

Ensure that the field is configured as a Facet in the index configuration, or else the query won’t return any possible value for your field.

LinqBasedPageExample_4.aspx.cs

IQueryable<SearchResult> query = context.GetQueryable<SearchResult>()
                                        .FacetOn(item => item.TemplateName)
                                        .CoveoOr(model.Query);

Add the Facets Section to the Interface

The QueryResults object already contains a property to retrieve the corrected queries.

To handle those corrections, add the following section to your page, preferably in the ResultsSection. The Repeater shows each corrected query as a label.

LinqBasedPageExample_4.aspx

<div class="facetsSection">
    <asp:Repeater ID="FacetsRepeater" runat="server">
        <ItemTemplate>
            <div class="facetSection">
                <div class="facetHeader">
                    <asp:Label Text='<%# Eval("FieldName") %>' runat="server" />
                </div>
                <div class="facetValues"></div>
            </div>
        </ItemTemplate>
    </asp:Repeater>
</div>

You can then bind the facets with the Repeater in your RenderWithResults method:

LinqBasedPageExample_4.aspx.cs

if(results.GroupByResults.Count > 0) {
    FacetsRepeater.DataSource = results.GroupByResults;
    FacetsRepeater.DataBind();
}

Result

The result should look like this:

Adding Facet Values to the Interface

The last result needs more work to be usable. To add values extracted from the query, add the following code in the facetValues section:

LinqBasedPageExample_4.aspx

<div class="facetValues">
    <asp:Repeater DataSource='<%# Eval("Values") %>' runat="server">
        <SeparatorTemplate><br/></SeparatorTemplate>
        <ItemTemplate>
            <asp:Label Text='<%# Eval("Value") %>' runat="server" />
        </ItemTemplate>
    </asp:Repeater>
</div>

If no results appear here and the resulting items correctly return values for this field, it might be that the field for the facet isn’t configured as a Facet in the index configuration.

Adding Interactivity to the Interface with Facet Values

You need to add interactivity with your facet values for them to be useful. This section contains a basic example, and covers only a fraction of the native implemented features present in the Coveo for Sitecore facets components.

Modifying Models

First, you need to create a new class to contain the Facet information.

The Expression property is a helper that generates the expression to match the field with its value.

LinqBasedPageExample_4.aspx.cs

public class FacetModel {
    public string FieldName { get; set; }
    public string FieldValue { get; set; }
    public string Expression {
        get {
            return "@" + FieldName + "==\"" + FieldValue + "\"";
        }
    }
}

Then, add this property in the SearchModel.

LinqBasedPageExample_4.aspx.cs

public class SearchModel {
    public string Query { get; set; }
    public IList<FacetModel> Facets { get; set; }
    public SearchModel() {
        Query = "";
        Facets = new List<FacetModel>();
    }
}

Modifying the Query

You now need to handle the Facets in your query.

LinqBasedPageExample_4.aspx.cs

using (var context = index.CreateSearchContext()) {
      IQueryable<SearchResult> query = context.GetQueryable<SearchResult>()
                                              .FacetOn(item => item.TemplateName)
                                              .CoveoOr(model.Query);
      foreach(FacetModel facet in model.Facets) {
          query = query.CoveoWhere(facet.Expression);
      }
      results = query.GetCoveoQueryResults();
}

Modifying the Interface

This solution is neither efficient nor readable, but serves the purpose of this tutorial well.

Feel free to adapt your own implementation to your use case.

You now need to map the CheckBox values to the model.

To ease this process, add an ID to your Repeater and facetHeader section Label, and change your facet value Label to a Checkbox.

LinqBasedPageExample_4.aspx

<div class="facetHeader">
    <asp:Label ID="FacetFieldName" Text='<%# Eval("FieldName") %>' runat="server" />
</div>
<div class="facetValues">
    <asp:Repeater ID="FacetValues" DataSource='<%# Eval("Values") %>' runat="server">
        <SeparatorTemplate><br/></SeparatorTemplate>
        <ItemTemplate>
            <asp:Checkbox ID="ValueCheckbox" Text='<%# Eval("Value") %>' runat="server" AutoPostBack="true" />
        </ItemTemplate>
    </asp:Repeater>
</div>

It’s now possible to get the CheckBox values in the mapping.

LinqBasedPageExample_4.aspx.cs

private void MapControlsToModel(SearchModel model) {
    model.Query = Querybox.Text;
    IList<FacetModel> facets = new List<FacetModel>();
    foreach(RepeaterItem facetItem in FacetsRepeater.Items){
        Label fieldNameLabel = facetItem.FindControl("FacetFieldName") as Label;
        Repeater valuesRepeater = facetItem.FindControl("FacetValues") as Repeater;
        foreach(RepeaterItem facetValueItem in valuesRepeater.Items){
            CheckBox box = facetValueItem.FindControl("ValueCheckbox") as CheckBox;
            if(box.Checked){
                facets.Add(new FacetModel {
                    FieldName = fieldNameLabel.Text,
                    FieldValue = box.Text
                });
            }
        }
    }
    model.Facets = facets;
}

Result

The new search page should look like this:

When selecting the Media folder facet value, you should see something similar to this:

Client-Side Code

The client-side code should now look like this:

LinqBasedPageExample_4.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="LinqBasedPageExample.aspx.cs" Inherits="Tutorial.LinqBasedPageExample" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>LINQ-based Page Example</title>
</head>
<body>
    <form id="pageForm" runat="server">
        <div class="searchSection">
            <asp:Literal Text="Query: " runat="server" />
            <asp:TextBox ID="Querybox" runat="server" />
            <asp:Button type="submit" Text="Run Query" runat="server" />
        </div>
        <div ID="ResultsSection" class="resultsSection" runat="server">
            <div class="facetsSection">
                <asp:Repeater ID="FacetsRepeater" runat="server">
                    <ItemTemplate>
                        <div class="facetSection">
                            <div class="facetHeader">
                                <asp:Label ID="FacetFieldName" Text='<%# Eval("FieldName") %>' runat="server" />
                            </div>
                            <div class="facetValues">
                                <asp:Repeater ID="FacetValues" DataSource='<%# Eval("Values") %>' runat="server">
                                    <SeparatorTemplate><br/></SeparatorTemplate>
                                    <ItemTemplate>
                                        <asp:Checkbox ID="ValueCheckbox" Text='<%# Eval("Value") %>' runat="server" AutoPostBack="true" />
                                    </ItemTemplate>
                                </asp:Repeater>
                            </div>
                        </div>
                    </ItemTemplate>
                </asp:Repeater>
            </div>
            <asp:GridView ID="LSTResults" AutoGenerateColumns="true" runat="server" />
        </div>
    </form>
</body>
</html>

Server-Side Code

The server-side code should now look like this:

LinqBasedPageExample_4.aspx.cs

namespace Tutorial {
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using Coveo.Framework.SearchService;
    using Coveo.SearchProvider.Linq;
    using Coveo.SearchProvider.LinqBase;
    using Sitecore.ContentSearch;
    using Sitecore.ContentSearch.Linq;
    using Sitecore.ContentSearch.SearchTypes;
    public partial class LinqBasedPageExample : Page {
        protected void Page_Load(object sender, EventArgs e) {
            SearchModel searchModel = new SearchModel();
            if (Page.IsPostBack) {
                MapControlsToModel(searchModel);
                Render(searchModel);
            }
        }
        private void Render(SearchModel model) {
            if(!String.IsNullOrEmpty(model.Query)) {
                ClearResults();
                QueryResults<SearchResult> results = ExecuteQuery(model);
                if(results.TotalCount != 0) {
                    RenderWithResults(model, results);
                }else{
                    RenderWithoutResults(model, results);
                }
                RenderCommon(model, results);
            }
        }
        private void ClearResults() {
            LSTResults.DataSource = "";
            LSTResults.DataBind();
        }
        private void RenderWithResults(SearchModel model,
                                       QueryResults<SearchResult> results) {
            LSTResults.DataSource = results.Results;
            LSTResults.DataBind();
            if(results.GroupByResults.Count > 0) {
                FacetsRepeater.DataSource = results.GroupByResults;
                FacetsRepeater.DataBind();
            }
        }
        private void RenderWithoutResults(SearchModel model,
                                          QueryResults<SearchResult> results) {
            // No results handling.
        }
        private void RenderCommon(SearchModel model,
                                  QueryResults<SearchResult> results) {
            // Rendering with or without results.
        }
        private QueryResults<SearchResult> ExecuteQuery(SearchModel model) {
            QueryResults<SearchResult> results;
            ISearchIndex index = ContentSearchManager.GetIndex("Coveo_web_index");
            using (var context = index.CreateSearchContext()) {
                  IQueryable<SearchResult> query = context.GetQueryable<SearchResult>()
                                                          .FacetOn(item => item.TemplateName)
                                                          .CoveoOr(model.Query);
                  foreach(FacetModel facet in model.Facets) {
                      query = query.CoveoWhere(facet.Expression);
                  }
                  results = query.GetCoveoQueryResults();
            }
            return results;
        }
        private void MapControlsToModel(SearchModel model) {
            model.Query = Querybox.Text;
            IList<FacetModel> facets = new List<FacetModel>();
            foreach(RepeaterItem facetItem in FacetsRepeater.Items){
                Label fieldNameLabel = facetItem.FindControl("FacetFieldName") as Label;
                Repeater valuesRepeater = facetItem.FindControl("FacetValues") as Repeater;
                foreach(RepeaterItem facetValueItem in valuesRepeater.Items){
                    CheckBox box = facetValueItem.FindControl("ValueCheckbox") as CheckBox;
                    if(box.Checked){
                        facets.Add(new FacetModel {
                            FieldName = fieldNameLabel.Text,
                            FieldValue = box.Text
                        });
                    }
                }
            }
            model.Facets = facets;
        }
    }
    public class SearchModel {
        public string Query { get; set; }
        public IList<FacetModel> Facets { get; set; }
        public SearchModel() {
            Query = "";
            Facets = new List<FacetModel>();
        }
    }
    public class SearchResult {
        public DateTime Date { get; set; }
        public string Title { get; set; }
        public string PrintableURI { get; set; }
        [IndexField("_templatename")]
        public string TemplateName { get; set; }
    }
    public class FacetModel {
        public string FieldName { get; set; }
        public string FieldValue { get; set; }
        public string Expression {
            get {
                return "@" + FieldName + "==\"" + FieldValue + "\"";
            }
        }
    }
}

Going Further

This tutorial only covers the basic use for facets; it’s still not yet usable in a production environment.

Here are some improvement possibilities for facets.

Changing the Name of the Facet

While the query uses the item property to decide which facet to compute, the results returned by the QueryResults object are noted in the Coveo field format.

To improve the displayed name, it would be possible to translate the field back to the Sitecore format.

Keeping All the Possible Facets Values in the Facet

The current implementation always updates the facets values after each query, meaning that after a filter is applied, the only returned value is the one that was selected.

It would be possible to split the query in two parts: one for the results, and one for the facets, in order to keep all the possible values for a facet.

In this case, it would be possible to mirror the currently checked filters with the shown checkboxes, and react if the user unchecks a filter, resulting in a much better user experience.

Styling the Facets

It’s also possible to show results in different ways, like a DropDownList, or even a slider.

The Coveo for Sitecore components offer a variety of tools that can be used out-of-the-box for these use case, and are more time efficient than coding your own LINQ.