LINQ QueryResults Example: Putting it All Together
LINQ QueryResults Example: Putting it All Together
Coveo for Sitecore (July 2016) Coveo for Sitecore 4.1 (November 2018)
This section presents the final search page if you followed the complete tutorial and put all the pieces in the same file.
Result
Your page should look similar to this:
Client-Side Code
Your client-side code should now look like this:
LinqBasedPageExample_Full.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="LinqBasedPageExample_Full.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
Your server-side code should now look like this:
LinqBasedPageExample_Full.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 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 Render(SearchModel model) {
if(!String.IsNullOrEmpty(model.Query)) {
try{
ClearResults();
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 ClearResults() {
LSTResults.DataSource = "";
LSTResults.DataBind();
}
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();
}
NoResultsSection.Visible = false;
ResultsSection.Visible = true;
}
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 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 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.");
}
}
}
}
Going Further
While this tutorial has a full working example, it’s not very user friendly. Here are some suggestions to improve the solution and customize it to your liking.
Binding the Model to the URL Parameters
Binding the model to the URL parameters has many uses. Here are some benefits:
- Lets users save the search page in their Favorites/Bookmarks
- Handles well browser history
- Lets other parts of the website refer directly to a specific query
For a quick adaptation of the full example handling queries with URL parameters, see LINQ QueryResults Advanced Example - Bind the Model to URL Parameters.
Adding Styles
While the current example page isn’t stylized, it provides many classes that can be quickly customized.
Feel free to add your own to brand your search page for your company.
Adding Tabs
While this tutorial doesn’t cover tabs, it would be possible to show different results using tabs.
Making Queries Asynchronous
Using your favorite method, it would be possible to wrap the query section in an asynchronous call to the server.