Un plugin de recherche pour les CMS et le commerce épisserver
Une version prenant en charge .NET 6+ est disponible. Contactez Kristian Borg pour plus de détails.
Tout d'abord, vous avez besoin pour créer votre index. Accédez à la page d'administration via le moteur de recherche de menu intégré -> Administration et statut, puis cliquez sur le bouton Create indices .
Cela créera un index par langue sur votre site. Si l'addon de commerce installé, des indices supplémentaires pour le contenu du catalogue seront également créés.

Un index distinct sera créé pour chaque langue active de votre site. Si vous ajoutez plus de langues plus tard, ce processus doit être répété.
Vous pouvez configurer votre configuration par programme avec le Singleton Epinova.ElasticSearch.Core.Conventions.Indexing . Ne faites cela qu'une seule fois par appdomain, généralement dans un module initialisable ou application_start ().
Un exemple de classe de configuration:
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 ) ;
}
}Explication des différentes options:
ExcludeType : ce type ne sera pas indexé. Les classes de base et les interfaces sont également prises en charge. Il en ExcludeFromSearchAttribute de même
ExcludeRoot : Ce nœud et ses enfants ne seront pas indexés. Généralement, un nœud-id dans le panc-tree Episerver.
IncludeFileType : définit l'extension pour les types de fichiers qui doivent être indexés. Voir également le nœud <files> dans la configuration.
ForType : point de départ pour l'indexation des règles d'un type particulier. Ne fait rien en soi, mais donne accès aux options suivantes:
IncludeField : définit les propriétés personnalisées à indexer. Par exemple, une méthode d'extension qui récupère des données externes ou effectue des agrégations complexes.
EnableSuggestions : offrez des données Autosugest à partir de ce type, soit à partir de toutes les propriétés, soit des propriétés sélectionnées via une expression de lambda.
IncludeProperty : même effet que la décoration d'une propriété avec [consultable]. Peut être utilisé si vous ne contrôlez pas la source du modèle.
EnableHighlighting : ajoutez des champs supplémentaires à mettre en évidence.
StemField : définit qu'une propriété doit être analysée dans la langue actuelle. Les propriétés nommées MainIntro , MainBody et Description seront toujours analysées.
Le module essaie de suivre les mêmes conventions que Episerver, ce qui signifie que toutes les propriétés de string de type et XhtmlString seront indexées à moins que, explicitement décorées de [Searchable(false)] . Des propriétés supplémentaires peuvent être indexées en les décorant avec [Searchable] ou avec les conventions ci-dessus. Décorez ContentArea avec [Searchable] pour indexer le contenu.
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 ( ) ;Recherche des mots indexés en commençant par la phrase:
string [ ] suggestions = service . GetSuggestions ( "baco" ) ;Avec la longueur par défaut automatique (recommandée):
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( )
. GetResults ( ) ;Avec une longueur spécifique:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Fuzzy ( 4 )
. GetResults ( ) ;Utilisez avec soin car cela ne prend pas en charge les fonctionnalités telles que la solution.
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 ( ) ;Avec des opérateurs limités:
SearchResult result = service
. SimpleQuerystringSearch < ArticlePage > ( " " bacon melt " sandwi*" ,
defaultOperator : Operator . And ,
allowedOperators :
SimpleQuerystringOperators . Prefix |
SimpleQuerystringOperators . Phrase ) ;
)
. GetResults ( ) ;Voir https://www.elastic.co/guide/en/elasticsearch/reference/current/Query-dsl-simple-Quey-string-Query.html pour la syntaxe.
Utilisation des propriétés du type Epinova.ElasticSearch.Core.Models.Properties.GeoPoint vous permet d'effectuer un filtrage géo-géo.
Exemple de modèle:
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 ) ;
} Trouvez des points à l'intérieur d'une zone carrée en fonction de ses coins supérieur gauche et inférieur à droite.
var topLeft = ( 59.9277542 , 10.7190847 ) ;
var bottomRight = ( 59.8881646 , 10.7983952 ) ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoBoundingBox ( x => x . Location , , topLeft , bottomRight )
. GetResults ( ) ; Trouvez des points à l'intérieur d'un cercle étant donné un point central et la distance du rayon.
var center = ( 59.9277542 , 10.7190847 ) ;
var radius = "10km" ;
SearchResult result = service
. Get < OfficePage > ( )
. FilterGeoDistance ( x => x . Location , radius , center )
. GetResults ( ) ; Trouvez des points à l'intérieur d'un polygone avec une quantité arbitraire de points. Les points de polygone peuvent par exemple être les contours d'une ville, d'un pays ou d'autres types de zones.
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 ( ) ;Trouver un contenu similaire au document-ID fourni
SearchResult result = service
. MoreLikeThis ( "42" )
. GetResults ( ) ;Commerce:
SearchResult result = service
. MoreLikeThis ( "123__CatalogContent" )
. GetResults ( ) ;Paramètres facultatifs:
minimumTermFrequency La fréquence de terme minimale en dessous de laquelle les termes seront ignorés à partir du document d'entrée. Par défaut est 1.
maxQueryTerms le nombre maximum de termes de requête qui seront sélectionnés. L'augmentation de cette valeur donne une plus grande précision au détriment de la vitesse d'exécution de la requête. Par défaut à 25.
minimumDocFrequency La fréquence de document minimum en dessous de laquelle les termes seront ignorés à partir du document d'entrée. Par défaut est 3.
minimumWordLength La longueur de mot minimum en dessous de laquelle les termes seront ignorés. Par défaut est 3.
Gadget:

