Fortis & Sitecore Tutorial 3 – using Sitecore ContentSearch API

Sitecore ContentSearch API is a rather powerful functionality that gives you pretty much unlimited capabilities in retrieving sets of items in your Sitecore installation using Lucene or SOLR search engines which Sitecore currently supports.
Using Fortis framework in your Sitecore solution gives you auto-generated model (using TDS or any other way of to generate it) and would be logical if we can query the search index using typed ItemWrapper based models.
Yes, Fortis supports these features out of the box and this topic is about how to use them.
All code mentioned in this post can be found on GitHub here.

Simple query example

Let’s imagine I have a product repository as a Sitecore Bucket item where I store my product items.

products-bucket

And I want to create a rendering which will list them all on the page.

fortis-demo-product-list

So I implement a ProductService class with a method for retrieving my product items

using System;
using System.Collections.Generic;
using System.Linq;
using Fortis.Search;
using FortisDemo.Model.Templates.UserDefined;
using Sitecore;
using Sitecore.ContentSearch;

namespace FortisDemo.Products
{
	public class ProductService : IProductService
	{
		protected readonly IItemSearchFactory ItemSearchFactory;

		public ProductService(IItemSearchFactory itemSearchFactory)
		{
			this.ItemSearchFactory = itemSearchFactory;
		}

		public IEnumerable<IProductPageItem> GetAlProducts()
		{
			var productRepositoryID = new Guid("{C1F3F0A1-145D-44A8-B2A3-2F395F10A653}");
			using (var searchContext = ContentSearchManager.CreateSearchContext((SitecoreIndexableItem)Context.Item))
			{
				var queryable = searchContext.GetQueryable<IProductPageItem>();

				return this.ItemSearchFactory.FilteredSearch(queryable)
					.Where(x => x.Paths.Contains(productRepositoryID))
					.ToList();
			}
		}
	}
}

Yes, as simple as that!
As we can see, the method returns us typed items wrapped into the IProductPageItem. And these item wrappers still have an instance of Sitecore.Data.Items.Item inside of them and when using these objects in the rendering we can access actual values from the Sitecore (and not from the index).

And this method is being called in my ProductController to fill the product list property of my view model:

using System.Web.Mvc;
using Fortis.Model;
using FortisDemo.Model.Templates.UserDefined;
using FortisDemo.Products;
using FortisDemo.Web.Mvc.Controllers;
using FortisDemo.Website.Areas.Product.Models;

namespace FortisDemo.Website.Areas.Product.Controllers
{
	public class ProductController : WebsiteController
	{
		protected readonly IProductService ProductService;

		public ProductController(IItemFactory itemFactory, IProductService productService) 
			: base(itemFactory)
		{
			this.ProductService = productService;
		}

		public ActionResult ProductList()
		{
			var renderingModel = new ProductListRenderingModel(this.ItemFactory.GetRenderingContextItems<IContentPageItem, IContentPageItem>())
			{
				Products = this.ProductService.GetAlProducts()
			};

			return View(renderingModel);
		}
	}
}

Installation & Configuration

In order to be able to use that you will need to install the respective nuget package to your website project depending on which search provider your Sitecore instance uses.

And that will be either


Install-Package Fortis.Search.Lucene

or


Install-Package Fortis.Search.Solr

These packages will install required Fortis dlls and a configuration files.
For Lucene there will be

  • Fortis.Search.Lucene.dll
  • /App_Config/Include/Fortis/Fortis.Search.Lucene.config

and for SOLR

  • Fortis.Search.Solr.dll
  • /App_Config/Include/Fortis/Fortis.Search.Solr.config

Config files will include configuration for index document property mapper so Sitecore can map indexed items to the Fortis ItemWrapper classes. Also there is a declaration of several computed search fields.

