Un complemento de búsqueda para Episerver CMS y Comercio
Una versión que admite .NET 6+ está disponible. Póngase en contacto con Kristian Borg para más detalles.
En primer lugar, necesitas crear tu índice. Vaya a la página de administración a través del motor de búsqueda de menú integrado -> Administración y estado, luego haga clic en el botón Create indices .
Esto creará un índice por idioma en su sitio. Si tiene instalado el complemento de comercio, también se crearán índices adicionales para el contenido de catálogo.

Se creará un índice separado para cada idioma activo en su sitio. Si agrega más idiomas más tarde, este proceso debe repetirse.
Puede configurar su configuración programáticamente con Singleton Epinova.ElasticSearch.Core.Conventions.Indexing . Solo haga esto una vez por appdomain, típicamente en un módulo inicializable o aplicación_start ().
Una clase de configuración de muestra:
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 ) ;
}
}Explicación de las diferentes opciones:
ExcludeType : este tipo no se indexará. Las clases e interfaces base también son compatibles. Lo mismo se puede lograr decorando su tipo con ExcludeFromSearchAttribute
ExcludeRoot : este nodo y sus hijos no serán indexados. Típicamente un ID de nodo en el árbol de la página Episerver.
IncludeFileType : Define la extensión de los tipos de archivos que deben indexarse. Consulte también el nodo <files> en la configuración.
ForType : punto de partida para las reglas de indexación de un tipo particular. No hace nada en sí mismo, pero proporciona acceso a las siguientes opciones:
IncludeField : Define las propiedades personalizadas que se indexarán. Por ejemplo, un método de extensión que obtiene datos externos o realiza agregaciones complejas.
EnableSuggestions : ofrezca datos de autosuggest de este tipo, ya sea de todas las propiedades o seleccionadas a través de una expresión de Lambda.
IncludeProperty : el mismo efecto que decorar una propiedad con [Searchable]. Se puede usar si no controla la fuente del modelo.
EnableHighlighting : agregue campos adicionales para resaltar.
StemField : define que una propiedad debe analizarse en el idioma actual. Las propiedades llamadas MainIntro , MainBody y Description siempre se analizarán.
El módulo intenta seguir las mismas convenciones que Episerver, lo que significa que todas las propiedades de string de tipo y XhtmlString se indexarán a menos que se decoren explícitamente con [Searchable(false)] . Se pueden indexar propiedades adicionales decorándolas con [Searchable] o con las convenciones anteriores. Decorar ContentArea con [Searchable] para indexar contenido.
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 ( ) ;Buscas palabras indexadas que comienzan con la frase:
string [ ] suggestions = service . GetSuggestions ( "baco" ) ;Con longitud predeterminada automáticamente (recomendado):
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( )
. GetResults ( ) ;Con longitud específica:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( 4 )
. GetResults ( ) ;Use con cuidado, ya que esto no admite características como 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 ( ) ;Con 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-Query-string-Query.html para la sintaxis.
Uso de propiedades del tipo Epinova.ElasticSearch.Core.Models.Properties.GeoPoint le permite realizar un filtrado basado en GEO.
Modelo de ejemplo:
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 ) ;
} Encuentre puntos dentro de un área cuadrada basada en sus esquinas superior izquierda e inferior derecha.
var topLeft = ( 59.9277542 , 10.7190847 ) ;
var bottomRight = ( 59.8881646 , 10.7983952 ) ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoBoundingBox ( x => x . Location , , topLeft , bottomRight )
. GetResults ( ) ; Encuentre puntos dentro de un círculo dado un punto central y la distancia del radio.
var center = ( 59.9277542 , 10.7190847 ) ;
var radius = "10km" ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoDistance ( x => x . Location , radius , center )
. GetResults ( ) ; Encuentre puntos dentro de un polígono con una cantidad arbitraria de puntos. Los puntos de polígono pueden, por ejemplo, ser los contornos de una ciudad, país u otros 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 ( ) ;Encuentre contenido similar al ID de documento proporcionado
SearchResult result = service
. MoreLikeThis ( "42" )
. GetResults ( ) ;Comercio:
SearchResult result = service
. MoreLikeThis ( "123__CatalogContent" )
. GetResults ( ) ;Parámetros opcionales:
minimumTermFrequency La frecuencia de término mínimo por debajo de la cual los términos se ignorarán desde el documento de entrada. Predeterminado es 1.
maxQueryTerms el número máximo de términos de consulta que se seleccionarán. El aumento de este valor da mayor precisión a expensas de la velocidad de ejecución de consultas. El valor predeterminado a 25.
minimumDocFrequency La frecuencia mínima del documento por debajo de la cual los términos se ignorarán desde el documento de entrada. Predeterminado a 3.
minimumWordLength La longitud mínima de la palabra por debajo de la cual se ignorarán los términos. Predeterminado a 3.
Artilugio:

Si no desea excluir un tipo a nivel mundial, solo puede hacerlo en el contexto de una consulta:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude < SaladPage > ( )
. GetResults ( ) ; Excluir un nodo en el tiempo de consulta:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude ( 42 )
. Exclude ( contentInstance )
. Exclude ( contentReference )
. GetResults ( ) ; La consulta utiliza CurrentCulture como predeterminado. Esto se puede anular:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Language ( CultureInfo . GetCultureInfo ( "en" ) )
. GetResults ( ) ; Use los métodos From() y Size() para la paginación, o los alias Skip() y Take() ::
SearchResult result = service
. Search < CoursePage > ( "foo" )
. From ( 10 )
. Size ( 20 )
. GetResults ( ) ; var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID ) ; Las facetas se crearán a partir de los filtros aplicados actualmente y no del resultado completo.
var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID , usePostFilter : false ) ; Un valor:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilter ) ;Un valor, por método de extensión:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . GetDepartmentID ( ) , selectedFilter ) ;Valores múltiples:
string [ ] selectedFilters = Request . Querstring [ "filters" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilters ) ;Uso y operador para filtrar en todos los 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
}
} Se pueden agrupar múltiples filtros para formar consultas más complejas.
En el siguiente ejemplo, lo siguiente debe ser cierto para dar una coincidencia:
Sizes es "xs" o "xl"
ProductCategory es "pants"
Brand es "levis" o "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 ciertos valores, use FilterMustNot
var query = service
. Search < PageData > ( "foo" )
. FilterMustNot ( x => x . Title , "bar" ) ; Para filtrar el ACL de los usuarios actuales, use FilterByACL
var query = service
. Search < PageData > ( "foo" )
. FilterByACL ( ) ; EPiServer.Security.PrincipalInfo.Current se utilizará de forma predeterminada, pero se puede suministrar un PrincipalInfo personalizado si es necesario.
Para buscar dentro de un rango de valores dado, use la función Range .
Los tipos compatibles son DateTime , double , decimal y long (incluida la conversión 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 ( ) ;El argumento menos de lo menos opcional.
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyNumber , 10 ) // Returns anything above 10
. GetResults ( ) ; Para buscar un intervalo dentro de otro intervalo, su propiedad debe ser de tipo Epinova.ElasticSearch.Core.Models.Properties.IntegerRange .
Actualmente int es el único tipo compatible.
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);
}
Si una propiedad es de tipo IDictionary<string, object> y marcada [Searchable] , se indexará como un object en ElasticSearch.
Esto es útil en escenarios en los que tiene datos dinámicos de valor clave que deben indexarse, como los sistemas PIM.
Enfoque de propiedad estándar:
public class ProductPage
{
[Searchable]
public IDictionary<string, object> Metadata { get; set; }
}
Los datos en sí no se devolverán, pero puede consultarlos y hacer facetas:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. InField ( x => x . Metadata + ".SomeKey" )
. FacetsFor ( x => x . Metadata + ".SomeKey" )
. GetResults ( ) ;Propiedades personalizadas:
public static class SearchConfig
{
public static void Init ( )
{
Epinova . ElasticSearch . Core . Conventions . Indexing . Instance
. ForType < ProductPage > ( ) . IncludeField ( "Metadata" , x => x . GetPimDataDictionary ( ) ) ;
}
}Este enfoque devuelve los datos:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. GetResults ( ) ;
var hit = result . Hits . First ( ) ;
var dict = hit . Custom [ "Metadata" ] as IDictionary < string , object > Las propiedades se pueden aumentar decorándolas con el atributo Boost :
[ Boost ( 13 ) ]
public string Title { get ; set ; }... o en la hora de la consulta:
SearchResult result = service
. Search ( "bacon" )
. Boost ( x => x . MyProp , 3 )
. GetResults ( ) ;Un tipo puede recibir un impulso positivo o 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 ( ) ;También puede aumentar los golpes dependiendo de su ubicación en el árbol de la página:
SearchResult result = service
. Search ( "bacon" )
. BoostByAncestor ( new ContentReference ( 42 ) , 2 )
. GetResults ( ) ; Las propiedades de fecha se pueden obtener más bajas según su valor, utilizando la función Decay . De esta manera, puede promover artículos más nuevos sobre los más antiguos.
SearchResult result = service
. Search ( "bacon" )
. Decay ( x => x . StartPublish , TimeSpan . FromDays ( 7 ) )
. GetResults ( ) ;Cada vez que ocurre el intervalo de fecha (segundo argumento), se restan 0.5 puntos de la puntuación. En el ejemplo anterior, se restarán 0.5 puntos después de 7 días, 1 puntos después de 14 días, y así sucesivamente.
Use .UseBestBets() en su consulta para obtener un contenido seleccionado más alto de lo normal.
SearchResult result = service
. Search ( "bacon" )
. UseBestBets ( )
. GetResults ( ) ;Las mejores apuestas se pueden administrar a través del motor de búsqueda de menú integrado -> Las mejores apuestas.

