This week I needed to add Blocks used on a page to EPiServer Search Index as content of the page they were used on. This is supported in Find, but as we are not using Find in this project, I needed a different solution.
First I added an extra property to my base class to hold the content of my Blocks. Note the ScaffoldColumn(false) attribute, which hides it in edit mode.
[CultureSpecific] [ScaffoldColumn(false)] [Searchable] public virtual string SearchText { get; set; }
Next I created an InitializableModule to attach some functionality to the publishing event
[ModuleDependency(typeof(InitializationModule))] public class SearchInitialization : IInitializableModule { #region Public Methods and Operators public void Initialize(InitializationEngine context) { DataFactory.Instance.PublishingPage += this.OnPublishingPage; } /// <summary> Raises the page event. </summary> /// <param name="sender"> The sender. </param> /// <param name="pageEventArgs"> Event information to send to registered event handlers. </param> public void OnPublishingPage(object sender, PageEventArgs pageEventArgs) { StandardPage page = pageEventArgs.Page as StandardPage; if (page == null) { return; } ContentArea contentArea = page.MainContentArea; if (contentArea == null) { return; } StringBuilder stringBuilder = new StringBuilder(); foreach (ContentAreaItem contentAreaItem in contentArea.Items) { IContent blockData = contentAreaItem.GetContent(); IEnumerable<string> props = GetSearchablePropertyValues(blockData, blockData.ContentTypeID); stringBuilder.AppendFormat(" {0}", string.Join(" ", props)); } page.SearchText= stringBuilder.ToString(); } public void Preload(string[] parameters) { } public void Uninitialize(InitializationEngine context) { DataFactory.Instance.PublishingPage -= this.OnPublishingPage; } #endregion #region Methods private IEnumerable<string> GetSearchablePropertyValues(IContentData contentData, ContentType contentType) { if (contentType == null) { yield break; } foreach (PropertyDefinition current in from d in contentType.PropertyDefinitions where d.Searchable || typeof(IPropertyBlock).IsAssignableFrom(d.Type.DefinitionType) select d) { PropertyData propertyData = contentData.Property[current.Name]; IPropertyBlock propertyBlock = propertyData as IPropertyBlock; if (propertyBlock != null) { foreach ( string current2 in this.GetSearchablePropertyValues( propertyBlock.Block, propertyBlock.BlockPropertyDefinitionTypeID)) { yield return current2; } } else { yield return propertyData.ToWebString(); } } yield break; } private IEnumerable<string> GetSearchablePropertyValues(IContentData contentData, int contentTypeID) { IContentTypeRepository contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>(); return this.GetSearchablePropertyValues(contentData, contentTypeRepository.Load(contentTypeID)); } #endregion }
So, what’s happening here?
- When publishing the page, I loop through all Blocks in the MainContentArea, also defined on my base class.
- I get all values from string properties marked "Searchable" in the Blocks and add them to the SearchText property on my base class. Note: If for some reason you don’t want a property on a Block indexed, just add [Seachable(false)] to it.
- The value of SearchText property is added to the index and you get results also if the term you are looking for is only used in a Block on the page.
The two helper classes are from the "EPiServer.Core.ContentSearchHandler".
That’s it.
Added a null check on line 26 of the initializable module, you might run into that
LikeLike
Hi there!
I’m trying to implement this solution into my episerver site using the Alloy templates, but I keep getting an error on line 66:
Error 81 Could not find an implementation of the query pattern for source type ‘EPiServer.DataAbstraction.PropertyDefinitionCollection’. ‘Where’ not found. Are you missing a reference or a using directive for ‘System.Linq’?
Any idea what’s causing this error?
Thanks in advance.
LikeLike
Hi Emil,
Do you have a reference to Linq in your project and a using in your class?
You should have at least these:
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using EPiServer.SpecializedProperties;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
Grtz,
Jeroen
LikeLike
So sloppy of me.. That solved it 🙂 Thank you for sharing this solution!
LikeLike
My pleasure. Glad it was solved.
LikeLike
Hi Jeroen, I am new at MVC and EpiServer. Can you tell me where to put the InitializableModule code?
LikeLike
Hi D, you can put an InitializableModule where you want basically. Usually I prefer a folder called infrastructure or business/initialization.
But is this a question in general or for this “module” specifically? If you want to use this module you can just install it from NuGet: http://nuget.episerver.com/en/OtherPages/Package/?packageId=EPi.Libraries.BlockSearch
LikeLike