THIS IS ARCHIVED DOCUMENTATION

LINQ QueryResults Advanced Example: Bind the Model to URL Parameters

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

This section shows a simple implementation to handle serialization of the model in the URL parameters.

Since this is a feature that requires a fully functioning search page, it’s based on the tutorial presented in LINQ QueryResults Example - Putting it All Together instead of the base one.

Adding a Mapping from the Model to the Controls

To handle the parameters from the query, you need to have a mapping from the model to the controls.

LinqBasedPageExample_A1.aspx.cs

private void MapModelToControls(SearchModel model) {
    Querybox.Text = model.Query;
    NbResultsPerPage.Text = model.NbResultsPerPage.ToString();
    PageNb.SelectedValue = model.Page.ToString();
    SortDescending.Checked = model.SortDescending;
    SortList.SelectedValue = model.SortType;
}

For testing purposes, you can now modify the Page_Load method to map the model to the control.

Modify the properties in-between and validate that your controls have the modified values.

LinqBasedPageExample_A1.aspx.cs

protected void Page_Load(object sender, EventArgs e) {
    SearchModel searchModel = new SearchModel();
    // Modify searchModel properties here.
    MapModelToControls(searchModel);
    if (Page.IsPostBack) {
        MapControlsToModel(searchModel);
        Render(searchModel);
    }
}

Transform the Model to URL Parameters

First, add two methods in the SearchModel class that handles serialization.

This tutorial completes these methods component per component.

LinqBasedPageExample_A1.aspx.cs

public string ToQueryParams() {
    return SerializeQueryParamsDictionary(ToQueryParamsDictionary());
}
private IDictionary<string, string> ToQueryParamsDictionary() {
    IDictionary<string, string> queryParamsDict = new Dictionary<string, string>();
    return queryParamsDict;
}
private string SerializeQueryParamsDictionary(IDictionary<string, string> queryParamsDict) {
    return "";
}

Serialize the Dictionary

To test the serialization as early as possible, add the following code in the SerializeQueryParamsDictionary method.

It reads the dictionary, and outputs a string with the query parameters set.

LinqBasedPageExample_A1.aspx.cs

private string SerializeQueryParamsDictionary(IDictionary<string, string> queryParamsDict) {
    if(queryParamsDict.Count > 0) {
        IList<string> paramsList = new List<string>();
        foreach (KeyValuePair<string, string> pair in queryParamsDict) {
            paramsList.Add(pair.Key + "=" + pair.Value);
        }
        return "?" + string.Join("&", paramsList);
    } else {
        return "";
    }
}

Adding the Query to the Dictionary

Add the following code to add the query to dictionary.

It uses a constant string as the parameter to help you later, when you want to read the values back.

LinqBasedPageExample_A1.aspx.cs

private const string QUERY_PARAMETER_NAME = "query";
private IDictionary<string, string> ToQueryParamsDictionary() {
    IDictionary<string, string> queryParamsDict = new Dictionary<string, string>();
    if(!String.IsNullOrEmpty(Query)) {
        queryParamsDict.Add(QUERY_PARAMETER_NAME, Query);
    }
    // Other values here.
    return queryParamsDict;
}

Calling searchModel.ToQueryParams() while searchModel.Query is defined should return ?query=YOUR_QUERY_VALUE.

The remaining parameters are added later in the tutorial.

Reading Back the Parameters

Add the following code to the SearchModel class to let the model read values.

This method is used in the next step of the tutorial.

LinqBasedPageExample_A1.aspx.cs

public void FromQueryParams(NameValueCollection queryString) {
    Query = queryString[QUERY_PARAMETER_NAME];
}

Building the URI When Sending a Query and Redirecting

Since you want the search page to be driven by the URL parameter, it would be better to handle the parameter in the PostBack and redirect your client to the correct page according to their model.

Add the following methods to your page.

  • GetUri returns the part before the query parameters in your URI.
  • GetUriWithParameters returns the URI with the query parameters from the model.

LinqBasedPageExample_A1.aspx.cs

private string GetUri() {
    string url = HttpContext.Current.Request.Url.AbsoluteUri;
    return url.IndexOf("?") > -1 ? url.Substring(0, url.IndexOf("?")) : url;
}
private string GetUriWithParameters(SearchModel model) {
    return GetUri() + model.ToQueryParams();
}

