Поисковый плагин для Episerver CMS и Commerce
Версия, поддерживающая .net 6+ доступна. Свяжитесь с Кристианом Боргом для получения подробной информации.
Прежде всего, вам нужно создать свой индекс. Перейдите на страницу администрирования через встроенную поисковую систему меню -> Администрирование и статус, затем нажмите кнопку Create indices .
Это создаст один индекс на язык на вашем сайте. Если у вас установлен Addon Commerce, также будут созданы дополнительные индексы для контента каталога.

Для каждого активного языка будет создан отдельный индекс. Если вы добавите больше языков позже, этот процесс необходимо повторить.
Вы можете настроить свою настройку программно с помощью Singleton Epinova.ElasticSearch.Core.Conventions.Indexing . Сделайте это только один раз на приложение, обычно в инициализируемом модуле или Application_Start ().
Пример класса конфигурации:
public static class SearchConfig
{
public static void Init ( )
{
Epinova . ElasticSearch . Core . Conventions . Indexing . Instance
. ExcludeType < ErrorPage > ( )
. ExcludeType < StartPage > ( )
. ExcludeRoot ( 42 )
. IncludeFileType ( "pdf" )
. IncludeFileType ( "docx" )
. ForType < ArticlePage > ( ) . IncludeProperty ( x => x . Changed )
. ForType < ArticlePage > ( ) . IncludeField ( x => x . GetFoo ( ) )
. ForType < ArticlePage > ( ) . IncludeField ( "TenYearsAgo" , x => DateTime . Now . AddYears ( - 10 ) )
. ForType < ArticlePage > ( ) . EnableSuggestions ( x => x . Title )
. ForType < TagPage > ( ) . EnableSuggestions ( )
. ForType < ArticlePage > ( ) . EnableHighlighting ( x => x . MyField )
. ForType < ArticlePage > ( ) . StemField ( x => x . MyField ) ;
}
}Объяснение различных вариантов:
ExcludeType : этот тип не будет индексирован. Базовые классы и интерфейсы также поддерживаются. То же самое можно было достичь, украшая ваш тип с ExcludeFromSearchAttribute
ExcludeRoot : этот узел и его дети не будут проиндексированы. Как правило, идентификатор узла на странице Episerver.
IncludeFileType : определяет расширение для типов файлов, которые должны быть индексированы. См. Также узел <files> в конфигурации.
ForType : отправная точка для правил индексации конкретного типа. Ничего не делает само по себе, но обеспечивает доступ к следующим параметрам:
IncludeField : определяет пользовательские свойства, которые должны быть проиндексированы. Например, метод расширения, который получает внешние данные или выполняет сложные агрегации.
EnableSuggestions : предлагайте аутозащитные данные из этого типа, либо из всех свойств, либо выбранных, через выражение Lambda.
IncludeProperty : тот же эффект, что и украшение свойства с [доступным для поиска]. Можно использовать, если вы не контролируете источник модели.
EnableHighlighting : добавьте дополнительные поля, которые будут выделены.
StemField : определяет, что собственность должна быть проанализирована на текущем языке. Свойства с именем MainIntro , MainBody и Description всегда будут анализироваться.
Модуль пытается следовать тем же соглашениям, что и Episerver, что означает, что все свойства типа string и XhtmlString будут индексировать, если только явно украшены [Searchable(false)] . Дополнительные свойства могут быть проиндексированы, украшая их [Searchable] или с помощью соглашений выше. Украсьте ContentArea [Searchable] для индексации содержимого.
SearchResult result = service
. Search ( "bacon" )
. GetResults ( ) ; SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. GetResults ( ) ; SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. InField ( x => x . MainIntro )
. InField ( x => x . MainBody )
. InField ( x => x . MyCustomMethod ( ) )
. GetResults ( ) ;Ищет индексированные слова, начиная с фразы:
string [ ] suggestions = service . GetSuggestions ( "baco" ) ;С автоматической длиной по умолчанию (рекомендуется):
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( )
. GetResults ( ) ;С конкретной длиной:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( 4 )
. GetResults ( ) ;Используйте с осторожностью, так как это не поддерживает такие функции, как Stemming.
SearchResult result = service
. WildcardSearch < ArticlePage > ( "*bacon*" )
. GetResults ( ) ; SearchResult result = service
. WildcardSearch < ArticlePage > ( "me?t" )
. GetResults ( ) ; SearchResult result = service
. SimpleQuerystringSearch < ArticlePage > ( "(bacon | ham) melt -cheese" )
. GetResults ( ) ;С ограниченными операторами:
SearchResult result = service
. SimpleQuerystringSearch < ArticlePage > ( " " bacon melt " sandwi*" ,
defaultOperator : Operator . And ,
allowedOperators :
SimpleQuerystringOperators . Prefix |
SimpleQuerystringOperators . Phrase ) ;
)
. GetResults ( ) ;См. Https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html для синтаксиса.
Использование свойств типа Epinova.ElasticSearch.Core.Models.Properties.GeoPoint позволяет вам выполнять фильтрацию на основе гео.
Пример модели:
public class OfficePage : StandardPage
{
[ Display ]
public virtual double Lat { get ; set ; }
[ Display ]
public virtual double Lon { get ; set ; }
// Helper property, you could always roll an editor for this
public GeoPoint Location => new GeoPoint ( Lat , Lon ) ;
} Найдите точки в квадратной области в зависимости от ее верхних левых и правых нижних углов.
var topLeft = ( 59.9277542 , 10.7190847 ) ;
var bottomRight = ( 59.8881646 , 10.7983952 ) ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoBoundingBox ( x => x . Location , , topLeft , bottomRight )
. GetResults ( ) ; Найдите точки внутри круга, которая дает центральную точку и расстояние радиуса.
var center = ( 59.9277542 , 10.7190847 ) ;
var radius = "10km" ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoDistance ( x => x . Location , radius , center )
. GetResults ( ) ; Найдите точки внутри многоугольника с произвольным количеством очков. Например, точки многоугольника могут быть контурами города, страны или других видов областей.
var polygons = new [ ]
{
( 59.9702837 , 10.6149134 ) ,
( 59.9459601 , 11.0231964 ) ,
( 59.7789455 , 10.604809 )
} ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoPolygon ( x => x . Location , polygons )
. GetResults ( ) ;Найти контент, аналогичный предоставлению идентификации документов
SearchResult result = service
. MoreLikeThis ( "42" )
. GetResults ( ) ;Коммерция:
SearchResult result = service
. MoreLikeThis ( "123__CatalogContent" )
. GetResults ( ) ;Дополнительные параметры:
minimumTermFrequency частота минимальной частоты, ниже которой будут проигнорированы термины из входного документа. По умолчанию 1.
maxQueryTerms Максимальное количество терминов запроса, которое будет выбрано. Увеличение этого значения дает большую точность за счет скорости выполнения запросов. По умолчанию до 25.
minimumDocFrequency минимальной частоты документов, ниже которой будут проигнорированы термины из входного документа. По умолчанию 3.
minimumWordLength слов минимальная длина слова, ниже которой будут проигнорированы термины. По умолчанию 3.
Гаджет:

