Jonas Bergqvist
Feb 7, 2013
  7968
(3 votes)

Simple search page for Find in Alloy MVC

When you think about a search product, you probably start’s to think about the free text search directly. For EPiServer Find, that’s shame, even if it’s understandable.

I don’t see EPiServer Find just as a standard search product, I see it more like a very powerful API together with a document database. When you need to receive collection of data, I think you should get the data from the document database whenever you can, instead of the SQL server.

With this said, I’m going to show you how easy it is to create a standard free text search, together with a simple facet, in Alloy MVC.

Model

First of all, I’m going to create a new model, because the PageViewModel<SearchPage> is not enough. We need a search result collection in the model. We could have used the SearchPageModel in an even easier example, or used it as the base class for the new model, but I will not do that in this example. The argument for not inheriting from SearchPageModel is that we now can use more properties in our view, even if I’m not going to do it in this example.

public class FindSearchPageViewModel : PageViewModel<SearchPage>    
{        
    public FindSearchPageViewModel(SearchPage currentPage, string searchQuery)
            : base(currentPage)
    {
            
        SearchQuery = searchQuery;
    }        
 
    public string SearchQuery { get; private set; }        
    public UnifiedSearchResults Results { get; set; }    
}
 

The properties needed are a string with the searched query, and the result. I will use UnifiedSearch in EPiServer Find, and I will therefore have the UnifiedSearchResults as the type for the Results property.

Controller

In the controller, we will create a new instance of the model, search against UnifiedSearch, if the search query isn’t empty, and then we will set the result property.

[TemplateDescriptor(Default = true)]
public class FindSearchPageController : PageController<SearchPage>
{
    public ActionResult Index(SearchPage currentPage, string searchQuery)
    {
        var model = new FindSearchPageViewModel(currentPage, searchQuery);
        if (String.IsNullOrEmpty(searchQuery))
        {
            return View(model);
        }
 
        var unifiedSearch = SearchClient.Instance.UnifiedSearchFor(searchQuery);
        model.Results = unifiedSearch.ApplyBestBets().Track().GetResult();
        return View(model);
    }
}

When searching against unified search, I’m also including “apply best bets”, and “track” in the search.

View

The model should of course use the model created earlier.

What we should do first is creating a textbox, and a button, and make sure we can send necessary data back to the index method in the controller.

After the textbox and button, we should write the result, if it exists with link to the page representing each result item.

@model EPiServer.Templates.Alloy.Models.ViewModels.FindSearchPageViewModel 
@{    ViewBag.Title = "Index";} 
 
@using (Html.BeginForm(ViewContext.RouteData.Values))
{     
    @Html.TextBox("searchQuery", Model.SearchQuery)    
    <input type="button" value="search" />
} 
 
@if (Model.Results != null){     
    <ul>        
        @foreach (var item in Model.Results)        
        {
             <li>
                <h4><a href="@item.Url">@item.Title</a></h4>
                @item.Excerpt
            </li>
        }    
    </ul>
}

We use the Html-helper “BeginForm” and we send the routed values back to the controller, together with the search query from the textbox.

From the result property, we use the Url, Title, and Excerpt properties to show the result.

Section facet

To make it a little bit more fun, I’m going to add a facet to the search.

Model

The model needs to contain a collection of facet results. I’m going to create a class containing a link text, and route values. The route values will be used when filtering the results.

public class FindSearchPageViewModel : PageViewModel<SearchPage>
{
    public FindSearchPageViewModel(SearchPage currentPage, string searchQuery)
        : base(currentPage)
    {
        SearchQuery = searchQuery;
    }
    public string SearchQuery { get; private set; }
    public UnifiedSearchResults Results { get; set; }
    public IEnumerable<FindSearchSectionModel> Sections { get; set; }
}
 
public class FindSearchSectionModel
{
    public string LinkText { get; set; }
    public RouteValueDictionary Values { get; set; }
}

Controller