You’re now ready to handle the redirection when your user sends a query:

LinqBasedPageExample_A1.aspx.cs

protected void Page_Load(object sender, EventArgs e) {
    SearchModel searchModel = new SearchModel();
    searchModel.FromQueryParams(Request.QueryString);
    if (Page.IsPostBack) {
        MapControlsToModel(searchModel);
        Response.Redirect(GetUriWithParameters(searchModel));
    } else {
        Render(searchModel);
        MapModelToControls(searchModel);
    }
}

Result

When querying on your search page, it should now update your URL. For example, for this query:

Your URL would end with:

?query=sitecore

Binding the Other Components

Now that your query is bound, you’re ready to add the other components to the serializer and deserializer.

Pager

For the pager, you need two new properties:

LinqBasedPageExample_A1.aspx.cs

private const string NB_RESULTS_PER_PAGE_PARAMETER_NAME = "nbResultsPerPage";
private const string PAGE_PARAMETER_NAME = "page";

Add the following code to deserialize the query parameters to the model:

LinqBasedPageExample_A1.aspx.cs

public void FromQueryParams(NameValueCollection queryString) {
    // Other components
    string nbResultsPerPage = queryString[NB_RESULTS_PER_PAGE_PARAMETER_NAME];
    if(nbResultsPerPage != null) {
        NbResultsPerPage = int.Parse(nbResultsPerPage);
    }
    string page = queryString[PAGE_PARAMETER_NAME];
    if(page != null) {
        Page = int.Parse(page);
    }
    // Other components
}

Add the following code to serialize the model to query parameters:

LinqBasedPageExample_A1.aspx.cs

private IDictionary<string, string> ToQueryParamsDictionary() {
    // Other components
    if(NbResultsPerPage != DEFAULT_NB_RESULTS_PER_PAGE) {
        queryParamsDict.Add(NB_RESULTS_PER_PAGE_PARAMETER_NAME, NbResultsPerPage.ToString());
    }
    if(Page != 0) {
        queryParamsDict.Add(PAGE_PARAMETER_NAME, Page.ToString());
    }
    // Other components
    return queryParamsDict;
}

Facets

For the facets, you need one new property:

LinqBasedPageExample_A1.aspx.cs

private const string FACETS_PARAMETER_NAME = "facets";

The facets are a little harder, since there can be many values for a given facet.

Add the following code to deserialize the query parameters to the model:

LinqBasedPageExample_A1.aspx.cs

public void FromQueryParams(NameValueCollection queryString) {
    // Other components
    string facets = queryString[FACETS_PARAMETER_NAME];
    if(facets != null) {
        foreach(string facet in facets.Trim('[', ']').Split('|')) {
            string[] split = facet.Split('=');
            string fieldName = split[0];
            string fieldValue = split[1];
            Facets.Add(new FacetModel(){
                FieldName = fieldName,
                FieldValue = fieldValue
            });
        };
    }
    // Other components
}

Add the following code to serialize the model to query parameters:

LinqBasedPageExample_A1.aspx.cs

private IDictionary<string, string> ToQueryParamsDictionary() {
    // Other components
    if(Facets.Count > 0) {
        string facets = String.Join("|", Facets.Select(facet => facet.FieldName + "=" + facet.FieldValue));
        queryParamsDict.Add(FACETS_PARAMETER_NAME, "[" + facets + "]");
    }
    // Other components
    return queryParamsDict;
}

Sort

For the sorting component, you need two new properties:

LinqBasedPageExample_A1.aspx.cs

private const string IS_DESCENDING_PARAMETER_NAME = "descending";
private const string SORT_PARAMETER_NAME = "sort";

Add the following code to deserialize the query parameters to the model:

LinqBasedPageExample_A1.aspx.cs

public void FromQueryParams(NameValueCollection queryString) {
    // Other components
    string isDescending = queryString[IS_DESCENDING_PARAMETER_NAME];
    if(isDescending != null){
        SortDescending = isDescending != "false";
    }
    string sort = queryString[SORT_PARAMETER_NAME];
    if(sort != null){
        SortType = sort;
    }
    // Other components
}

