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.
And I want to create a rendering which will list them all on the page.
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! 🙂