Se pueden recopilar estadísticas simples utilizando la función .Track() . Esto rastreará el número de veces que se consulta un término y si devuelve algún golpe.
SearchResult result = service
. Search ( "bacon" )
. Track ( )
. GetResults ( ) ; 
Nota: Si su cadena de conexión no se llama EPiServerDB debe suministrar su nombre en el seguimiento de la contratación trackingConnectionStringName .
La derecha se aplica por defecto a todas las propiedades del tipo XhtmlString , o las llamadas MainIntro o MainBody .
El lenguaje se basa en el lenguaje de contenido. Otras propiedades de string de tipo se pueden evitar decorándolas con el Stem -attribido:
[ Stem ]
public string Title { get ; set ; } Para enumerar el contenido de cierto tipo sin ninguna puntuación o análisis, use la función Get . Esto se puede usar junto con SortBy para listados simples.
SearchResult result = service
. StartFrom ( somePageLink )
. Get < ArticlePage > ( )
. GetResults ( ) ; La clasificación normalmente es realizada por Elasticsearch basada en la puntuación de cada partido. La clasificación manual solo debe usarse en escenarios donde la puntuación no es relevante, por ejemplo, cuando se usa la función Get mencionada anteriormente.
SearchResult result = service
. Get < ArticlePage > ( )
. SortBy ( p => p . StartPublish )
. ThenBy ( p => p . Foo )
. ThenBy ( p => p . Bar )
. GetResults ( ) ; Al clasificar en un GeOpoint, hay un argumento obligatorio más; compareTo . Los elementos se compararán con estas coordenadas, y las distancias resultantes se utilizarán como valores de clasificación.
Para el control absoluto, puede usar un script para ordenar documentos. Tenga en cuenta que esto podría afectar el rendimiento.
Ejemplo de clasificación basada en una cierta marca de tiempo:
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 la sintaxis de secuencias de comandos.
Se incluye un filtro de tejas que puede sugerir palabras similares que se encuentran en el índice al buscar palabras mal escritas.
Cualquier sugerencia encontrada se incluirá en la propiedad DidYouMean del resultado de la búsqueda:
SearchResult result = service
. Search ( "alloi" )
. GetResults ( ) ;
string [ ] didYouMean = result . DidYouMean ; // [ "alloy", "all" ] Cualquier propiedad que deba actuar como fuente de sugerencias debe marcarse con [DidYouMeanSource] .
public class StandardPage : SitePageData
{
[ DidYouMeanSource ]
public virtual XhtmlString MainBody { get ; set ; }
} Los resultados devueltos por GetResults() no tienen ningún conocimiento de Episerver. Use la función GetContentResults() en un contexto de Episerver.
Esto aplicará automáticamente los filtros FilterAccess , FilterPublished y FilterTemplate .
IEnumerable < IContent > content = service
. Search < CoursePage > ( text )
. GetContentResults ( ) ; Si desea utilizar alguno de los proveedores incluidos cuando busque en modo de edición, vaya a CMS -> Admin -> config -> Configuración de la herramienta -> Configuración de búsqueda.
Marque a los proveedores y arrastiéndolos a la parte superior de la lista.
Los sinónimos se pueden administrar desde el motor de búsqueda del menú -> Sinónimos.
El contenido se volverá a ingresar automáticamente al realizar operaciones comunes como la publicación, la mudanza y la eliminación.
Para hacer una indexación inicial de todos los contenidos, ejecute la tarea programada «Elasticsearch: índice CMS Content»
La reintegración también se puede activar manualmente en contenido individual a través del Menu Tools-Menu:

... o a través del menú contextual en el árbol de la página:

Los resultados devueltos por GetResults() no tienen ningún conocimiento de Episerver. Use la función GetCatalogResults() en un contexto de comercio Episerver. Esto elegirá automáticamente el índice correcto y aplicará los filtros FilterAccess , FilterPublished y FilterTemplate .
IEnumerable < ProductContent > content = service
. Search < ProductContent > ( text )
. GetCatalogResults ( ) ; El contenido se volverá a ingresar automáticamente al realizar operaciones comunes como la publicación, la mudanza y la eliminación.
Para hacer una indexación inicial de todos los contenidos, ejecute la tarea programada «Elasticsearch: Index Commerce Content»
Puede cambiar entre tokenizer normal y tri -gramo (codificado por minuto a min = 3, max = 3, tokens = dígitos, char, recomendaciones por elásticas) a través del motor de búsqueda del menú -> Administración y estado.
Use la función Highlight() para obtener un extracto de 150 caracteres desde donde ocurrió la coincidencia en el texto.
SearchResult result = service
. Search ( "bacon" )
. Highlight ( )
. GetContentResults ( ) ; El resaltado está habilitado de forma predeterminada en propiedades llamadas MainIntro , MainBody , Attachment y Description .
El marcador predeterminado es <mark>
Puede personalizar este comportamiento con las siguientes opciones de configuración:
Indexing . Instance . ForType < ArticlePage > ( ) . EnableHighlighting ( x => x . MyField ) ;
Indexing . Instance . SetHighlightFragmentSize ( 42 ) ;
Indexing . Instance . SetHighlightTag ( "blink" ) ; Los resultados se pueden encontrar en la propiedad Highlight de cada golpe.
Use la función Bulk para indexar contenido personalizado.
Ejemplo:
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 ( ) ; Si prefiere un índice personalizado para evitar colisiones de EG, esto debe suministrarse al indexar y buscar:
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 utiliza HttpClient estándar para llamar a Elasticsearch. A veces es necesario manejar los mensajes enviados de manera diferente. Por ejemplo, firmar solicitud de servicios en la nube.
Si desea varios httpmessagehandlers, le recomendamos encallarlos antes de configurar.
Por ejemplo:
MessageHandler . Instance . SetMessageHandler ( new AWSHandler ( ) ) ; GetCustomResults devuelve objetos fuertemente tipados en lugar de GetContentResults , que solo devuelve las ID.
Los objetos personalizados no requieren una propiedad Id (o argumento correspondiente en el CTOR BulkOperation ), pero esto se recomienda si desea controlar el versiones y las actualizaciones/deleciones.