Um plug -in de pesquisa para CMS e comércio EpiServer
Uma versão que suporta .NET 6+ está disponível. Entre em contato com Kristian Borg para obter detalhes.
Primeiro de tudo, você precisa criar seu índice. Vá para a página de administração através do mecanismo de pesquisa de menu incorporado -> Administração e status e clique no botão Create indices .
Isso criará um índice por idioma em seu site. Se você tiver o comércio de comércio instalado, índices adicionais para conteúdo de catálogo também serão criados.

Um índice separado será criado para cada idioma ativo em seu site. Se você adicionar mais idiomas posteriormente, esse processo precisará ser repetido.
Você pode configurar sua configuração programaticamente com o singleton Epinova.ElasticSearch.Core.Conventions.Indexing . Faça isso apenas uma vez por aplicativo, normalmente em um módulo inicializável ou application_start ().
Uma classe de configuração de amostra:
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 ) ;
}
}Explicação das diferentes opções:
ExcludeType : este tipo não será indexado. Classes e interfaces base também são suportadas. O mesmo pode ser alcançado decorando seu tipo com ExcludeFromSearchAttribute
ExcludeRoot : Este nó e seus filhos não serão indexados. Normalmente, um nó-Id na página do EpiServer.
IncludeFileType : Define a extensão dos tipos de arquivos que devem ser indexados. Consulte também o nó <files> na configuração.
ForType : ponto de partida para a indexação regras de um tipo específico. Não faz nada em si, mas fornece acesso às seguintes opções:
IncludeField : define propriedades personalizadas a serem indexadas. Por exemplo, um método de extensão que busca dados externos ou executa agregações complexas.
EnableSuggestions : Ofereça dados autosgestões desse tipo, de todas as propriedades ou selecionadas por meio de uma expressão de Lambda.
IncludeProperty : Mesmo efeito que a decoração de uma propriedade com [pesquisável]. Pode ser usado se você não controlar a fonte do modelo.
EnableHighlighting : adicione campos adicionais a serem destacados.
StemField : define que uma propriedade deve ser analisada no idioma atual. Propriedades nomeadas MainIntro , MainBody e Description sempre serão analisadas.
O módulo tenta seguir as mesmas convenções que o EpiServer, o que significa que todas as propriedades da string do tipo e XhtmlString serão indexadas, a menos que explicitamente decoradas com [Searchable(false)] . Propriedades adicionais podem ser indexadas decorando -as com [Searchable] ou com as convenções acima. Decore ContentArea com [Searchable] para indexar o conteúdo.
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 ( ) ;Pesquisa por palavras indexadas começando com a frase:
string [ ] suggestions = service . GetSuggestions ( "baco" ) ;Com o comprimento padrão automático (recomendado):
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( )
. GetResults ( ) ;Com comprimento específico:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( 4 )
. GetResults ( ) ;Use com cuidado, pois isso não suporta recursos como o 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 ( ) ;Com operadores limitados:
SearchResult result = service
. SimpleQuerystringSearch < ArticlePage > ( " " bacon melt " sandwi*" ,
defaultOperator : Operator . And ,
allowedOperators :
SimpleQuerystringOperators . Prefix |
SimpleQuerystringOperators . Phrase ) ;
)
. GetResults ( ) ;Consulte https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-queryy-string-query.html para sintaxe.
O uso de propriedades do tipo Epinova.ElasticSearch.Core.Models.Properties.GeoPoint permite executar a filtragem baseada em geo.
Modelo de exemplo:
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 ) ;
} Encontre pontos dentro de uma área quadrada com base em seus cantos superior e inferior direito.
var topLeft = ( 59.9277542 , 10.7190847 ) ;
var bottomRight = ( 59.8881646 , 10.7983952 ) ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoBoundingBox ( x => x . Location , , topLeft , bottomRight )
. GetResults ( ) ; Encontre pontos dentro de um círculo dado um ponto central e a distância do raio.
var center = ( 59.9277542 , 10.7190847 ) ;
var radius = "10km" ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoDistance ( x => x . Location , radius , center )
. GetResults ( ) ; Encontre pontos dentro de um polígono com uma quantidade arbitrária de pontos. Os pontos de polígono podem, por exemplo, ser os contornos de uma cidade, país ou outros tipos de áreas.
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 ( ) ;Encontre conteúdo semelhante ao ID de documentão fornecido
SearchResult result = service
. MoreLikeThis ( "42" )
. GetResults ( ) ;Comércio:
SearchResult result = service
. MoreLikeThis ( "123__CatalogContent" )
. GetResults ( ) ;Parâmetros opcionais:
minimumTermFrequency A frequência mínima de termo abaixo da qual os termos serão ignorados no documento de entrada. Padrões para 1.
maxQueryTerms O número máximo de termos de consulta que serão selecionados. Aumentar esse valor fornece maior precisão às custas da velocidade de execução da consulta. Padrões para 25.
minimumDocFrequency A frequência mínima do documento abaixo do qual os termos serão ignorados no documento de entrada. Padrões para 3.
minimumWordLength comprimento do comprimento mínimo abaixo do qual os termos serão ignorados. Padrões para 3.
Gadget:

Se você não deseja excluir um tipo globalmente, pode fazê -lo apenas no contexto de uma consulta:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude < SaladPage > ( )
. GetResults ( ) ; Exclua um nó no tempo de consulta:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude ( 42 )
. Exclude ( contentInstance )
. Exclude ( contentReference )
. GetResults ( ) ; A consulta usa CurrentCulture como padrão. Isso pode ser substituído:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Language ( CultureInfo . GetCultureInfo ( "en" ) )
. GetResults ( ) ; Use os métodos From() e Size() para paginação, ou os aliases Skip() e Take() :
SearchResult result = service
. Search < CoursePage > ( "foo" )
. From ( 10 )
. Size ( 20 )
. GetResults ( ) ; var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID ) ; As facetas serão criadas a partir dos filtros aplicados atualmente e não de todo o resultado.
var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID , usePostFilter : false ) ; Um valor:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilter ) ;Um valor, por método de extensão:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . GetDepartmentID ( ) , selectedFilter ) ;Valores múltiplos:
string [ ] selectedFilters = Request . Querstring [ "filters" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilters ) ;Use e operador para filtrar todos os filtros:
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
}
} Vários filtros podem ser agrupados para formar consultas mais complexas.
No exemplo abaixo, o seguinte deve ser verdadeiro para dar uma correspondência:
Sizes são "xs" ou "xl"
ProductCategory é "pants"
Brand é "levis" ou "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" } )
) ; Para filtrar certos valores, use FilterMustNot
var query = service
. Search < PageData > ( "foo" )
. FilterMustNot ( x => x . Title , "bar" ) ; Para filtrar o ACL dos usuários atuais, use FilterByACL
var query = service
. Search < PageData > ( "foo" )
. FilterByACL ( ) ; EPiServer.Security.PrincipalInfo.Current será usado por padrão, mas um PrincipalInfo personalizado pode ser fornecido, se necessário.
Para pesquisar em um determinado intervalo de valores, use a função Range .
Os tipos suportados são DateTime , double , decimal e long (incluindo conversão implícita de int , byte etc.)
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 ( ) ;O argumento menos do que é opcional.
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyNumber , 10 ) // Returns anything above 10
. GetResults ( ) ; Para procurar um intervalo dentro de outro intervalo, sua propriedade deve ser do tipo Epinova.ElasticSearch.Core.Models.Properties.IntegerRange .
Atualmente int é o único tipo suportado.
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);
}
Se uma propriedade for do tipo IDictionary<string, object> e marcado [Searchable] , ela será indexada como um object no Elasticsearch.
Isso é útil em cenários em que você possui dados dinâmicos de valor-chave que devem ser indexados, como nos sistemas PIM.
Abordagem de propriedade padrão:
public class ProductPage
{
[Searchable]
public IDictionary<string, object> Metadata { get; set; }
}
Os dados em si não serão devolvidos, mas você pode consultá -los e fazer facetas:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. InField ( x => x . Metadata + ".SomeKey" )
. FacetsFor ( x => x . Metadata + ".SomeKey" )
. GetResults ( ) ;Propriedades personalizadas:
public static class SearchConfig
{
public static void Init ( )
{
Epinova . ElasticSearch . Core . Conventions . Indexing . Instance
. ForType < ProductPage > ( ) . IncludeField ( "Metadata" , x => x . GetPimDataDictionary ( ) ) ;
}
}Esta abordagem retorna os dados:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. GetResults ( ) ;
var hit = result . Hits . First ( ) ;
var dict = hit . Custom [ "Metadata" ] as IDictionary < string , object > As propriedades podem ser impulsionadas decorando -as com o atributo Boost :
[ Boost ( 13 ) ]
public string Title { get ; set ; }… Ou na hora da consulta:
SearchResult result = service
. Search ( "bacon" )
. Boost ( x => x . MyProp , 3 )
. GetResults ( ) ;Um tipo pode receber um impulso positivo ou negativo:
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 ( ) ;Você também pode aumentar os hits, dependendo da localização deles na árvore da página:
SearchResult result = service
. Search ( "bacon" )
. BoostByAncestor ( new ContentReference ( 42 ) , 2 )
. GetResults ( ) ; As propriedades da data podem ser pontuadas mais baixas, dependendo do seu valor, usando a função Decay . Dessa forma, você pode promover artigos mais recentes sobre os mais antigos.
SearchResult result = service
. Search ( "bacon" )
. Decay ( x => x . StartPublish , TimeSpan . FromDays ( 7 ) )
. GetResults ( ) ;Toda vez que o intervalo de data (segundo argumento) ocorre, 0,5 pontos serão subtraídos da pontuação. No exemplo acima, 0,5 pontos serão subtraídos após 7 dias, 1 pontos após 14 dias e assim por diante.
Use .UseBestBets() na sua consulta para obter conteúdo selecionado maior que o normal.
SearchResult result = service
. Search ( "bacon" )
. UseBestBets ( )
. GetResults ( ) ;As melhores apostas podem ser administradas através do mecanismo de pesquisa de menu incorporado -> Melhores apostas.

