Customize the Search-Index Results (Dnn ☢️ only)
Dnn has a built-in search engine which crawls all the modules asking them for data.
You can easily modify how data in your modules appear in the Dnn search results.
Tip
Before you start, make sure you understand how the Search Index and Customizations work.
Note
This document applies to 2sxc 12.02+. As of 2sxc 12 we only recommend this new approach using the separate code file.
Previous versions used another mechanism which is deprecated. If you need to know more, read the Obsolete Razor docs.
Programming a Search Mapper
Here's an example of a SearchMapper.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using ToSic.Sxc.Context;
using ToSic.Sxc.Search;
/*
Custom code which views use to customize how dnn search treats data of that view.
It's meant for customizing the internal indexer of the platform, _not_ for Google Search.
To use it, create a custom code (.cs) file which implements ICustomizeSearch interface.
You can also inherit from a DynamicCode base class (like Code12) if you need more functionality.
For more guidance on search customizations, see https://go.2sxc.org/customize-search
*/
public class SearchMapper : Custom.Hybrid.Code12, ICustomizeSearch
{
/// <summary>
/// Populate the search
/// </summary>
/// <param name="searchInfos">Dictionary containing the streams and items in the stream for this search.</param>
/// <param name="moduleInfo">Module information with which you can find out what page it's on etc.</param>
/// <param name="beginDate">Last time the indexer ran - because the data you will get is only what was modified since.</param>
public void CustomizeSearch(Dictionary<string, List<ISearchItem>> searchInfos, IModule moduleInfo, DateTime beginDate)
{
// Set this to true if you want to see logs of this search in the insights
// Only do this while developing, otherwise you'll flood the logs and never see the important parts
Log.Preserve = false;
foreach (var si in searchInfos["AllPosts"])
{
var entity = AsDynamic(si.Entity);
si.Title = "Title: " + entity.Title;
si.QueryString = "details=" + entity.UrlKey;
}
// Remove not needed streams
var keys = searchInfos.Keys.ToList();
foreach (var key in keys)
{
if (key != "AllPosts")
{
searchInfos.Remove(key);
}
}
}
}
Basics to get Right
- The File name can be anything you want, but the class name must match it.
- Your code can be a simple C# class, but we recommend it inherits from
Custom.Hybrid.Code12
- ...because you then also get more objects like
App
orCmsContext
- You can also inherit from
Custom.Dnn.Code12
which would give you theDnn
object but we don't suggest it, because you should use theCmsContext
where possible.
- ...because you then also get more objects like
- Your class must implement
ToSic.Sxc.Search.ICustomizeSearch
to inform the compiler that it can help with search mapping - You must then implement
public void CustomizeSearch(...)
as shown in the example
Understanding Search-Mapping
Your code will receive the data which would otherwise just be passed to the Dnn Indexer. You can then modify it as you want and make changes like:
- Remove streams from the dictionary
streamInfos
- thereby dropping entire sets of Entities - Remove Entities in a specific stream
- Change properties like the Title
- Change properties like the QueryString - this is great on list views where data is indexed in the list, but the link in the search results should go to a details page.
Develop Search Customizations
To create your search indexing code you'll probably need to tweak and test a few times. Note that the 2sxc Blog App shows you a real-life example of Search-Customizations.
So once you've configured a View to use a custom Search-Mapper your work will usually consist of doing the following
- Making some changes
- Going into the Dnn Admin and flushing the search-index
- Then run the indexer and wait till it's completed
- Check the results or debug issues using the Dnn Events-Log or 2sxc Insights (see below)
Debugging Search Indexing
Two tools will help you to debug issues
1. Dnn Events Log
Really bad issues (like if your code cannot compile) will be logged in the Dnn Events. So if your code isn't even running, check that.
2. 2sxc Insights
2sxc Insights will help you see what's happening exactly in your code when you need it.
Warning
By default the search index will not log its work in the Insights, because it would flood the logs and you wouldn't find the occurances which you need.
Because of this, logging is disabled by default, and your code can activate it using Log.Preserve = true;
Remember to add a bunch of logging like Log.Add("Got to here");
etc. to verify everything works step-by-step.
Common Issues
Already Indexed Data is not Reindexed
Often when you're playing with indexing customizations you'll re-run the indexer and expect to see the changed results - but it's still what was there before. This is because each Entity has a modified timestamp and only changed entities will be re-indexed. This is great for performance, but challenging when making changes.
👉 Remember to flush the Dnn Search Index before re-indexing to really see if your code worked.
Search Index and Multilanguage (i18n)
It's important to know that on multi-language sites, the module is indexed multiple times for each language. So just be aware of that.
This event is called by the view-engine after calling CustomizeData and before passing the Data
object to the Dnn Search Indexer.
You can override this event to change how data is presented to the search, for example by bundling items together, or by giving items different URLs so that search knows that they are to appear on a sub-page.
We have an rich series of Razor tutorials. You should really check them out 👍.
How to use
In your razor page (.cshtml file) you can add a script block implementing this, as follows:
@using ToSic.Eav.Run;
@using ToSic.Sxc.Dnn.Run;
@using ToSic.Sxc.Search;
@functions
{
// this method is optional - your code wouldn't need it, but it's in here to show how it would work together
// the CustomizeData would be called first, and potentially modify what is in the Data-object
public override void CustomizeData()
{
// Don't customize anything, nothing to customize in this case
}
/// <summary>
/// Populate the search - ensure that each entity has an own url/page
/// </summary>
/// <param name="searchInfos"></param>
/// <param name="moduleInfo"></param>
/// <param name="startDate"></param>
public override void CustomizeSearch(Dictionary<string, List<ISearchItem>> searchInfos, IContainer moduleInfo, DateTime beginDate)
{
foreach (var si in searchInfos["Default"])
{
// tell the search system what url it should use in the result
si.QueryString = "mid="+ (moduleInfo as DnnContainer).Id + "&feature=" + si.Entity.EntityId;
}
}
}
The code above will skip customizing any data (but often you would want that too), then CustomizeSearch modifies the list of search-items before they are indexed.
How it works
In general everything will work automatically. This is what happens:
- 2sxc will retrieve the data added to this module
- 2sxc will call the CustomizeData() event if the template has such an event. In this event, your code can add more data to the module as needed.
- Note that during the search index, no Request-variables exist.
- So your method will cause an error if it does something like var x = Request["Category"].
- In case of an error, the index will still continue to work, but your changes to the data will fail
- To help you with this, a new property called Purpose was added. It tells you if this view/template was created for displaying or for indexing.
- 2sxc will then use the data and create SearchItems, ready to index.
- Each entity will be turned into a SearchItem
- Each Content-Type will have an own list (so you can differentiate between all the SearchItems for the Categories and the SearchItems for the Questions)
- Multi-Language is handled correctly, so the English index will contain the English content, etc.
- 2sxc will then call a CustomizeSearch() event, so your code could provide changes.
- A common scenario is to say that each entity (say each question) has a different URL (say a details-page).
- So even though all entities belong to the module (and Dnn only knows of this one module), the module can say that each entity has an own details page.
- One this is done, the SearchItems are converted to official SearchDocument-objects and handed over to Dnn
Read also
- Purpose - which tells you why the current code is running so you could change the data added
- CustomizeData
Demo App and further links
You should find some code examples in this demo App
More links: Description of the feature on 2sxc docs
History
- Introduced in 2sxc 6.2
- Added support for newer Dnn versions at a later time - not sure when
- Easier standalone
.cs
implementation introduced in 2sxc 12