Add the following code to serialize the model to query parameters:

LinqBasedPageExample_A1.aspx.cs

private IDictionary<string, string> ToQueryParamsDictionary() {
    // Other components
    if(!SortDescending){
        queryParamsDict.Add(IS_DESCENDING_PARAMETER_NAME, "false");
    }
    if(SortType != SortTypes.Relevancy){
        queryParamsDict.Add(SORT_PARAMETER_NAME, SortType);
    }
    // Other components
    return queryParamsDict;
}

Result

The result should look like this:

And your URL should end with:

?query=sitecore&nbResultsPerPage=6&facets=[fz95xtemplatename96887=Sample%20Item]&descending=false

Client-Side Code

LinqBasedPageExample_A1.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">
            <div class="searchSection">
                <asp:Literal Text="Query: " runat="server" />
                <asp:TextBox ID="Querybox" runat="server" />
            </div>
            <div class="pagerSection">
                <asp:Literal Text="Results per page: " runat="server" />
                <asp:TextBox ID="NbResultsPerPage" Text="20" type="number" runat="server" />
                <asp:Literal Text="Page: " runat="server" />
                <asp:DropDownList ID="PageNb" AppendDataBoundItems="true" AutoPostBack="true" runat="server" />
            </div>
            <div class="sortSection">
                <asp:DropDownList ID="SortList" AppendDataBoundItems="true" AutoPostBack="true" runat="server">
                    <asp:ListItem Text="Relevancy" Value="relevancy"></asp:ListItem>
                    <asp:ListItem Text="Date" Value="date"></asp:ListItem>
                </asp:DropDownList>
                <asp:Checkbox ID="SortDescending" Text="Descending" Checked="true" AutoPostBack="true" runat="server" />
            </div>
            <asp:Button type="submit" Text="Run Query" runat="server" />
        </div>
        <div ID="QueryErrorSection" class="queryErrorSection" Visible="False" runat="server">
            <asp:Label ID="QueryErrorLabel" runat="server" />
        </div>
        <div ID="ResultsSection" class="resultsSection" runat="server">
            <asp:GridView ID="LSTResults" AutoGenerateColumns="true" 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>
        </div>
        <div ID="NoResultsSection" class="noResultsSection" Visible="False" runat="server">
            <asp:Literal ID="NoResultsLabel" runat="server" />
            <div ID="DidYouMeanSection" Visible="False" runat="server">
                <asp:Repeater ID="DidYouMeanRepeater" runat="server">
                    <HeaderTemplate>Did you mean: </HeaderTemplate>
                    <SeparatorTemplate> or </SeparatorTemplate>
                    <ItemTemplate>
                        <asp:Button OnClientClick='ModifyQuery(this.value); return false;' Text='<%# Eval("CorrectedQuery") %>' runat="server" />
                    </ItemTemplate>
                    <FooterTemplate>?</FooterTemplate>
                </asp:Repeater>
            </div>
        </div>
        <div class="expressionSection">
            <asp:Literal Text="Current expression: " runat="server" />
            <asp:Literal ID="CurrentExpressionLabel" runat="server" />
        </div>
    </form>
</body>
</html>
<script type="text/javascript">
    function ModifyQuery(newValue){
        document.getElementById("Querybox").value = newValue;
        document.getElementById("pageForm").submit();
    }
</script>

Server-Side Code