Fortis.Search.Lucene.config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<contentSearch>
			<indexConfigurations>
				<defaultLuceneIndexConfiguration>
					<indexDocumentPropertyMapper>
						<patch:attribute name="type">Fortis.Search.LuceneDocumentTypeMapper, Fortis.Search.Lucene</patch:attribute>
						<objectFactory>
							<patch:attribute name="type">Fortis.Search.DefaultDocumentMapperObjectFactory, Fortis</patch:attribute>
						</objectFactory>
					</indexDocumentPropertyMapper>
					<fields hint="raw:AddComputedIndexField">
						<field fieldName="_isstandardvalues">Fortis.Search.ComputedFields.IsStandardValues, Fortis</field>
						<field fieldName="_templates">Fortis.Search.ComputedFields.InheritedTemplates, Fortis</field>
					</fields>
				</defaultLuceneIndexConfiguration>
			</indexConfigurations>
		</contentSearch>
	</sitecore>
</configuration>

Fortis.Search.Solr.config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<contentSearch>
			<indexConfigurations>
				<defaultSolrIndexConfiguration>
					<indexDocumentPropertyMapper>
						<patch:attribute name="type">Fortis.Search.SolrDocumentTypeMapper, Fortis.Search.Solr</patch:attribute>
						<objectFactory>
							<patch:attribute name="type">Fortis.Search.DefaultDocumentMapperObjectFactory, Fortis</patch:attribute>
						</objectFactory>
					</indexDocumentPropertyMapper>
					<fields hint="raw:AddComputedIndexField">
						<field fieldName="_isstandardvalues" returnType="bool">Fortis.Search.ComputedFields.IsStandardValues, Fortis</field>
						<field fieldName="_templates" returnType="string">Fortis.Search.ComputedFields.InheritedTemplates, Fortis</field>
					</fields>
				</defaultSolrIndexConfiguration>
			</indexConfigurations>
		</contentSearch>
	</sitecore>
</configuration>

How it works

So there is no magic really and Fortis uses default Sitecore attributes to map the properties to the indexed fields in the auto-generated model classes:
For example for the ContentTitle field we will see two properties:


[IndexField(IndexFieldNames.ContentTitle)]
public virtual ITextFieldWrapper ContentTitle
{
	get { return GetField<TextFieldWrapper>(FieldNames.ContentTitle, IndexFieldNames.ContentTitle); }
}

[IndexField(IndexFieldNames.ContentTitle)]
public string ContentTitleValue
{
	get { return ContentTitle.Value; }
}

The second one ends with “Value” and these properties are intended to be used in LINQ expressions syntax for the IQueryable. This is done because in Fortis each property is an ItemWrapper and expressions not always work properly when writing nested property calls like item.ContentTitle.Value.

ItemSearchFactory

There are few examples on using the ItemSearchFactory here.
Basically there are several options you can use:

  • .Search(IQueryable qyeryable) method. In result you will have an IQueryable and you can do anything with it. Just call .ToList() on it to execute your query.
  • .FilteredSearch(IQueryable qyeryable) – this method will add into your queryable filters so StandardValues items will be skipped, the search will be only in the context language and in the latest item versions. Returns the IQueryable.
  • .GetResults(IQueryable qyeryable) – this method will execute your queryable and return the instance of ISearchResults where you can get some extended search result data.

ISearchResults interface:

public interface ISearchResults<TSource> : IEnumerable<ISearchHit<TSource>>, IEnumerable
{
	FacetResults Facets { get; set; }

	IEnumerable<ISearchHit<TSource>> Hits { get; set; }

	int TotalHits { get; set; }
}

IQueryable extensions

Fortis framework comes with number of IQueryable extension methods to help you writing your queries. They are mostly self-explanatory:

  • .WhereContextLanguage()
  • .WhereLatestVersion()
  • .WhereNotStandardValues()
  • .ApplyFilters() – this extension just calls all three methods above
  • .ContainsAnd(x => x.TagsValue, guidEnumerable) – select items where multilist field “Tags” contain all items in the guidEnumerable
  • .ContainsOr(x => x.TagsValue, guidEnumerable) – select items where multilist field “Tags” contain at least one item in the guidEnumerable

What are you waiting for? Go and try it! 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s