In the controller, I will add a terms facet on the unified search. I will use the “SearchSection” property for the facet. To get the facet result back, I will use the same syntax on the result (TermsFacetFor(x => x.SearchSection). This will retrieve the facet result for the “SearchSection”.

When setting the “Sections” property on the model, we need to create a section model for every result in the facet result. I will set the link text as the facet term together  with the number of hits. The values property will use the routed data, but it will set a new value (sections) to the facet term. Now we can filter result by adding a new parameter to the method called “sections”.

The last thing I need to do is adding a filter when the sections parameter has been set. I will use “FilterHits” to only filter the result, not the facets.

[TemplateDescriptor(Default = true)]    
public class FindSearchPageController : PageController<SearchPage>    
{        
    public ActionResult Index(SearchPage currentPage, string searchQuery, string sections)
    {       
        var model = new FindSearchPageViewModel(currentPage, searchQuery);
        if (String.IsNullOrEmpty(searchQuery))            
        {                
            return View(model);            
        }             
 
        var unifiedSearch = SearchClient.Instance.UnifiedSearchFor(searchQuery)                
            .TermsFacetFor(x => x.SearchSection);             
 
        if (!String.IsNullOrEmpty(sections))            
        {                
            unifiedSearch = unifiedSearch                              
                .FilterHits(x => x.SearchSection.Match(sections));           
         }             
        model.Results = unifiedSearch.ApplyBestBets().Track().GetResult();
        model.Sections = model.Results.TermsFacetFor(x => x.SearchSection)    
            .Select(x => new FindSearchSectionModel()                
        {                    
            LinkText = String.Format(CultureInfo.InvariantCulture,
                "{0} ({1})", x.Term, x.Count),
            Values = new RouteValueDictionary(RouteData.Values)                     
            {                         
                { "searchQuery", searchQuery },
                { "sections", x.Term }                    
             }                
        });             
        return View(model);       
    }    
}
 

View

In the view, I will check if the sections property has been set. If it contains values, I will use the Html-helper “RouteLink” to create a link for each result.

@model EPiServer.Templates.Alloy.Models.ViewModels.FindSearchPageViewModel 
@{    ViewBag.Title = "Index";} 
 
@using (Html.BeginForm(ViewContext.RouteData.Values)){     
    @Html.TextBox("searchQuery", Model.SearchQuery)    
    <input type="button" value="search" />
} @if (Model.Sections != null){
     <ul>
        @foreach (var item in Model.Sections)
        {
             <li>
                @Html.RouteLink(item.LinkText, item.Values)
            </li>
        }
    </ul>
}
 
@if (Model.Results != null)
{
     <ul>
        @foreach (var item in Model.Results)
        {
             <li>
                <h4><a href="@item.Url">@item.Title</a></h4>
                @item.Excerpt
            </li>
        }
    </ul>
}

Using statements

If you are going to try this out, please include the following namespaces in your controller:
using EPiServer.Find;
using EPiServer.Find.Cms;

using EPiServer.Find.Framework;
using EPiServer.Find.Framework.Statistics;

More to read

For more information about Unified search, please read: Joels blog about Unified Search

For more information about Alloy MVC, see this article

Feb 07, 2013

Comments

Jonathan Roberts
Jonathan Roberts Mar 7, 2013 11:05 AM

Hi,
I am searching our site using UnifiedSearchResults and I am indexing Composer controls to search for content added via these controls. When using the UnifiedSearchResults the results are sometimes duplicated and the URL for the duplicated result takes me to the Control. Is there a way to filter the results or stop it from bringing back duplicates?
Thanks

Please login to comment.
Latest blogs
Content Delivery API – The Case of the Duplicate API Refresh Token

Creating a custom refresh provider to resolve the issues with duplicate tokens in the DXC The post Content Delivery API – The Case of the Duplicate...

David Lewis | Sep 29, 2022 | Syndicated blog

New Optimizely certifications - register for beta testing before November 1st

In January 2023, Optimizely is making updates to the current versions of our certification exams to make sure that each exam covers the necessary...

Jamilia Buzurukova | Sep 28, 2022

Optimizely community meetup - Sept 29 (virtual + Melbourne)

Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP's see the...

Ynze | Sep 27, 2022 | Syndicated blog

Preview multiple Visitor Groups directly while browsing your Optimizely site

Visitor groups are great - it's an easy way to add personalization towards market segments to your site. But it does come with it's own set of...

Allan Thraen | Sep 26, 2022 | Syndicated blog

The Report Center is finally back in Optimizely CMS 12

With Episerver.CMS.UI 12.12.0 the Report Center is finally re-introduced in the core product.

Tomas Hensrud Gulla | Sep 26, 2022 | Syndicated blog

Dynamic Route in ASP.NET Core When MapDynamicControllerRoute Does Not Work

Background Creating one of the add-on for Optimizely I had to deal with challenge to register dynamically route for the API controller. Dynamic rou...

valdis | Sep 25, 2022 | Syndicated blog