Если вы не хотите исключать тип во всем мире, вы можете сделать это только в контексте запроса:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude < SaladPage > ( )
. GetResults ( ) ; Исключить узел на время запроса:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude ( 42 )
. Exclude ( contentInstance )
. Exclude ( contentReference )
. GetResults ( ) ; Запрос использует CurrentCulture в качестве дефолта. Это может быть переопределено:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Language ( CultureInfo . GetCultureInfo ( "en" ) )
. GetResults ( ) ; Используйте методы From() и Size() для страниц, или псевдонимы Skip() и Take() :
SearchResult result = service
. Search < CoursePage > ( "foo" )
. From ( 10 )
. Size ( 20 )
. GetResults ( ) ; var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID ) ; Графики будут созданы из применяемых в настоящее время фильтров, а не от всего результата.
var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID , usePostFilter : false ) ; Одно значение:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilter ) ;Одно значение, по методу расширения:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . GetDepartmentID ( ) , selectedFilter ) ;Несколько значений:
string [ ] selectedFilters = Request . Querstring [ "filters" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilters ) ;Используйте и оператор для фильтрации на всех фильтрах:
string [ ] selectedFilters = Request . Querstring [ "filters" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilters , Operator . And ) ; SearchResult results = query . GetResults ( ) ; foreach ( FacetEntry facet in results . Facets )
{
// facet.Key = name of the property, ie. DepartmentID
// facet.Count = number of unique values for this facet
foreach ( FacetHit hit in facet . Hits )
{
// hit.Key = facet value
// hit.Count = number with this value
}
} Несколько фильтров могут быть сгруппированы, чтобы сформировать более сложные запросы.
В приведенном ниже примере должно быть верно, чтобы дать совпадение:
Sizes - это либо "xs" , либо "xl"
ProductCategory - это "pants"
Brand - это либо "levis" , либо "diesel"
query = query
. FilterGroup ( group => group
. Or ( page => page . Sizes ( ) , new [ ] { "xs" , "xl" } )
. And ( page => page . ProductCategory , "pants" )
. Or ( page => page . Brand , new [ ] { "levis" , "diesel" } )
) ; Чтобы отфильтровать определенные значения, используйте FilterMustNot
var query = service
. Search < PageData > ( "foo" )
. FilterMustNot ( x => x . Title , "bar" ) ; Чтобы фильтровать текущих пользователей ACL, используйте FilterByACL
var query = service
. Search < PageData > ( "foo" )
. FilterByACL ( ) ; EPiServer.Security.PrincipalInfo.Current будет использоваться по умолчанию, но при необходимости может поставляться пользовательский PrincipalInfo .
Чтобы найти в данном диапазоне значений, используйте функцию Range .
Поддерживаемые типы - это DateTime , double , decimal и long (включая неявное преобразование int , byte и т. Д.)
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . StartPublish , DateTime . Now . Date , DateTime . Now . Date . AddDays ( 2 ) )
. GetResults ( ) ; SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyNumber , 10 , 20 )
. GetResults ( ) ;Менее, чем аргумент является необязательным.
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyNumber , 10 ) // Returns anything above 10
. GetResults ( ) ; Чтобы найти интервал в другом интервале, ваша собственность должна быть типа Epinova.ElasticSearch.Core.Models.Properties.IntegerRange .
В настоящее время int является единственным поддерживаемым типом.
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyRange , 10 , 20 )
. GetResults ( ) ; public class ArticlePage : StandardPage
{
[Display]
public virtual int From { get; set; }
[Display]
public virtual int To { get; set; }
public IntegerRange MyRange => new IntegerRange(From, To);
}
Если свойство имеет тип IDictionary<string, object> и Marked [Searchable] , оно будет индексирован как object в Elasticsearch.
Это полезно в сценариях, где у вас есть динамические данные о значении ключей, которые должны быть индексированы, как из PIM-систем.
Стандартный подход к недвижимости:
public class ProductPage
{
[Searchable]
public IDictionary<string, object> Metadata { get; set; }
}
Сами данные не будут возвращены, но вы можете запросить их и сделать грани:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. InField ( x => x . Metadata + ".SomeKey" )
. FacetsFor ( x => x . Metadata + ".SomeKey" )
. GetResults ( ) ;Пользовательские свойства:
public static class SearchConfig
{
public static void Init ( )
{
Epinova . ElasticSearch . Core . Conventions . Indexing . Instance
. ForType < ProductPage > ( ) . IncludeField ( "Metadata" , x => x . GetPimDataDictionary ( ) ) ;
}
}Этот подход возвращает данные:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. GetResults ( ) ;
var hit = result . Hits . First ( ) ;
var dict = hit . Custom [ "Metadata" ] as IDictionary < string , object > Свойства можно повысить, украшая их с атрибутом Boost :
[ Boost ( 13 ) ]
public string Title { get ; set ; }… Или в запросе времени:
SearchResult result = service
. Search ( "bacon" )
. Boost ( x => x . MyProp , 3 )
. GetResults ( ) ;Тип может быть предоставлен либо положительный, либо отрицательный импульс:
SearchResult result = service
. Search ( "bacon" )
. Boost < ArticlePage > ( 2 )
. GetResults ( ) ; SearchResult result = service
. Search ( "bacon" )
. Boost ( typeof ( ArticlePage ) , 2 )
. GetResults ( ) ; SearchResult result = service
. Search ( "bacon" )
. Boost < ArticlePage > ( - 3 )
. GetResults ( ) ;Вы также можете повысить хиты в зависимости от их местоположения в дереве страницы:
SearchResult result = service
. Search ( "bacon" )
. BoostByAncestor ( new ContentReference ( 42 ) , 2 )
. GetResults ( ) ; Свойства даты могут быть оценены ниже в зависимости от их значения, используя функцию Decay . Таким образом, вы можете продвигать новые статьи по сравнению с более старыми.
SearchResult result = service
. Search ( "bacon" )
. Decay ( x => x . StartPublish , TimeSpan . FromDays ( 7 ) )
. GetResults ( ) ;Каждый раз, когда происходит интервал даты (2 -й аргумент), из оценки будет вычтено 0,5 балла. В приведенном выше примере 0,5 баллов будут вычтены через 7 дней, 1 балла через 14 дней и так далее.
Используйте .UseBestBets() в вашем запросе, чтобы оценить выбранной контент выше обычного.
SearchResult result = service
. Search ( "bacon" )
. UseBestBets ( )
. GetResults ( ) ;Лучшие ставки можно управлять с помощью встроенной поисковой системы меню -> Лучшие ставки.