Si vous ne souhaitez pas exclure un type à l'échelle mondiale, vous ne pouvez le faire que dans le contexte d'une requête:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude < SaladPage > ( )
. GetResults ( ) ; Exclure un nœud au moment de la requête:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Exclude ( 42 )
. Exclude ( contentInstance )
. Exclude ( contentReference )
. GetResults ( ) ; La requête utilise CurrentCulture par défaut. Cela peut être remplacé:
SearchResult result = service
. Search < ArticlePage > ( "bacon" )
. Language ( CultureInfo . GetCultureInfo ( "en" ) )
. GetResults ( ) ; Utilisez les méthodes From() et Size() pour la pagination, ou les alias Skip() et Take() :
SearchResult result = service
. Search < CoursePage > ( "foo" )
. From ( 10 )
. Size ( 20 )
. GetResults ( ) ; var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID ) ; Des facettes seront créées à partir des filtres actuellement appliqués et non du résultat entier.
var query = service
. Search < CoursePage > ( "foo" )
. FacetsFor ( x => x . DepartmentID , usePostFilter : false ) ; Une valeur:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilter ) ;Une valeur, par méthode d'extension:
string selectedFilter = Request . Querstring [ "filter" ] ;
query = query . Filter ( p => p . GetDepartmentID ( ) , selectedFilter ) ;Valeurs multiples:
string [ ] selectedFilters = Request . Querstring [ "filters" ] ;
query = query . Filter ( p => p . DepartmentID , selectedFilters ) ;Utilisation et opérateur pour filtrer tous les filtres:
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
}
} Plusieurs filtres peuvent être regroupés pour former des requêtes plus complexes.
Dans l'exemple ci-dessous, ce qui suit doit être vrai pour donner une correspondance:
Sizes sont "xs" ou "xl"
ProductCategory est "pants"
Brand est soit "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" } )
) ; Pour filtrer certaines valeurs, utilisez FilterMustNot
var query = service
. Search < PageData > ( "foo" )
. FilterMustNot ( x => x . Title , "bar" ) ; Pour filtrer les utilisateurs actuels ACL, utilisez FilterByACL
var query = service
. Search < PageData > ( "foo" )
. FilterByACL ( ) ; EPiServer.Security.PrincipalInfo.Current sera utilisé par défaut, mais un PrincipalInfo personnalisé peut être fourni si nécessaire.
Pour rechercher dans une plage de valeurs donnée, utilisez la fonction Range .
Les types pris en charge sont DateTime , double , decimal et long (y compris la conversion implicite 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 ( ) ;L'argument moins que est facultatif.
SearchResult result = service
. Search ( "bacon" )
. Range ( x => x . MyNumber , 10 ) // Returns anything above 10
. GetResults ( ) ; Pour rechercher un intervalle à l'intérieur d'un autre intervalle, votre propriété doit être de type Epinova.ElasticSearch.Core.Models.Properties.IntegerRange .
Actuellement int est le seul type pris en charge.
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 une propriété est de type IDictionary<string, object> et marqué [Searchable] , il sera indexé en tant object dans Elasticsearch.
Ceci est utile dans les scénarios où vous disposez de données dynamiques de valeur clé qui doivent être indexées, comme à partir de PIM-Systems.
Approche de la propriété standard:
public class ProductPage
{
[Searchable]
public IDictionary<string, object> Metadata { get; set; }
}
Les données elle-même ne seront pas retournées, mais vous pouvez les interroger et faire des facettes:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. InField ( x => x . Metadata + ".SomeKey" )
. FacetsFor ( x => x . Metadata + ".SomeKey" )
. GetResults ( ) ;Propriétés personnalisées:
public static class SearchConfig
{
public static void Init ( )
{
Epinova . ElasticSearch . Core . Conventions . Indexing . Instance
. ForType < ProductPage > ( ) . IncludeField ( "Metadata" , x => x . GetPimDataDictionary ( ) ) ;
}
}Cette approche renvoie les données:
SearchResult result = service
. Search < ProductPage > ( "bacon" )
. GetResults ( ) ;
var hit = result . Hits . First ( ) ;
var dict = hit . Custom [ "Metadata" ] as IDictionary < string , object > Les propriétés peuvent être stimulées en les décorant avec l'attribut Boost :
[ Boost ( 13 ) ]
public string Title { get ; set ; }… Ou au moment de la requête:
SearchResult result = service
. Search ( "bacon" )
. Boost ( x => x . MyProp , 3 )
. GetResults ( ) ;Un type peut être donné un coup de pouce positif ou négatif:
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 ( ) ;Vous pouvez également augmenter les coups en fonction de leur emplacement dans l'arbre de page:
SearchResult result = service
. Search ( "bacon" )
. BoostByAncestor ( new ContentReference ( 42 ) , 2 )
. GetResults ( ) ; Les propriétés de date peuvent être notées plus bas en fonction de leur valeur, en utilisant la fonction de Decay . De cette façon, vous pouvez promouvoir des articles plus récents sur des articles plus âgés.
SearchResult result = service
. Search ( "bacon" )
. Decay ( x => x . StartPublish , TimeSpan . FromDays ( 7 ) )
. GetResults ( ) ;Chaque fois que l'intervalle de date (2ème argument) se produit, 0,5 point sera soustrait du score. Dans l'exemple ci-dessus, 0,5 point sera soustrait après 7 jours, 1 point après 14 jours, etc.
Utilisez .UseBestBets() sur votre requête pour noter le contenu sélectionné plus haut que la normale.
SearchResult result = service
. Search ( "bacon" )
. UseBestBets ( )
. GetResults ( ) ;Les meilleurs paris peuvent être administrés via le moteur de recherche de menu intégré -> Meilleurs paris.

Des statistiques simples peuvent être collectées à l'aide de la fonction .Track() . Cela suivra le nombre de fois où un terme est interrogé et s'il a renvoyé des coups sûrs.
SearchResult result = service
. Search ( "bacon" )
. Track ( )
. GetResults ( ) ; 
Remarque: Si votre chaîne de connexion n'est pas nommée EPiServerDB vous devez fournir son nom dans le trackingConnectionStringName -Configuration voir l'installation
La tige est par défaut appliquée à toutes les propriétés du type XhtmlString , ou de celles nommées MainIntro ou MainBody .
La langue est basée sur la langue de contenu. D'autres propriétés de string de type peuvent être tirées en les décorant avec la Stem -ttribute:
[ Stem ]
public string Title { get ; set ; } Pour répertorier le contenu d'un certain type sans aucune notation ou analyse, utilisez la fonction Get . Cela peut être utilisé conjointement avec SortBy pour des listes simples.
SearchResult result = service
. StartFrom ( somePageLink )
. Get < ArticlePage > ( )
. GetResults ( ) ; Le tri est normalement effectué par Elasticsearch en fonction du score de chaque match. Le tri manuel ne doit être utilisé que dans les scénarios où la notation n'est pas pertinente, par exemple lors de l'utilisation de la fonction Get précédemment mentionnée.
SearchResult result = service
. Get < ArticlePage > ( )
. SortBy ( p => p . StartPublish )
. ThenBy ( p => p . Foo )
. ThenBy ( p => p . Bar )
. GetResults ( ) ; Lors du tri sur un GeoPoint, il y a un argument obligatoire de plus; compareTo . Les éléments seront comparés à ces coordonnées, et les distances résultantes seront utilisées comme valeurs de tri.
Pour un contrôle absolu, vous pouvez utiliser un script pour trier les documents. Notez que cela pourrait affecter les performances.
Exemple de tri basé sur un certain horodat:
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 ( ) ;Voir https://www.elastic.co/guide/en/elasticsearch/painless/current/index.html pour la syntaxe de script.
Un filtre de bardeaux est inclus qui peut suggérer des mots similaires trouvés dans l'index lors de la recherche de mots mal orthographiés.
Toutes les suggestions trouvées seront incluses dans la propriété DidYouMean du résultat de la recherche:
SearchResult result = service
. Search ( "alloi" )
. GetResults ( ) ;
string [ ] didYouMean = result . DidYouMean ; // [ "alloy", "all" ] Toutes les propriétés qui devraient agir comme source de suggestions doivent être marquées de [DidYouMeanSource] .
public class StandardPage : SitePageData
{
[ DidYouMeanSource ]
public virtual XhtmlString MainBody { get ; set ; }
} Les résultats renvoyés par GetResults() n'ont aucune connaissance de Episerver. Utilisez la fonction GetContentResults() dans un contexte épisserver.
Cela appliquera automatiquement l' FilterAccess des filtres, FilterPublished et FilterTemplate .
IEnumerable < IContent > content = service
. Search < CoursePage > ( text )
. GetContentResults ( ) ; Si vous souhaitez utiliser l'un des fournisseurs inclus lors de la recherche en mode édition, accédez à CMS -> admin -> config -> Paramètres d'outil -> Configuration de recherche.
Cochez les fournisseurs et faites-les glisser en haut de la liste.
Les synonymes peuvent être administrés à partir du moteur de recherche de menu -> Synonymes.
Le contenu sera automatiquement réindexé lors de l'exécution d'opérations communes telles que la publication, le déménagement et la suppression.
Pour faire une indexation initiale de tous les contenus, exécutez la tâche planifiée «Elasticsearch: Index CMS Contenu»
La réindexation peut également être déclenchée manuellement sur le contenu individuel via les outils-menu:

… Ou via le menu contextuel de l'arbre de page:

Les résultats renvoyés par GetResults() n'ont aucune connaissance de Episerver. Utilisez la fonction GetCatalogResults() dans un contexte de commerce épisserver. Cela choisira automatiquement l'index correct et appliquera l' FilterAccess filtre Filtres, FilterPublished et FilterTemplate .
IEnumerable < ProductContent > content = service
. Search < ProductContent > ( text )
. GetCatalogResults ( ) ; Le contenu sera automatiquement réindexé lors de l'exécution d'opérations communes telles que la publication, le déménagement et la suppression.
Pour faire une indexation initiale de tous les contenus, exécutez la tâche planifiée «Elasticsearch: Index Commerce Contenu»
Vous pouvez basculer entre le tokenzer normal et Tri-Gram (codé en dur à min = 3, max = 3, jetons = chiffre, char, par recommandations élastiques) via le moteur de recherche de menu -> Administration et statut.
Utilisez la fonction Highlight() pour obtenir un extrait de 150 caractères d'où la correspondance s'est produite dans le texte.
SearchResult result = service
. Search ( "bacon" )
. Highlight ( )
. GetContentResults ( ) ; La mise en surbrillance est par défaut activée sur les propriétés nommées MainIntro , MainBody , Attachment et Description .
Le marqueur par défaut est <mark>
Vous pouvez personnaliser ce comportement avec les options de configuration suivantes:
Indexing . Instance . ForType < ArticlePage > ( ) . EnableHighlighting ( x => x . MyField ) ;
Indexing . Instance . SetHighlightFragmentSize ( 42 ) ;
Indexing . Instance . SetHighlightTag ( "blink" ) ; Les résultats peuvent être trouvés dans la propriété Highlight de chaque coup.
Utilisez la fonction Bulk pour indexer le contenu personnalisé.
Exemple:
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 vous préférez un index personnalisé pour éviter les collisions par exemple, cela doit être fourni lors de l'indexation et de la recherche:
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 utilise HttpClient standard pour appeler Elasticsearch. Parfois, il est nécessaire de gérer les messages envoyés différemment. Par exemple, signature de la demande de services cloud.
Si vous voulez plusieurs HttpMessageHandlers, nous vous recommandons de les enchaîner avant de définir.
Par exemple:
MessageHandler . Instance . SetMessageHandler ( new AWSHandler ( ) ) ; GetCustomResults renvoie des objets fortement typés par opposition à GetContentResults , qui ne renvoie que des ID.
Les objets personnalisés ne nécessitent pas de propriété Id (ou un argument correspondant dans le CTOR BulkOperation ), mais cela est recommandé si vous souhaitez contrôler le versioning et les mises à jour / délétions.