Henrik Fransas
May 20, 2015
  5734
(3 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

valdis
valdis May 20, 2015 09:08 PM

Would be great if you could take a time and review is for instance SearchSection override working for unified search. I can't get it working somehow, even in Find v9.x

Henrik Fransas
Henrik Fransas May 20, 2015 09:12 PM

I can do that Valdis

Could you give me a code example on what you are trying to do?

May 20, 2015 10:01 PM

I had to override SearchSection since there was a bug in the default implementation. Reported this as a bug. But it seems to work now, when I removed our custom implementation.

valdis
valdis May 24, 2015 10:19 PM

I need a custom SearchSection for each unified search item.

Tried as class property for indexed type:

public virtual string SearchSection {get {...} }



Seems like not working. Tried to exlude and include the field:

SearchClient.Instance.Conventions.ForInstancesOf()
                    .ExcludeField(x => x.SearchSection())
                    .IncludeField(x => x.SearchSection());
public static string SearchSection(this ArticlePage content)
{
    return ...
}



Seems like not helpig either...

Please login to comment.
Latest blogs
A First Look at Optimizely Remote MCP Server for Experimentation

Optimizely just released a Remote MCP Server for Experimentation and I've been trying it out to see what it can do. If you don't know, MCP (Model...

Jacob Pretorius | May 1, 2026

Promoted and Certified

What a busy week

Andy Blyth | May 1, 2026 |

Announcing new library: SettingsManager

When you run .net app, there have been a few ways to store settings. Those can be set via appSettings.json, or via Azure Portal AppService...

Quan Mai | Apr 30, 2026

From Prompting to Production: Optimizely Opal University Cohort and the Future of Agentic MarTech

Most organizations today are still playing with AI. They experiment with prompts, test ideas in isolated chats, and occasionally automate a task or...

Augusto Davalos | Apr 28, 2026

Six Compelling Reasons for Upgrading to CMS 13

Most software updates ask you to keep up. Optimizely CMS 13 asks something different — it asks whether your digital strategy is built for a world...

Muhammad Talha | Apr 28, 2026

Optimizely CMS 13 breaking changes: GetContentTypePropertyDisplayName

When upgrading from CMS 12 to 13, resolving property display names may not work as before. Here’s what changed.

Tomas Hensrud Gulla | Apr 27, 2026 |