Getting Started Using Vulcan Search in Episerver
Brad McDavid#CMS, #Episerver, #Code
We offer detailed instructions for how to set up Vulcan search in Episerver, which will index Episerver CMS content to Elasticsearch.
Vulcan is a relatively new (almost a year old now), open source search client built to index Episerver CMS content to Elasticsearch. It was created to fill a void in the default Episerver search offerings of Lucene and Episerver Find, which also utilizes Elasticsearch. Vulcan offers a modern search experience, similar to Find, but with the flexibility of choosing a hosting environment, such as an on-premises or cloud hosted service.
It’s fairly simple to get started with Vulcan; only a few basic components are required:
- Elasticsearch 2.x server (the latest 5.x is not currently supported)
- Episerver CMS web project
Let’s look at how to set up Vulcan using Episerver’s example Alloy site with no search configured and a local Elasticsearch server:
Setting Up a Local Elasticsearch
Elasticsearch is pretty easy to set up on Windows machine. You can do so by simply downloading a supported version in a zip file and extracting the files.
Once Java is installed and Elasticsearch files are extracted, execute the bin\elasticsearch.bat file, and if all is working correctly, you will see a command prompt similar to this:
The key message to pay attention to is the line which notes that the health status has changed from RED to YELLOW, which lets you know Elasticsearch is running. Don’t worry about the YELLOW status; it basically means that Elasticsearch is not running in a clustered environment, which isn’t needed for local development, but is highly recommended for production environments.
Adding Vulcan to Episerver
Now, with Elasticsearch up and running, we can install Vulcan to the Episerver CMS website project by following these steps:
- Open the project in Visual Studio.
- Right-click on the Episerver website project.
- Select Manage NuGet Packages.
- Select the Episerver Feed as the Package Source.
- If Episerver is not configured as a NuGet package source, simply click the gear icon and add it using the following:
- Name: Episerver
- Source: http://nuget.episerver.com/feed/packages.svc/
- Select Browse.
- If Episerver is not configured as a NuGet package source, simply click the gear icon and add it using the following:
- Enter “Vulcan” in the search box, and you should see results similar to the image below:
- Install the base package Vulcan.Core. It is also recommended to install TcbInternetSolutions.Vulcan.UI, since it has many nice benefits, such as displaying indexing modifiers, showing indexed item totals, and adding synonyms.
- Note: If the Forms NuGet package is required, you will need to set the Dependency Behavior to IgnoreDependencies in the install options to upgrade Newtonsoft.Json to version 9.0.1, which is required by later version of the NEST Nuget package, which the core Vulcan package requires.
- After the package(s) are installed, open the web.config and locate the AppSettings.
- Change the following lines from:
- <add key="VulcanUrl" value="SET THIS" />
- <add key="VulcanIndex" value="SET THIS" />
- To:
- <add key="VulcanUrl" value="http://localhost:9200" />
- <add key="VulcanIndex" value="vulcan.demo.local" />
- Tip: Append the environment name to the VulcanIndex, so each environment can use its own index. Values can be changed using config transforms to match the environment during deployments.
- Change the following lines from:
- At this point, the site can be compiled, and content can be indexed with Vulcan, but you will not yet be able to utilize the search results on the site.
Indexing CMS Content
Episerver CMS content gets indexed initially by an Episerver scheduled job. Also, the index stays up to date using Episerver content events for PublishedContent, MovedContent, DeletedContent, and DeletedContentLanguage. The screenshot below displays the Vulcan Index Content scheduled job to execute to populate the search index:
Creating a Vulcan Search Service
The default Alloy site provides a good demonstration of how to create a MVC search controller using a search service class with the default Lucene implementation. To make the search service work with Vulcan, we need to abstract the search service a little further using the following interfaces:
using EPiServer.Core;
using EPiServer.ServiceLocation;
using System.Collections.Generic;
using System.Web;
namespace VulcanDemo.Business
{
public interface ISearchService
{
bool IsActive { get; }
ISearchResults Search(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch, int currentPage, int maxResults);
}
public interface ISearchResults
{
IEnumerable Items { get; }
long TotalHits { get; }
string Version { get; }
}
[ServiceConfiguration(typeof(ISearchResults))]
public class CustomSearchResults : ISearchResults
{
public CustomSearchResults(IEnumerable items, long totalHits, string version)
{
Items = items;
TotalHits = totalHits;
Version = version;
}
public IEnumerable Items { get; }
public long TotalHits { get; }
public string Version { get; }
}
}
These abstractions will then be applied to the existing search service and a new VulcanSearchService class:
using EPiServer;
using EPiServer.Core;
using EPiServer.ServiceLocation;
using System.Collections.Generic;
using System.Globalization;
using System.Web;
using TcbInternetSolutions.Vulcan.Core;
using TcbInternetSolutions.Vulcan.Core.Extensions;
using TcbInternetSolutions.Vulcan.Core.Implementation;
namespace VulcanDemo.Business
{
[ServiceConfiguration(typeof(ISearchService))]
public class VulcanSearchService : ISearchService
{
IVulcanHandler _VulcanHandler;
IContentLoader _ContentLoader;
public bool IsActive => true;
public VulcanSearchService(IVulcanHandler vulcanHandler, IContentLoader contentLoader)
{
_VulcanHandler = vulcanHandler;
_ContentLoader = contentLoader;
}
public ISearchResults Search(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch, int currentPage, int maxResults)
{
// get a client using the given language
var client = _VulcanHandler.GetClient(new CultureInfo(languageBranch));
// uses GetSearchHits extension, source located at:
// https://github.com/TCB-Internet-Solutions/vulcan/blob/master/TcbInternetSolutions.Vulcan.Core/Extensions/IVulcanClientExtensions.cs
// otherwise a full client.Search could be called to customize further
var siteHits = client.GetSearchHits(searchText, currentPage, maxResults, searchRoots: searchRoots);
return new CustomSearchResults(ConvertToSearchResponseItem(siteHits.Items), siteHits.TotalHits, "Vulcan");
}
private IEnumerable ConvertToSearchResponseItem(List items)
{
foreach(var item in items)
{
yield return _ContentLoader.Get(new ContentReference(item.Id.ToString()));
}
}
}
}
For brevity, the source of the Lucene based search service can be found on GitHub.
The final action necessary to display Vulcan search results is to add the ISearchService to the MVC search controller using dependency injection:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EPiServer.Core;
using EPiServer.Framework.Web;
using VulcanDemo.Business;
using VulcanDemo.Models.Pages;
using VulcanDemo.Models.ViewModels;
using EPiServer.Web;
using EPiServer.Web.Routing;
namespace VulcanDemo.Controllers
{
public class SearchPageController : PageControllerBase
{
private const int MaxResults = 40;
private readonly ISearchService _searchService;
private readonly UrlResolver _urlResolver;
private readonly TemplateResolver _templateResolver;
public SearchPageController(
ISearchService searchService,
TemplateResolver templateResolver,
UrlResolver urlResolver)
{
_searchService = searchService;
_templateResolver = templateResolver;
_urlResolver = urlResolver;
}
[ValidateInput(false)]
public ViewResult Index(SearchPage currentPage, string q, int pageNumber = 1)
{
var model = new SearchContentModel(currentPage)
{
SearchServiceDisabled = !_searchService.IsActive,
SearchedQuery = q
};
if(!string.IsNullOrWhiteSpace(q) && _searchService.IsActive)
{
var hits = Search(q.Trim(),
new[] { SiteDefinition.Current.StartPage, SiteDefinition.Current.GlobalAssetsRoot, SiteDefinition.Current.SiteAssetsRoot },
ControllerContext.HttpContext,
currentPage.LanguageID,
pageNumber).ToList();
model.Hits = hits;
model.NumberOfHits = hits.Count();
}
return View(model);
}
///
/// Performs a search for pages and media and maps each result to the view model class SearchHit.
///
///
/// The search functionality is handled by the injected SearchService in order to keep the controller simple.
/// Uses EPiServer Search. For more advanced search functionality such as keyword highlighting,
/// facets and search statistics consider using EPiServer Find.
///
private IEnumerable Search(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch, int pageNumber)
{
var searchResults = _searchService.Search(searchText, searchRoots, context, languageBranch, pageNumber, MaxResults);
return searchResults.Items.SelectMany(CreateHitModel);
}
private IEnumerable CreateHitModel(IContent content)
{
if (content != null && HasTemplate(content) && IsPublished(content as IVersionable))
{
yield return CreatePageHit(content);
}
}
private bool HasTemplate(IContent content)
{
return _templateResolver.HasTemplate(content, TemplateTypeCategories.Page);
}
private bool IsPublished(IVersionable content)
{
if (content == null)
return true;
return content.Status.HasFlag(VersionStatus.Published);
}
private SearchContentModel.SearchHit CreatePageHit(IContent content)
{
return new SearchContentModel.SearchHit
{
Title = content.Name,
Url = _urlResolver.GetUrl(content.ContentLink),
Excerpt = content is SitePageData ? ((SitePageData) content).TeaserText : string.Empty
};
}
}
}
Accessing Vulcan in the Episerver UI
If the NuGet package TcbInternetSolutions.Vulcan.UI is installed, we can view some very helpful information in the Episerver UI. Selecting the Vulcan menu item displays information about:
- Each index (one will exist for each language enabled on the site, plus one invariant language for uploaded assets).
- Index modifiers – any class that implements the IVulcanIndexingModifier
- POCO Indexers – any class that implements the IVulcanPocoIndexer interface to index custom POCO (plain old CLR objects), that are not Episerver CMS content.
Display the Results
Since we abstracted the search service and didn’t change the search results view model, we didn’t have to change the result page code. The screenshot below displays what a search would look like if any ISearchService is used:
Moving to Production
When we are ready to move the Vulcan search to production, we need to consider hosting options beyond just a local Elasticsearch instance. The simplest and easiest solution is to use cloud based hosting, which is beyond the scope of this article, but will be the fastest way to implement Vulcan, since we only need the VulcanUrl and VulcanIndex web.config app settings. Another possible solution is to host on-premises behind a firewall, with a cluster of Linux servers, such as Red Hat or Ubuntu. The important point here is that there are many options that can work for any setup.
We hope this look at how to implement Vulcan search on your Episerver website has been helpful, but if you have any questions, please feel free to contact us. Do you have any tips of your own for getting the most out of Episerver? Please share them in the comments below!
Related Posts
Custom Fields and ElasticSearch
Diagram's Ryan Duffing offers a tutorial on indexing and retrieving custom fields with Epinova.Elasticsearch for Optimizely (formerly Episerver).
The Episerver Optimizely Rebrand: What It Means For Your Business
Episerver has officially announced its rebrand to Optimizely. Here’s why—and how your business can benefit.
Results Matter.
We design creative digital solutions that grow your business, strengthen your brand and engage your audience. Our team blends creativity with insights, analytics and technology to deliver beauty, function, accessibility and most of all, ROI. Do you have a project you want to discuss?
Like what you read?
Subscribe to our blog "Diagram Views" for the latest trends in web design, inbound marketing and mobile strategy.