LinqBasedPageExample_A1.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();
            searchModel.FromQueryParams(Request.QueryString);
            if (Page.IsPostBack) {
                MapControlsToModel(searchModel);
                Response.Redirect(GetUriWithParameters(searchModel));
            } else {
                Render(searchModel);
                MapModelToControls(searchModel);
            }
        }
        private void MapControlsToModel(SearchModel model) {
            model.Query = Querybox.Text;
            model.NbResultsPerPage = int.Parse(NbResultsPerPage.Text);
            if(!String.IsNullOrEmpty(PageNb.SelectedValue)) {
                model.Page = int.Parse(PageNb.SelectedValue);
            }
            model.SortDescending = SortDescending.Checked;
            model.SortType = SortList.SelectedValue;
            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;
        }
        private void MapModelToControls(SearchModel model) {
            Querybox.Text = model.Query;
            NbResultsPerPage.Text = model.NbResultsPerPage.ToString();
            PageNb.SelectedValue = model.Page.ToString();
            SortDescending.Checked = model.SortDescending;
            SortList.SelectedValue = model.SortType;
        }
        private void Render(SearchModel model) {
            if(!String.IsNullOrEmpty(model.Query)) {
                try{
                    QueryResults<SearchResult> results = ExecuteQuery(model);
                    if(results.TotalCount != 0) {
                        RenderWithResults(model, results);
                    } else {
                        RenderWithoutResults(model, results);
                    }
                    RenderCommon(model, results);
                }catch(Exception ex){
                    RenderException(model, ex);
                }
            }
        }
        private void RenderWithResults(SearchModel model,
                                       QueryResults<SearchResult> results) {
            LSTResults.DataSource = results.Results;
            LSTResults.DataBind();
            int numberOfPages = GetNumberOfPages(results.TotalCount, model.NbResultsPerPage);
            for(int i = 0; i < numberOfPages; i++) {
                PageNb.Items.Add(new ListItem(i.ToString(), i.ToString()));
            }
            if(results.GroupByResults.Count > 0) {
                FacetsRepeater.DataSource = results.GroupByResults;
                FacetsRepeater.DataBind();
            }
        }
        private int GetNumberOfPages(double totalCount, int nbResultsPerPage) {
            return (int)Math.Ceiling(totalCount / nbResultsPerPage);
        }
        private void RenderWithoutResults(SearchModel model,
                                          QueryResults<SearchResult> results) {
            NoResultsLabel.Text = "No results for " + model.Query;
            NoResultsSection.Visible = true;
            ResultsSection.Visible = false;
        }
        private void RenderCommon(SearchModel model,
                                  QueryResults<SearchResult> results){
            if(results.QueryCorrections.Count > 0) {
                DidYouMeanRepeater.DataSource = results.QueryCorrections;
                DidYouMeanRepeater.DataBind();
                DidYouMeanSection.Visible = true;
            }
            CurrentExpressionLabel.Text = results.Debug.BasicExpression;
        }
        private void RenderException(SearchModel model,
                                     Exception e){
            ResultsSection.Visible = false;
            QueryErrorSection.Visible = true;
            QueryErrorLabel.Text = "Exception thrown while querying: " + e.Message.ToString();
        }
        private string GetUri() {
            string url = HttpContext.Current.Request.Url.AbsoluteUri;
            return url.IndexOf("?") > -1 ? url.Substring(0, url.IndexOf("?")) : url;
        }
        private string GetUriWithParameters(SearchModel model) {
            return GetUri() + model.ToQueryParams();
        }
        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>()
                                                              .CoveoOr(model.Query)
                                                              .Skip(model.NbResultsPerPage * model.Page)
                                                              .Take(model.NbResultsPerPage)
                                                              .FacetOn(item => item.TemplateName)
                                                              .SortBy(model.SortType, model.SortDescending);
                  foreach(FacetModel facet in model.Facets){
                      query = query.CoveoWhere(facet.Expression);
                  }
                  results = query.GetCoveoQueryResults();
            }
            return results;
        }
    }
    public class SearchResult {
        public DateTime Updated { get; set; }
        public DateTime Date { get; set; }
        public string Title { get; set; }
        public string PrintableURI { get; set; }
        [IndexField("_templatename")]
        public string TemplateName { get; set; }
    }
    public class SearchModel {
        private const string QUERY_PARAMETER_NAME = "query";
        private const string NB_RESULTS_PER_PAGE_PARAMETER_NAME = "nbResultsPerPage";
        private const string PAGE_PARAMETER_NAME = "page";
        private const string FACETS_PARAMETER_NAME = "facets";
        private const string IS_DESCENDING_PARAMETER_NAME = "descending";
        private const string SORT_PARAMETER_NAME = "sort";
        public const int DEFAULT_NB_RESULTS_PER_PAGE = 20;
        public string Query { get; set; }
        public int NbResultsPerPage { get; set; }
        public int Page { get; set; }
        public IList<FacetModel> Facets { get; set; }
        public string SortType { get; set; }
        public bool SortDescending { get; set; }
        public SearchModel() {
            Query = "";
            NbResultsPerPage = DEFAULT_NB_RESULTS_PER_PAGE;
            Page = 0;
            Facets = new List<FacetModel>();
            SortType = SortTypes.Relevancy;
            SortDescending = true;
        }
        public void FromQueryParams(NameValueCollection queryString) {
            Query = queryString[QUERY_PARAMETER_NAME];
            string nbResultsPerPage = queryString[NB_RESULTS_PER_PAGE_PARAMETER_NAME];
            if(nbResultsPerPage != null) {
                NbResultsPerPage = int.Parse(nbResultsPerPage);
            }
            string page = queryString[PAGE_PARAMETER_NAME];
            if(page != null) {
                Page = int.Parse(page);
            }
            string facets = queryString[FACETS_PARAMETER_NAME];
            if(facets != null) {
                foreach(string facet in facets.Trim('[', ']').Split('|')) {
                    string[] split = facet.Split('=');
                    string fieldName = split[0];
                    string fieldValue = split[1];
                    Facets.Add(new FacetModel(){
                        FieldName = fieldName,
                        FieldValue = fieldValue
                    });
                };
            }
            string isDescending = queryString[IS_DESCENDING_PARAMETER_NAME];
            if(isDescending != null){
                SortDescending = isDescending != "false";
            }
            string sort = queryString[SORT_PARAMETER_NAME];
            if(sort != null){
                SortType = sort;
            }
        }
        public string ToQueryParams() {
            return SerializeQueryParamsDictionary(ToQueryParamsDictionary());
        }
        private IDictionary<string, string> ToQueryParamsDictionary() {
            IDictionary<string, string> queryParamsDict = new Dictionary<string, string>();
            if(!String.IsNullOrEmpty(Query)) {
                queryParamsDict.Add(QUERY_PARAMETER_NAME, Query);
            }
            if(NbResultsPerPage != DEFAULT_NB_RESULTS_PER_PAGE) {
                queryParamsDict.Add(NB_RESULTS_PER_PAGE_PARAMETER_NAME, NbResultsPerPage.ToString());
            }
            if(Page != 0) {
                queryParamsDict.Add(PAGE_PARAMETER_NAME, Page.ToString());
            }
            if(Facets.Count > 0) {
                string facets = String.Join("|", Facets.Select(facet => facet.FieldName + "=" + facet.FieldValue));
                queryParamsDict.Add(FACETS_PARAMETER_NAME, "[" + facets + "]");
            }
            if(!SortDescending){
                queryParamsDict.Add(IS_DESCENDING_PARAMETER_NAME, "false");
            }
            if(SortType != SortTypes.Relevancy){
                queryParamsDict.Add(SORT_PARAMETER_NAME, SortType);
            }
            return queryParamsDict;
        }
        private string SerializeQueryParamsDictionary(IDictionary<string, string> queryParamsDict) {
            if(queryParamsDict.Count > 0) {
                IList<string> paramsList = new List<string>();
                foreach (KeyValuePair<string, string> pair in queryParamsDict) {
                    paramsList.Add(pair.Key + "=" + pair.Value);
                }
                return "?" + string.Join("&", paramsList);
            } else {
                return "";
            }
        }
    }
    public class FacetModel {
        public string FieldName { get; set; }
        public string FieldValue { get; set; }
        public string Expression {
            get {
                return "@" + FieldName + "==\"" + FieldValue + "\"";
            }
        }
    }
    public static class SortTypes {
        public const string Date = "date";
        public const string Relevancy = "relevancy";
    }
    public static class QueryableSearchResultsExtensions {
        public static IQueryable<SearchResult> SortBy(this IQueryable<SearchResult> query,
                                                      string sortType,
                                                      bool isDescending) {
            switch(sortType){
                case SortTypes.Date:
                    return isDescending ? query.OrderByDescending(item => item.Updated)
                                        : query.OrderBy(item => item.Updated);
                case SortTypes.Relevancy:
                    return query;
                default:
                    throw new NotSupportedException("This sort type is not supported.");
            }
        }
    }
}