Stay up to date with weekly industry insights

the latest trends in web design, inbound marketing, mobile strategy and more...

Utilizing Vulcan for Listing Pages in Episerver

Listing pages are a pretty common type that you find in most Episerver applications. Examples would be news article or event listing pages. A common feature on all listing pages is pagination, or, the ability to break the data into digestible chunks to navigate through. When developing a listing page, performance is a big thing to consider. If you are dealing with large amounts of data, you want to be as efficient as possible when listing it.

I was working on an Episerver build recently that was using Vulcan as the search client. One of the page types that I had to build was a news article listing. Since I was already using Vulcan for the search functionality, I decided to also use it for my listing pages. It ended up being a really nice solution that was fast and worked well for pagination of large data sets.

There isn't a ton of documentation on how to use Vulcan like this, so, I wanted to share a bit of code. This article assumes that you are familiar with Episerver and know how to install the Vulcan search client.

First, I created two interfaces, one for each article item and one for the article item list. 

using EPiServer.Core;

namespace WSOL.Business.Interfaces
{
    public interface IArticleListItem
    {
        ContentReference Image { get; set; }
        DateTime Date { get; set; }
        string Title { get; set; }
        string Teaser { get; set; }
        ContentReference Link { get; set; }
    }

    public interface IArticleList
    {
        IEnumerable<IArticleListItem> Items { get; set; }
        long Total { get; set; }
    }
}

Next, I built the service that gets the paginated data and returns it to the controller. Something to point out here is that this is essentially the same thing you would do to build a keyword type search with Vulcan. Since there is no keyword to search, we pass an empty string to our query so that it returns everything that is of type ArticleDetailPageData.

The caveat to that and what makes this whole thing so good is that you can pass Vulcan .Skip and .Take, which works like the LINQ extensions of the same name, except that you don't have to get all the data first, then sort. All you have to do in the code is tell Vulcan what page you are on and how many items per page and it returns only what you need, already sorted the way you tell it to. This example service has a very simple sort descriptor that returns the articles sorted by descending release date. 

namespace WSOL.Business.Services
{
    [EPiServer.ServiceLocation.ServiceConfiguration(typeof(IArticleService))]
    public class ArticleService : IArticleService
    {
        private readonly IArticleList _articleList;        
        private readonly IContentLoader _contentLoader;
        private readonly IVulcanHandler _vulcanHandler;

        public ArticleService(IArticleList articleList, IContentLoader contentLoader, IVulcanHandler vulcanHandler)
        {
            _articleList = articleList;
            _contentLoader = contentLoader;
            _vulcanHandler = vulcanHandler;            
        }

        public IArticleList GetArticles(int pageSize, int pageNumber, ContentReference scope)
        {
            var client = _vulcanHandler.GetClient();
            var typesToSearchFor = typeof(ArticleDetailPageData).GetSearchTypesFor();
            var searchScope = new ContentReference[] { scope };

            SortDescriptor<ArticleDetailPageData> sort = new SortDescriptor<ArticleDetailPageData>().Descending(f => f.ReleaseDate);

            QueryContainer query = new QueryContainerDescriptor<ArticleDetailPageData>().FilterForPublished<IContent>();

            var articleHits = client.SearchContent<ArticleDetailPageData>(d => d
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .Fields(fs => fs.Field(p => p.ContentLink).Field(p => p.Name)) // only return contentLink
                .Query(q => query)
                .Sort(s => sort),
                principleReadFilter: PrincipalInfo.Current.Principal,
                typeFilter: typesToSearchFor.Distinct(),
                rootReferences: searchScope
            );
                       
            _articleList.Total = articleHits.Total;
            _articleList.Items = articleHits.Hits.SelectMany(CreateArticleModelFromVulcan).ToList();

            return _articleList;
        }

        private IEnumerable<IArticleListItem> CreateArticleModelFromVulcan(IHit<IContent> responseItem)
        {
            ContentReference contentReference = null;

            if (ContentReference.TryParse(responseItem.Id, out contentReference))
            {
                var article = _contentLoader.Get<ArticleDetailPageData>(contentReference);

                var articleItem = ServiceLocator.Current.GetInstance<IArticleListItem>();

                articleItem.Date = article.ReleaseDate;
                articleItem.Image = article?.FeaturedImage ?? SiteHelper.SiteSettings.FallbackArticleImage;
                articleItem.Link = article.ContentLink;
                articleItem.Teaser = article?.Teaser ?? string.Empty;
                articleItem.Title = article?.Title ?? article.Name;

                yield return articleItem;               
            }                                          
        }
    }
}

Now this service can be injected into any controller that needs to list articles.

Utilizing Vulcan for the article listing page allowed me to quickly build a simple, flexible way to return sets of paginated Episerver content. If you're interested in getting started using Vulcan Search in Episerver, check out Brad McDavid's blogWant to know how we can use approaches like this to make your website more efficient? Leave a comment below.

About the Author

John McKillip
John McKillip
As one of WSOL's Web Developers, John builds enterprise-level web solutions that are fast and scalable for a variety of clients and industries. He is passionate about all things computer science related, especially building web applications with .NET MVC. He is also very interested in user-centered, responsive design and keeping up with the latest front-end development techniques.