Простая статистика может быть собрана с использованием функции .Track() . Это будет отслеживать количество раз, когда термин будет запрашивается и возвращал ли он какие -либо хиты.
SearchResult result = service
. Search ( "bacon" )
. Track ( )
. GetResults ( ) ; 
ПРИМЕЧАНИЕ. Если ваша строка подключения не называется EPiServerDB , вы должны предоставить его имя в trackingConnectionStringName -configuration см. Установка
Stemming по умолчанию применяется ко всем свойствам типа XhtmlString или тех, которые называются MainIntro или MainBody .
Язык основан на контент -языке. Другие свойства string типа могут быть установлены, украшая их с помощью Stem -atribute:
[ Stem ]
public string Title { get ; set ; } Чтобы перечислить содержимое определенного типа без какого -либо оценки или анализа, используйте функцию Get . Это можно использовать в сочетании с SortBy для простых списков.
SearchResult result = service
. StartFrom ( somePageLink )
. Get < ArticlePage > ( )
. GetResults ( ) ; Сортировка обычно выполняется Elasticsearch на основе оценки каждого матча. Ручная сортировка должна использоваться только в сценариях, где оценка не является актуальной, например, при использовании ранее упомянутой функции Get .
SearchResult result = service
. Get < ArticlePage > ( )
. SortBy ( p => p . StartPublish )
. ThenBy ( p => p . Foo )
. ThenBy ( p => p . Bar )
. GetResults ( ) ; При сортировке на геопоте есть еще один обязательный аргумент; compareTo . Элементы будут сравниваться с этими координатами, и полученные расстояния будут использоваться в качестве значений сортировки.
Для абсолютного контроля вы можете использовать скрипт для сортировки документов. Обратите внимание, что это может повлиять на производительность.
Пример сортировки на основе определенной метки времени:
var timestamp = new DateTimeOffset ( new DateTime ( 2019 , 1 , 1 ) ) . ToUnixTimeMilliseconds ( ) ;
var script = $ "doc['StartPublish'].date.getMillis() > { timestamp } ? 0 : 1" ;
SearchResult result = service
. Get < ArticlePage > ( )
. SortByScript ( script , true , "number" )
. GetResults ( ) ;См. Https://www.elastic.co/guide/en/elasticsearch/painsle/current/index.html для синтаксиса сценариев.
Включен фильтр гальки, который может предложить аналогичные слова, найденные в индексе при поиске слов с ошибками.
Любые найденные предложения будут включены в свойство результата поиска DidYouMean :
SearchResult result = service
. Search ( "alloi" )
. GetResults ( ) ;
string [ ] didYouMean = result . DidYouMean ; // [ "alloy", "all" ] Любые свойства, которые должны выступать в качестве источника предложений, должны быть отмечены [DidYouMeanSource] .
public class StandardPage : SitePageData
{
[ DidYouMeanSource ]
public virtual XhtmlString MainBody { get ; set ; }
} Результаты, возвращаемые GetResults() не имеют никаких знаний о Episerver. Используйте функцию GetContentResults() в контексте Episerver.
Это автоматически применяет фильтры FilterAccess , FilterPublished и FilterTemplate .
IEnumerable < IContent > content = service
. Search < CoursePage > ( text )
. GetContentResults ( ) ; Если вы хотите использовать любого из прилагаемых поставщиков при поиске в режиме редактирования, перейдите к CMS -> Admin -> config -> Настройки инструмента -> Поиск конфигурации.
Узнайте поставщиков и перетащите их в верхнюю часть списка.
Синонимы можно управлять из поисковой системы меню -> синонимы.
Контент будет автоматически повторно индексирован при выполнении общих операций, таких как публикация, перемещение и удаление.
Чтобы выполнить начальную индексацию всего содержимого, запустите запланированную задачу «Elasticsearch: Index CMS -контент»
Реиндексирование также может быть вызвано вручную на отдельном контенте через инструменты-меню:

