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.
- Installing EPiServer Find
- Filtering what to index
- Implementing a simple search page
- Extend search page with facets
- Extend search page with simple and advanced autocomplete
- Custom search and click tracking
- Making use of Boosting, Best Bets, Synonyms and Related Queries
- Extend the search service with caching layer and be prepared for Find out of order
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.
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
I can do that Valdis
Could you give me a code example on what you are trying to do?
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.
I need a custom SearchSection for each unified search item.
Tried as class property for indexed type:
Seems like not working. Tried to exlude and include the field:
Seems like not helpig either...