Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more

Henrik Fransas
May 20, 2015
  2265
(0 votes)

Adding EPiServer Find to Alloy - Part 3

In this blog series I am going to show you step by step how you can add EPiServer Find to a project and also how to use it and the most functions in it.

Part 3 - Implementing a simple search page

In this post we are going to implement a simple search page, or actually we are going to change the one in Alloy to use EPiServer Find, otherwise it will work the same way as the ordinary one.

Creating the search service with interface

First we need to create a SearchService that works against EPiServer Find and since Alloy already have a class with that name that we will not use any more, we change it to work with EPiServer Find instead, we also going to introduce a Interface to it, to make it possible to do unittesting on it if we want, and also to make it easier to use with StructureMap.

So, first locate the file SearchService.cs below the Business-folder. Start by removing all the content in it. It should now look like this:

namespace AlloyWithFind.Business
{
    public class SearchService
    {
        
    }
}

We will work with IOC and StructureMap so instead of calling the client by writing for example like this: SearchClient.Instance.Search we will create a local instance of Iclient and then a constructor that will take an IClient as a parameter. It will look like this:

private readonly IClient _client;

public SearchService(IClient iClient)
{
    _client = iClient;
}

By doing this StructureMap will automatically see that the class needs an instance of IClient and create it for us. After that we mimic the old Search function from Alloy but we only keep searchText and maxResults as parameters since we do not need the others.

Since we are mimicking the old function that search in all content we will do a UnifiedSearch with the extension method For in Find and we will return the same thing as the old function did. In that way we do not need to change the view so much. The UnifiedSearch will search in all content and types and it will also return a summary for each hit so we have all the information we need to create an instance of SearchHit.

We will end up with a function that looks like this:

public IEnumerable<SearchContentModel.SearchHit> Search(string searchText, int maxResults)
{
    var searchResults = _client.UnifiedSearchFor(searchText).Take(maxResults).GetResult();

    return searchResults.Hits.Select(hit => new SearchContentModel.SearchHit()
    {
        Title = hit.Document.Title,
        Url = hit.Document.Url,
        Excerpt = hit.Document.Excerpt,
    });
}

As you can see we return a list of searchit instead of a SearchResult and this is to make it as simple as possible. We will need to change this later on when adding facets and so on.

Alloy does not today work with an interface for SearchService but we want to do that so we can use StructureMap to resolve it for us. Therefore we not create a interface that looks like this:

using System.Collections.Generic;
using AlloyWithFind.Models.ViewModels;

namespace AlloyWithFind.Business
{
    public interface ISearchService
    {
        IEnumerable<SearchContentModel.SearchHit> Search(string searchText, int maxResults);
    }
}

Right now we only have one function in it, but we will extend it with more functions later on in this blog series. Now we need to implement it with our SearchService and we will also update the service so it only contains the functions we need. That will result in a SearchService that looks like this:

using System.Collections.Generic;
using System.Linq;
using AlloyWithFind.Models.ViewModels;
using EPiServer.Find;

namespace AlloyWithFind.Business
{
    public class SearchService : ISearchService
    {
        private readonly IClient _client;

        public SearchService(IClient client)
        {
            _client = client;
        }

        public IEnumerable<SearchContentModel.SearchHit> Search(string searchText, int maxResults)
        {
            var searchResults = _client.UnifiedSearchFor(searchText).Take(maxResults).GetResult();

            return searchResults.Hits.Select(hit => new SearchContentModel.SearchHit()
            {
                Title = hit.Document.Title,
                Url = hit.Document.Url,
                Excerpt = hit.Document.Excerpt,
            });
        }
    }
}

As you can see it is very simple right now, but we will complete it later on.

Implement the updated SearchService on search page

Now it is time to update the SearchPageController to work with the new SearchService. Out of the box in Alloy the SearchPageController is quite complex to be able to search in both content and media so first we need to strip it down a lot. Start by remove all but the constructor and the Index action. Then we will change the constructor so it only uses the new SearchService interface and last we change the index action so it does not use the search function we just removed but instead it use the Search function in the SearchService directly. So we end up with a SearchPageController as simple as this one:

using System.Linq;
using System.Web.Mvc;
using AlloyWithFind.Business;
using AlloyWithFind.Models.Pages;
using AlloyWithFind.Models.ViewModels;

namespace AlloyWithFind.Controllers
{
    public class SearchPageController : PageControllerBase<SearchPage>
    {
        private const int MaxResults = 40;
        private readonly ISearchService _searchService;

        public SearchPageController(ISearchService searchService) 
        {
            _searchService = searchService;
        }

        [ValidateInput(false)]
        public ViewResult Index(SearchPage currentPage, string q)
        {
            var model = new SearchContentModel(currentPage)
                {
                    SearchServiceDisabled = false,
                    SearchedQuery = q
                };

            if(!string.IsNullOrWhiteSpace(q))
            {
                var hits = _searchService.Search(q.Trim(), MaxResults).ToList();
                model.Hits = hits;
                model.NumberOfHits = hits.Count();
            }

            return View(model);
        }
    }
}

Since we are not changing what the index action return it should just be to run the site and it will just work.
But, that is not the case. If you build and run and do a search you will get this error:

No parameterless constructor defined for this object.

The reason you get this error is because EPiServer does not know how to resolve the controller with the ISearchService parameter to it. We need to tell StructorMap how it should handle it and we do that by opening the file DependencyResolverInitialization.cs (Below Business/Initialization) and add these lines inside the function ConfigureContainer

container.Scan(c =>
{
    c.AssemblyContainingType<ISearchService>();
    c.WithDefaultConventions();
});

After we do that, build and run the site, the search page should work again and give back pretty much the same response as it did with EPiServer Search. So now we are back to where we began, let’s start to do some more fun! In the next part we will extend the search page with facets.

May 20, 2015

Comments

Please login to comment.
Latest blogs
Optimizely Configured Commerce and Spire CMS - Figuring out Handlers

I recently entered the world of Optimizely Configured Commerce and Spire CMS. Intriguing, interesting and challenging at the same time, especially...

Ritu Madan | Mar 12, 2025

Another console app for calling the Optimizely CMS REST API

Introducing a Spectre.Console.Cli app for exploring an Optimizely SaaS CMS instance and to source code control definitions.

Johan Kronberg | Mar 11, 2025 |

Extending UrlResolver to Generate Lowercase Links in Optimizely CMS 12

When working with Optimizely CMS 12, URL consistency is crucial for SEO and usability. By default, Optimizely does not enforce lowercase URLs, whic...

Santiago Morla | Mar 7, 2025 |

Optimizing Experiences with Optimizely: Custom Audience Criteria for Mobile Visitors

In today’s mobile-first world, delivering personalized experiences to visitors using mobile devices is crucial for maximizing engagement and...

Nenad Nicevski | Mar 5, 2025 |

Unable to view Optimizely Forms submissions when some values are too long

I discovered a form where the form submissions could not be viewed in the Optimizely UI, only downloaded. Learn how to fix the issue.

Tomas Hensrud Gulla | Mar 4, 2025 |

CMS 12 DXP Migrations - Time Zones

When it comes to migrating a project from CMS 11 and .NET Framework on the DXP to CMS 12 and .NET Core one thing you need to be aware of is the...

Scott Reed | Mar 4, 2025