… Или через контекстное меню в дереве страниц:

Результаты, возвращаемые GetResults() не имеют никаких знаний о Episerver. Используйте функцию GetCatalogResults() в контексте коммерции Episerver. Это автоматически выберет правильный индекс и применяет фильтры FilterAccess , FilterPublished и FilterTemplate .
IEnumerable < ProductContent > content = service
. Search < ProductContent > ( text )
. GetCatalogResults ( ) ; Контент будет автоматически повторно индексирован при выполнении общих операций, таких как публикация, перемещение и удаление.
Чтобы выполнить начальную индексацию всего содержимого, запустите запланированную задачу «Elasticsearch: Index Commerce Content»
Вы можете переключаться между нормальным и трикокенизатором Tri -Gram (жестко -кодированный на Min = 3, max = 3, tokens = цифра, char, elastic рекомендации) через поисковую систему меню -> Администрирование и статус.
Используйте функцию Highlight() , чтобы получить отрыв из 150 символов, откуда совпадение произошло в тексте.
SearchResult result = service
. Search ( "bacon" )
. Highlight ( )
. GetContentResults ( ) ; Выделение по умолчанию включено на свойствах с именем MainIntro , MainBody , Attachment и Description .
Маркер по умолчанию <mark>
Вы можете настроить это поведение со следующими параметрами конфигурации:
Indexing . Instance . ForType < ArticlePage > ( ) . EnableHighlighting ( x => x . MyField ) ;
Indexing . Instance . SetHighlightFragmentSize ( 42 ) ;
Indexing . Instance . SetHighlightTag ( "blink" ) ; Результаты можно найти в свойстве Highlight каждого удара.
Используйте Bulk функцию для индексации пользовательского контента.
Пример:
var obj1 = new ComplexType { StringProperty = "this is myobj 1" } ;
var obj2 = new ComplexType { StringProperty = "this is myobj 2" } ;
var obj3 = new ComplexType { StringProperty = "this is myobj 3" } ;
ICoreIndexer indexer = ServiceLocator . Current . GetInstance < ICoreIndexer > ( ) ; // Can also be injected
BulkBatchResult bulk = indexer . Bulk ( new [ ]
{
new BulkOperation ( obj1 , "no" ) ,
new BulkOperation ( obj2 , "no" ) ,
new BulkOperation ( obj3 , "no" )
} ) ;
var results = _service
. Search < ComplexType > ( "myobj" )
. InField ( x => x . StringProperty )
. GetCustomResults ( ) ; Если вы предпочитаете пользовательский индекс, чтобы избежать, например, столкновения, это должно быть предоставлено при индексации и поиске:
var obj1 = new ComplexType { StringProperty = "this is myobj 1" } ;
var obj2 = new ComplexType { StringProperty = "this is myobj 2" } ;
var obj3 = new ComplexType { StringProperty = "this is myobj 3" } ;
ICoreIndexer indexer = ServiceLocator . Current . GetInstance < ICoreIndexer > ( ) ; // Can also be injected
string indexName = "my-uber-custom-name-no" ;
BulkBatchResult bulk = indexer . Bulk ( new [ ]
{
new BulkOperation ( obj1 , "no" , index : indexName ) ,
new BulkOperation ( obj2 , "no" , index : indexName ) ,
new BulkOperation ( obj3 , "no" , index : indexName )
} ) ;
var results = _service
. UseIndex ( indexName )
. Search < ComplexType > ( "myobj" )
. InField ( x => x . StringProperty )
. GetCustomResults ( ) ; Epinova.elasticsearch использует стандартный HttpClient для названия Elasticsearch. Иногда необходимо обрабатывать сообщения, отправляемые по -разному. Например, запрос подписания для облачных сервисов.
Если вы хотите несколько httpmessagehandlers, мы рекомендуем их цеплять перед настройкой.
Например:
MessageHandler . Instance . SetMessageHandler ( new AWSHandler ( ) ) ; GetCustomResults возвращает сильно напечатанные объекты, а не GetContentResults , что только возвращает идентификаторы.
Пользовательские объекты не требуют свойства Id (или соответствующего аргумента в CTOR BulkOperation ), но это рекомендуется, если вы хотите контролировать разведение версий и обновления/делеции.