As estatísticas simples podem ser coletadas usando a função .Track() . Isso rastreará o número de vezes que um termo é consultado e se ele retornou algum hits.
SearchResult result = service
. Search ( "bacon" )
. Track ( )
. GetResults ( ) ; 
NOTA: Se sua string de conexão não for nomeada EPiServerDB você deverá fornecer seu nome no trackingConnectionStringName -Configuration, consulte a instalação
Por padrão, o STEMMING é aplicado a todas as propriedades do tipo XhtmlString , ou aquelas denominadas MainIntro ou MainBody .
O idioma é baseado no idioma de conteúdo. Outras propriedades da string de tipo podem ser decoradas decorando -as com o Stem -Attribute:
[ Stem ]
public string Title { get ; set ; } Para listar o conteúdo de um determinado tipo sem qualquer pontuação ou análise, use a função Get . Isso pode ser usado em conjunto com SortBy para listagens simples.
SearchResult result = service
. StartFrom ( somePageLink )
. Get < ArticlePage > ( )
. GetResults ( ) ; A classificação é normalmente realizada pelo Elasticsearch com base na pontuação de cada partida. A classificação manual deve ser usada apenas em cenários em que a pontuação não é relevante, por exemplo, ao usar a função Get mencionada anteriormente.
SearchResult result = service
. Get < ArticlePage > ( )
. SortBy ( p => p . StartPublish )
. ThenBy ( p => p . Foo )
. ThenBy ( p => p . Bar )
. GetResults ( ) ; Ao classificar em um ponto general, há mais um argumento obrigatório; compareTo . Os itens serão comparados a essas coordenadas e as distâncias resultantes serão usadas como valores de classificação.
Para controle absoluto, você pode usar um script para classificar documentos. Observe que isso pode afetar o desempenho.
Exemplo de classificação com base em um certo registro de data e hora:
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 ( ) ;Consulte https://www.elastic.co/guide/en/elasticsearch/painless/current/index.html para script sintaxe.
Um filtro de telha está incluído que pode sugerir palavras semelhantes encontradas no índice ao procurar palavras com ortografia.
Quaisquer sugestões encontradas serão incluídas na propriedade DidYouMean do resultado da pesquisa:
SearchResult result = service
. Search ( "alloi" )
. GetResults ( ) ;
string [ ] didYouMean = result . DidYouMean ; // [ "alloy", "all" ] Quaisquer propriedades que devem atuar como fonte de sugestões devem ser marcadas com [DidYouMeanSource] .
public class StandardPage : SitePageData
{
[ DidYouMeanSource ]
public virtual XhtmlString MainBody { get ; set ; }
} Os resultados retornados por GetResults() não possuem nenhum conhecimento do EpiServer. Use a função GetContentResults() em um contexto EpiServer.
Isso aplicará automaticamente o filtro FilterAccess , FilterPublished e FilterTemplate .
IEnumerable < IContent > content = service
. Search < CoursePage > ( text )
. GetContentResults ( ) ; Se você deseja usar algum dos provedores incluídos ao pesquisar no modo Editar, vá para CMS -> Admin -> Config -> Configurações da ferramenta -> Pesquisar configuração.
Marque os provedores e arraste -os para o topo da lista.
Os sinônimos podem ser administrados no mecanismo de pesquisa de menu -> sinônimos.
O conteúdo será re-indexado automaticamente ao executar operações comuns, como publicação, movimentação e exclusão.
Para fazer uma indexação inicial de todos os conteúdos, execute a tarefa programada «Elasticsearch: Index CMS Content»
A re-indexamento também pode ser acionada manualmente em conteúdo individual por meio do menu das ferramentas:

… Ou através do menu de contexto na árvore da página:

Os resultados retornados por GetResults() não possuem nenhum conhecimento do EpiServer. Use a função GetCatalogResults() em um contexto de comércio de episódio. Isso escolherá automaticamente o índice correto e aplicará os filtros FilterAccess , FilterPublished e FilterTemplate .
IEnumerable < ProductContent > content = service
. Search < ProductContent > ( text )
. GetCatalogResults ( ) ; O conteúdo será re-indexado automaticamente ao executar operações comuns, como publicação, movimentação e exclusão.
Para fazer uma indexação inicial de todos os conteúdos, execute a tarefa programada «Elasticsearch: Index Commerce Content»
Você pode alternar entre o Tokenizer normal e o Tri -Rram (codificado para min = 3, max = 3, tokens = dígito, char, por recomendações elásticas) através do mecanismo de pesquisa de menu -> Administração e status.
Use a função Highlight() para obter um trecho de 150 caracteres de onde a correspondência ocorreu no texto.
SearchResult result = service
. Search ( "bacon" )
. Highlight ( )
. GetContentResults ( ) ; O destaque é, por padrão, ativado em propriedades denominadas MainIntro , MainBody , Attachment e Description .
Marcador padrão é <mark>
Você pode personalizar esse comportamento com as seguintes opções de configuração:
Indexing . Instance . ForType < ArticlePage > ( ) . EnableHighlighting ( x => x . MyField ) ;
Indexing . Instance . SetHighlightFragmentSize ( 42 ) ;
Indexing . Instance . SetHighlightTag ( "blink" ) ; Os resultados podem ser encontrados na propriedade Highlight de cada acerto.
Use a função Bulk para indexar conteúdo personalizado.
Exemplo:
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 ( ) ; Se você preferir um índice personalizado para evitar colisões, isso deve ser fornecido ao indexar e pesquisar:
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.elticsearch usa HttpClient padrão para chamar elasticsearch. Às vezes, é necessário lidar com mensagens enviadas de maneira diferente. Por exemplo, a solicitação de assinatura de serviços em nuvem.
Se você deseja vários HttpMessageHandlers, recomendamos que os encadeemos antes de definir.
Por exemplo:
MessageHandler . Instance . SetMessageHandler ( new AWSHandler ( ) ) ; GetCustomResults Retorna fortemente objetos digitados, em vez de GetContentResults , que retornam apenas IDs.
Objetos personalizados não requerem uma propriedade Id (ou argumento correspondente no CTOR BulkOperation ), mas isso é recomendado se você desejar controle sobre versão e atualizações/exclusões.