<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[.NET - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[.NET - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/fr/search-labs/blog/category/dot-net-programming</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/dot-net-programming</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/dot-net-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 03:04:59 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Comment utiliser le connecteur Elasticsearch Vector Store pour Microsoft Semantic Kernel pour le développement d'agents d'intelligence artificielle ?]]></title>
    <description><![CDATA[Microsoft Semantic Kernel est un kit de développement léger et open-source qui vous permet de créer facilement des agents d'intelligence artificielle et d'intégrer les derniers modèles d'intelligence artificielle dans votre base de code C#, Python ou Java. Avec la sortie du connecteur Semantic Kernel Elasticsearch Vector Store, les développeurs qui utilisent Semantic Kernel pour créer des agents d'intelligence artificielle peuvent désormais utiliser Elasticsearch comme un magasin vectoriel d'entreprise évolutif tout en continuant à utiliser les abstractions de Semantic Kernel.]]></description>
    <content:encoded><![CDATA[<p>En collaboration avec l'équipe <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel,</a> nous annonçons la disponibilité du <a href="https://github.com/elastic/semantic-kernel-net/">connecteur Semantic Kernel Elasticsearch Vector</a> Store, pour les utilisateurs <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel (.NET).</a> Semantic Kernel simplifie la création d'agents d'intelligence artificielle de niveau professionnel, notamment en permettant d'améliorer les grands modèles de langage (LLM) grâce à des réponses plus pertinentes et fondées sur des données provenant d'un magasin de vecteurs. Semantic Kernel fournit une couche d'abstraction transparente pour interagir avec les magasins vectoriels tels qu'Elasticsearch, offrant des fonctionnalités essentielles telles que la création, l'énumération et la suppression de collections d'enregistrements et le téléchargement, l'extraction et la suppression d'enregistrements individuels.</p><p>Le <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">connecteur Semantic Kernel Elasticsearch Vector Store, prêt à l'emploi, prend</a> en charge les <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">abstractions du Semantic Kernel</a> vector store, ce qui permet aux développeurs de brancher très facilement Elasticsearch en tant que magasin vectoriel lors de la création d'agents d'intelligence artificielle.</p><p>Elasticsearch est solidement ancré dans la communauté des logiciels libres et a récemment adopté la <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">licence AGPL</a>. Associés au noyau sémantique Microsoft open-source, ces outils offrent une solution puissante, prête pour l'entreprise. Vous pouvez commencer localement en démarrant Elasticsearch en quelques minutes en exécutant cette commande <code>curl -fsSL https://elastic.co/start-local | sh </code>(voir <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> pour plus de détails) et évoluer vers des versions <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">hébergées dans le nuage</a> ou <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">auto-hébergées</a> tout en mettant en production vos agents d'IA.</p><p>Dans ce blog, nous verrons comment utiliser le <a href="https://github.com/elastic/semantic-kernel-net/">connecteur Semantic Kernel Elasticsearch Vector Store</a> lors de l'utilisation de Semantic Kernel. Une version Python du connecteur sera disponible à l'avenir.</p><h2>Scénario de haut niveau : Construire une application RAG avec Semantic Kernel &amp; Elasticsearch</h2><p>Dans la section suivante, nous présentons un exemple. À un niveau élevé, nous construisons une application RAG (Retrieval Augmented Generation) qui prend la question d'un utilisateur en entrée et renvoie une réponse. Nous utiliserons Azure OpenAI<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">(un LLM local</a> peut également être utilisé) comme LLM, Elasticsearch comme magasin de vecteurs et Semantic Kernel (.net) comme cadre pour relier tous les composants ensemble.</p><p>Si vous n'êtes pas familier avec les architectures RAG, vous pouvez vous familiariser rapidement avec cet article <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">: https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag</a>.</p><p>La réponse est générée par le LLM qui est alimenté par le contexte, pertinent pour la question, récupéré à partir du vectorstore Elasticsearch. La réponse inclut également la source qui a été utilisée comme contexte par le LLM.</p><h3>Exemple de GCR</h3><p>Dans cet exemple spécifique, nous créons une application qui permet aux utilisateurs de poser des questions sur les hôtels stockés dans une base de données interne. L'utilisateur peut par exemple rechercher un hôtel spécifique, en fonction de différents critères, ou demander une liste d'hôtels.</p><p>Pour la base de données d'exemple, nous avons généré une <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">liste d'hôtels</a> contenant 100 entrées. La taille de l'échantillon est volontairement réduite pour vous permettre d'essayer la démo du connecteur aussi facilement que possible. Dans une application réelle, le connecteur Elasticsearch montrerait ses avantages par rapport à d'autres options, telles que l'implémentation du magasin vectoriel `InMemory`, en particulier lorsque l'on travaille avec de très grandes quantités de données.</p><p>L'application de démonstration complète se trouve dans le <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">référentiel du</a> connecteur Elasticsearch vector store.</p><p>Commençons par ajouter les paquets NuGet et les directives nécessaires à notre projet :</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>Nous pouvons maintenant créer notre modèle de données et le doter d'attributs spécifiques au Semantic Kernel pour définir le schéma du modèle de stockage et quelques indications pour la recherche textuelle :</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>Les attributs du schéma du modèle de stockage (`VectorStore*`) sont les plus pertinents pour l'utilisation réelle du connecteur Elasticsearch Vector Store, à savoir :</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> pour marquer une propriété d'une classe d'enregistrement comme étant la clé sous laquelle l'enregistrement est stocké dans un magasin vectoriel.</p></li><li><p><code>VectorStoreRecordData</code> pour marquer une propriété d'une classe d'enregistrement comme "data".</p></li><li><p><code>VectorStoreRecordVector</code> pour marquer une propriété d'une classe d'enregistrement en tant que vecteur.</p></li></ul><p>Tous ces attributs acceptent divers paramètres facultatifs qui peuvent être utilisés pour personnaliser davantage le modèle de stockage. Dans le cas de <code>VectorStoreRecordKey </code>, par exemple, il est possible de spécifier une fonction de distance différente ou un type d'indice différent.</p><p>Les attributs de recherche de texte (<code>TextSearch*</code>) seront importants dans la dernière étape de cet exemple. Nous y reviendrons plus tard.</p><p>Dans l'étape suivante, nous initialisons le moteur du noyau sémantique et obtenons des références aux services de base. Dans une application réelle, l' <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">injection de dépendances</a> devrait être utilisée au lieu d'accéder directement à la collection de services. La même chose s'applique à la configuration et aux secrets codés en dur, qui doivent être lus à l'aide d'un <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">fournisseur de configuration</a>:</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>Le service <code>vectorStoreCollection</code> peut maintenant être utilisé pour créer la collection et ingérer quelques <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">enregistrements de démonstration :</a></p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>Cela montre comment Semantic Kernel réduit l'utilisation d'un magasin de vecteurs, avec toute sa complexité, à quelques simples appels de méthodes.</p><p>Sous le capot, un nouvel index est créé dans Elasticsearch et tous les mappages de propriétés nécessaires sont créés. Notre ensemble de données est ensuite intégré de manière totalement transparente dans le modèle de stockage et finalement stocké dans l'index. Voici à quoi ressemblent les mappings dans Elasticsearch.</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p>Le site <code>embeddings.GenerateEmbeddingsAsync()</code> appelle de manière transparente le service Azure AI Embeddings Generation configuré.</p><p>La dernière étape de cette démonstration est encore plus magique.</p><p>Avec un seul appel à <code>InvokePromptAsync</code>, toutes les opérations suivantes sont effectuées lorsque l'utilisateur pose une question sur les données :</p><p>1. L'intégration de la question de l'utilisateur est générée.</p><p>2. Le magasin de vecteurs est parcouru à la recherche d'entrées pertinentes</p><p>3. Les résultats de la requête sont insérés dans un modèle d'invite</p><p>4. La requête proprement dite, sous la forme de l'invite finale, est envoyée au service d'achèvement de chat de l'IA.</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>Vous vous souvenez des attributs <code>TextSearch*</code> que nous avons définis précédemment dans notre modèle de données ? Ces attributs nous permettent d'utiliser les espaces réservés correspondants dans notre modèle d'invite, qui sont automatiquement remplis avec les informations provenant de nos entrées dans le magasin de vecteurs.</p><p>La réponse finale à notre question "Veuillez m'indiquer tous les hôtels qui disposent d'un bar sur le toit." est la suivante :</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>La réponse se réfère correctement à l'entrée suivante dans notre fichier hotels.csv</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>Cet exemple montre très bien comment l'utilisation de Microsoft Semantic Kernel permet une réduction significative de la complexité grâce à ses abstractions bien pensées, tout en permettant un très haut niveau de flexibilité. En changeant une seule ligne de code, par exemple, le magasin de vecteurs ou les services d'intelligence artificielle utilisés peuvent être remplacés sans qu'il soit nécessaire de remanier une autre partie du code.</p><p>Dans le même temps, le cadre fournit un ensemble considérable de fonctionnalités de haut niveau, telles que la fonction `InvokePrompt`, ou le système de plugin de modèle ou de recherche.</p><p>L'application de démonstration complète se trouve dans le référentiel du connecteur Elasticsearch vector store.</p><h2>Quelles sont les autres possibilités offertes par Elasticsearch ?</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Nouveau mappage semantic_text d'Elasticsearch : Simplifier la recherche sémantique</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Classement sémantique dans Elasticsearch à l'aide d'extracteurs</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">Techniques avancées de RAG partie 1 : Traitement des données</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Techniques avancées de RAG, partie 2 : Requêtes et tests</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Construire RAG avec Llama 3 open-source et Elastic</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">Un tutoriel sur la construction d'un agent local utilisant LangGraph, LLaMA3 et Elasticsearch vector store à partir de zéro</a></p></li></ul><h2>Elasticsearch &amp; Semantic Kernel : Quelle est la prochaine étape ?</h2><ul><li><p>Nous avons montré comment le magasin vectoriel Elasticsearch peut être facilement intégré au Semantic Kernel lors de la construction d'applications GenAI en .NET. Restez à l'écoute pour une prochaine intégration de Python.</p></li><li><p>Comme Semantic Kernel construit des abstractions pour des fonctions de recherche avancées telles que la <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a>, la connexion Elasticsearch permettra aux développeurs .NET de les mettre en œuvre facilement tout en utilisant Semantic Kernel.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Création d'une application de recherche avec Blazor et Elasticsearch]]></title>
    <description><![CDATA[Apprenez à construire une application de recherche en utilisant Blazor et Elasticsearch, et à utiliser le client Elasticsearch .NET pour la recherche hybride.]]></description>
    <content:encoded><![CDATA[<p>Dans cet article, vous apprendrez à tirer parti de vos compétences en C# pour créer une application de recherche à l'aide de Blazor et d'Elasticsearch. Nous allons utiliser le client <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a> pour exécuter des requêtes de recherche <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">plein texte</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">sémantique</a> et <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">hybride</a>.</p><p><strong>NOTE</strong> Si vous êtes familier avec l'ancienne version du client Elasticsearch C# <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">NEST</a>, lisez cet <a href="https://www.elastic.co/search-labs/blog/net-client-evolution">article de blog</a> sur la dépréciation du client NEST et les nouvelles fonctionnalités. <em>NEST était la génération précédente du client .NET, qui a été remplacée par le client </em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="Construire une application blazor : esre avec diagramme blazor" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">Qu'est-ce que Blazor ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">Qu'est-ce que l'ESRE ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">Configuration d'ELSER</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">Indexation des données</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">Demande de permis de construire</a></p></li></ul><h2>Qu'est-ce que Blazor ?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Serveur Blazor" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a> est un framework web open source basé sur HTML, CSS et C# créé par Microsoft pour permettre aux développeurs de créer des applications web qui s'exécutent sur le client ou le serveur. Blazor vous permet également de créer des composants réutilisables pour construire des applications plus rapidement ; il permet aux développeurs de construire la vue HTML et les actions en C# dans le même fichier, ce qui aide à maintenir un code lisible et propre. De plus, avec Blazor Hybrid, vous pouvez créer des applications mobiles natives accédant aux capacités de la plateforme native via le code .NET.</p><p>Quelques-unes des caractéristiques qui font de Blazor un framework idéal pour travailler :</p><ul><li><p>Options de rendu côté serveur et côté client</p></li><li><p>Composants réutilisables de l'interface utilisateur</p></li><li><p>Mises à jour en temps réel avec SignalR</p></li><li><p>Gestion intégrée des états</p></li><li><p>Système de routage intégré</p></li><li><p>Vérifications solides du typage et de la compilation</p></li></ul><h3>Pourquoi Blazor ?</h3><p>Blazor offre plusieurs avantages par rapport à d'autres cadres et bibliothèques : il permet aux développeurs d'utiliser le langage C# pour le code client et le code serveur, en fournissant un typage fort et une vérification au moment de la compilation, ce qui améliore la fiabilité. Il s'intègre parfaitement à l'écosystème .NET, permettant la réutilisation de bibliothèques et d'outils .NET, et offre un support de débogage robuste.</p><h2>Qu'est-ce que l'ESRE ?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">Elasticsearch Relevance Engine™ (ESRE</a> ) est un ensemble d'<a href="https://www.elastic.co/guide/en/esre/current/learn.html">outils permettant de créer des applications de recherche</a> utilisant l'apprentissage automatique et l'intelligence artificielle au-dessus du puissant moteur de recherche Elasticsearch.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="esre" /><p>Pour en savoir plus sur l'ESRE, vous pouvez lire notre article de blog <a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">ici.</a></p><h2>Configuration d'ELSER</h2><p>Pour tirer parti des capacités <a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a> d'Elastic, nous allons utiliser <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> comme fournisseur de modèle.</p><p><em>Remarque : pour utiliser les modèles ELSER d'Elasticsearch, vous devez disposer d'une licence Platinum ou Enterprise, et d'un nœud de Machine Lerning (ML) dédié d'une taille minimale de 4 Go. Pour en savoir plus,</em> <a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>cliquez ici.</em></a></p><p>Commencez par créer le point d'arrivée de l'inférence :</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>Si vous utilisez ELSER pour la première fois, il se peut que vous rencontriez une erreur 502 Bad Gateway lorsque le modèle se charge en arrière-plan. Vous pouvez vérifier l'état du modèle sur <code>Machine Learning &gt; Trained Models</code> dans Kibana. Une fois qu'il est déployé, vous pouvez passer à l'étape suivante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="Vérification des modèles formés" /><h2>Indexation des données</h2><p>Vous pouvez télécharger le jeu de données <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">ici</a> et importer les données à l'aide de Kibana. Pour ce faire, allez sur la page d'accueil et cliquez sur "Upload data". Ensuite, téléchargez le fichier et cliquez sur <code>Import</code>. Enfin, allez dans l'onglet <code>Advanced</code> et collez les mappings suivants :</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="Importer des données" /><p>Nous allons créer un index capable d'exécuter des requêtes sémantiques et en texte intégral. Le type de champ <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">semantic_text</a> se chargera du découpage et de l'intégration des données. <em>Notez que nous indexons</em> <em><code>longDescription</code></em> <em>comme</em> <em><code>semantic_text</code></em>,<em> vous pouvez utiliser copy_to si vous voulez indexer un champ comme</em> <em><code>semantic_text</code></em> <em>et text`.</em></p><h2>Construire l'application avec Blazor &amp; Elasticsearch</h2><h3>Clé API</h3><p>La première chose à faire est de créer une clé API pour authentifier nos requêtes à Elasticsearch. La clé API doit être en lecture seule et n'être autorisée qu'à interroger l'index <code>books-blazor</code>.</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>Vous verrez quelque chose comme ceci :
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>Enregistrez la valeur du champ de réponse <code>encoded</code> car vous en aurez besoin ultérieurement. Si vous travaillez sur <a href="https://www.elastic.co/cloud/">Elastic Cloud</a>, vous aurez également besoin de votre Cloud ID. (Vous pouvez le trouver <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">ici</a>).</p><h4>Création du projet Blazor</h4><p>Commencez par installer Blazor et créez un projet d'exemple en suivant les <a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">instructions officielles.</a></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Blazor tutorial - Création du projet Blazor" /><p>Une fois le projet créé, la structure des dossiers et des fichiers doit ressembler à ceci :</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p>Le modèle d'application comprend <a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a> pour le stylisme.</p><p>Terminez la configuration du projet en installant le client <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET :</a></p>dotnet add package Elastic.Clients.Elasticsearch<p>Une fois cette étape terminée, votre page devrait ressembler à ceci :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor hello world" /><h3>Structure des dossiers</h3><p>Nous allons maintenant organiser nos dossiers comme suit :</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>Les dossiers sont expliqués :</p><ul><li><p>Components/Pages/Search.razor : page principale contenant la barre de recherche, les résultats et les filtres.</p></li><li><p>Composants/Pages/Search.razor.css : les styles de page.</p></li><li><p>Components/Elasticsearch/SearchBar.razor : composant de barre de recherche.</p></li><li><p>Components/Elasticsearch/Results.razor : composant de résultats.</p></li><li><p>Components/Elasticsearch/Facet.razor : composant de filtres.</p></li><li><p>Components/Svg/GlassIcon.razor : icône de recherche.</p></li><li><p>Components/_Imports.razor : ce fichier importera tous les composants.</p></li><li><p>Models/Book.cs : ce fichier contient le schéma du champ livre.</p></li><li><p>Models/Response.cs : ce fichier stocke le schéma de la réponse, y compris les résultats de la recherche, les facettes et le nombre total de résultats.</p></li><li><p>Services/ElasticsearchService.cs : Service Elasticsearch. Il gère la connexion et les requêtes à Elasticsearch.</p></li></ul><h4>Configuration initiale</h4><p>Commençons par un peu de nettoyage.</p><p>Supprimer les fichiers :</p><ul><li><p>Composants/Pages/Compteur.razor</p></li><li><p>Composants/Pages/Météo.razor</p></li><li><p>Composants/Pages/Home.razor</p></li><li><p>Components/Layout/NavMenu.razor</p></li><li><p>Composants/Layout/NavMenu.razor.css</p></li></ul><p>Vérifiez le fichier <code>/Components/_Imports.razor</code>. Vous devriez avoir les importations suivantes :</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>Intégrer Elastic dans le projet</h4><p>Maintenant, importons les composants d'Elasticsearch :</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p>Nous allons supprimer la barre latérale par défaut afin de disposer de plus d'espace pour notre application en la supprimant du fichier <code>/Components/Layout/MainLayout.razor</code>:</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="barre de navigation supprimée de la mise en page" /><p>Entrons maintenant les informations d'identification Elasticsearch pour les <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">secrets d'utilisateur</a>:</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>Grâce à cette approche, .Net 8 stocke les données sensibles dans un emplacement distinct, en dehors du dossier du projet, et les rend accessibles à l'aide de l'interface <code>IConfiguration</code>. Ces variables seront disponibles pour tout projet .Net qui utilise les mêmes secrets d'utilisateur.</p><p>Ensuite, modifions le fichier <code>Program.cs</code> pour lire les secrets et monter le client Elasticsearch :</p><p>Tout d'abord, il faut importer les bibliothèques nécessaires :</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services : contient le service Elasticsearch.</p></li><li><p>Elastic.Clients.Elasticsearch : importe la bibliothèque .Net 8 du client Elasticsearch.</p></li><li><p>Elastic.Transport : importe la bibliothèque de transport Elasticsearch, ce qui nous permet d'utiliser la classe ApiKey pour authentifier nos requêtes.</p></li></ul><p>Deuxièmement, insérez le code suivant avant la ligne <code>var app = builder.Build()</code>:</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>Ce code va lire les informations d'identification Elasticsearch à partir des secrets d'utilisateur et créer une instance de client Elasticsearch.</p><p>Après l'initialisation du client ElasticSearch, ajoutez la ligne suivante pour enregistrer le service Elasticsearch :</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>L'étape suivante consistera à construire la logique de recherche dans le fichier <code>/Services/ElasticsearchService.cs</code>:</p><p>Tout d'abord, il faut importer les bibliothèques et les modèles nécessaires :</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>Ensuite, ajoutez la classe <code>ElasticsearchService</code>, le constructeur et les variables :</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>Configuration de la recherche</h4><p>Construisons maintenant notre logique de recherche :</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> construira les filtres pour la requête de recherche en utilisant les facettes sélectionnées par l'utilisateur.</p></li><li><p><code>BuildHybridQuery</code> construira une requête de <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a> qui combine la recherche plein texte et la recherche sémantique.</p></li></ul><p>Ensuite, ajoutez la méthode de recherche :</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>: effectuera la recherche en utilisant la requête hybride et renverra les résultats en incluant les agrégations pour construire les facettes.</p></li><li><p><code>FormatFacets</code>: formatera la réponse des agrégations dans un dictionnaire.</p></li><li><p><code>ConvertFacetDictionary</code>: convertit le dictionnaire de facettes dans un format plus lisible.</p></li></ul><p>L'étape suivante consiste à créer les modèles qui représenteront les données renvoyées dans le site <code>hits</code> de la requête Elasticsearch et qui seront imprimées en tant que résultats dans notre page de recherche.</p><p>Nous commençons par créer le fichier <code>/Models/Book.cs</code> et y ajouter ce qui suit :</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>Ensuite, il faut configurer la réponse Elastic dans le fichier <code>/Models/Response.cs</code> et ajouter ce qui suit :</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>Configuration d'une interface utilisateur de base</h4><p>Ensuite, ajoutez le composant SearchBar. Dans le fichier <code>/Components/Elasticsearch/SearchBar.razor</code>, ajoutez ce qui suit :</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>Ce composant contient une barre de recherche et un bouton pour effectuer la recherche.</p><p>Blazor offre une grande flexibilité en permettant de générer du HTML dynamiquement en utilisant du code C# dans le même fichier.</p><p>Ensuite, dans le fichier <code>/Components/Elasticsearch/Results.razor</code>, nous construirons le composant de résultats qui affichera les résultats de la recherche :</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>Enfin, nous devons créer des facettes pour filtrer les résultats de la recherche.</p><p><em>Remarque : les facettes sont des filtres qui permettent aux utilisateurs d'affiner les résultats de leur recherche en fonction d'attributs ou de catégories spécifiques, tels que le type de produit, la fourchette de prix ou la marque. Ces filtres sont généralement présentés sous forme d'options cliquables, souvent sous forme de cases à cocher, afin d'aider les utilisateurs à affiner leur recherche et à trouver plus facilement des résultats pertinents. Dans le contexte d'Elasticsearch, les facettes sont créées à l'aide d'</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>agrégations</em></a><em>.</em></p><p>Nous configurons les facettes en plaçant le code suivant dans le fichier <code>/Components/Elasticsearch/Facet.razor</code>:</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>Ce composant lit une agrégation <code>terms</code> sur les champs <code>author</code>, <code>categories</code> et <code>status</code>, puis produit une liste de filtres à renvoyer à Elasticsearch.</p><p>Mettons tout cela bout à bout.</p><p>Dans le fichier <code>/Components/Pages/Search.razor</code>:</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>Notre page fonctionne !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Exemple de page Blazor" /><p>Comme vous pouvez le constater, la page est fonctionnelle mais manque de style. Ajoutons quelques feuilles de style CSS pour le rendre plus organisé et plus réactif.</p><p>Commençons par remplacer les styles de présentation. Dans le fichier <code>Components/Layout/MainLayout.razor.css</code>:</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p>Ajoutez les styles pour la page de recherche dans le fichier <code>Components/Pages/Search.razor.css</code>:</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>Notre page commence à s'améliorer :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="Page Blazor après l'ajout de styles pour la page de recherche" /><p>Mettons-y les dernières touches :</p><p>Créez les fichiers suivants :</p><ul><li><p>Composants/Elasticsearch/Facet.razor.css</p></li><li><p>Composants/Elasticsearch/Results.razor.css</p></li></ul><p>Et ajoutez les styles pour <code>Facet.razor.css</code>:</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p>Pour <code>Results.razor.css</code>:</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>Résultat final :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="Résultat final de la construction de la page de l'application blazor" /><p>Pour lancer l'application, vous pouvez utiliser la commande suivante :</p><p><code>dotnet watch</code></p><p>Vous avez réussi ! Vous pouvez désormais rechercher des livres dans votre index Elasticsearch en utilisant la barre de recherche et filtrer les résultats par auteur, catégorie et statut.</p><h3>Effectuer une recherche plein texte et sémantique</h3><p>Par défaut, notre application effectuera une <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a> utilisant à la fois le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">texte intégral</a> et la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">recherche sémantique</a>. Vous pouvez modifier la logique de recherche en créant deux méthodes distinctes, l'une pour le texte intégral et l'autre pour la recherche sémantique, puis en sélectionnant une méthode pour élaborer la requête en fonction des données saisies par l'utilisateur.</p><p>Ajoutez les méthodes suivantes à la classe <code>ElasticsearchService</code> dans le fichier <code>/Services/ElasticsearchService.cs</code>:</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>Ces deux méthodes fonctionnent de manière similaire à la méthode <code>BuildHybridQuery</code>, mais elles n'effectuent qu'une recherche plein texte ou sémantique.</p><p>Vous pouvez modifier la méthode <code>SearchBooksAsync</code> pour utiliser la méthode de recherche sélectionnée :</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>Vous pouvez trouver le formulaire complet <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">ici</a></p><h2>Conclusion</h2><p>Blazor est un framework efficace qui vous permet de construire des applications web en utilisant C#. Elasticsearch est un moteur de recherche puissant qui vous permet de créer des applications de recherche. En combinant les deux, vous pouvez facilement créer des applications de recherche robustes, en tirant parti de la puissance d'ESRE pour créer une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">expérience de recherche sémantique</a> en peu de temps.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Evolution du client Elasticsearch .NET : De NEST à Elastic.Clients.Elasticsearch]]></title>
    <description><![CDATA[Découvrez l'évolution du client Elasticsearch .NET et la transition de NEST à Elastic.Clients.Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introduction au client .NET et à NEST</h2><p>Dans le monde .NET, l'intégration avec Elasticsearch est depuis longtemps facilitée par la bibliothèque <code>NEST</code>, qui sert d'interface robuste aux développeurs pour interagir avec les puissantes capacités de recherche et d'analyse d'Elasticsearch. <code>NEST</code>née de la nécessité de disposer d'un client .NET natif pour Elasticsearch, a rapidement gagné en popularité auprès des développeurs en raison de la richesse de ses fonctionnalités et de ses capacités d'intégration transparentes.</p><p>Depuis près de <a href="https://github.com/elastic/elasticsearch-net/commit/724f932ba598915c8c3c35a19827fdfa4f782c1d">14 ans</a> et seulement 8 mois après le <a href="https://github.com/elastic/elasticsearch/commit/ec72ca8b7a115f9b2eea3c76c518062b99a1d015">premier commit d'Elasticsearch</a>, NEST suit fidèlement les versions d'Elasticsearch.</p><h2>Passage de NEST à Elastic.Clients.Elasticsearch</h2><p>Avec l'évolution d'Elasticsearch, la maintenance de la base de code complexe de <code>NEST</code> est devenue de plus en plus difficile. Nous avons reconnu la nécessité d'une approche plus durable du développement des clients et nous avons entrepris de redéfinir le client .NET à partir de zéro. Il nous a fallu près d'un an pour publier une première version bêta et un an de plus pour nous rapprocher de la prise en charge de tous les points d'extrémité du serveur. L'une des décisions les plus difficiles à prendre a été de réduire la portée de la bibliothèque afin de donner la priorité à la maintenabilité.</p><p>Compte tenu de la taille de la surface de l'API Elasticsearch aujourd'hui, il n'est plus pratique de maintenir plus de 450 points d'extrémité et près de 3000 types (demandes, réponses, requêtes, agrégations, etc.) à la main. Pour garantir un alignement cohérent, précis et opportun entre les clients linguistiques et Elasticsearch, les clients 8.x et de nombreux types associés sont désormais automatiquement générés par le code à partir d'une <a href="https://github.com/elastic/elasticsearch-specification">spécification partagée.</a> Il s'agit d'une solution courante pour maintenir l'alignement entre le client et le serveur parmi les SDK et les bibliothèques, tels que ceux pour Azure, AWS et Google Cloud Platform.</p><p>La spécification Elasticsearch a été créée il y a plus de 8 ans en exportant les mappages de types de <code>NEST</code> et grâce au travail acharné de l'équipe clients, nous pouvons maintenant utiliser la même spécification pour créer un nouveau client .NET (et des clients pour de nombreux autres langages tels que Java, Go, etc.)</p><p>Avec la sortie de la version 8.13, l'abandon de <code>NEST</code> a été officiellement annoncé. Avec la transition d'Elasticsearch vers <code>Elastic.Clients.Elasticsearch</code>, <code>NEST</code> sera progressivement supprimé et atteindra sa fin de vie à la fin de l'année. Les développeurs sont fortement encouragés à commencer les efforts de migration dès le début afin d'assurer une transition en douceur et d'atténuer les perturbations potentielles. L'adoption du site <code>Elastic.Clients.Elasticsearch</code> permet non seulement d'assurer la compatibilité avec les dernières fonctionnalités du serveur, mais aussi de protéger les applications contre les fonctionnalités obsolètes.</p><h2>Elastic.Clients.Elasticsearch : aperçu des fonctionnalités et des changements</h2><p>Le passage au client v8 <code>Elastic.Clients.Elasticsearch</code> permet d'accéder à toutes les nouvelles fonctionnalités d'Elasticsearch 8 et apporte également de nombreuses modernisations à la bibliothèque elle-même, mais implique également une réduction des fonctionnalités de commodité par rapport à son prédécesseur. Parmi les nouvelles fonctionnalités de base, citons le langage de requête <code>ES|QL</code>, des capacités modernes d'apprentissage automatique (ML) et des diagnostics améliorés sous la forme d'activités compatibles avec OpenTelemetry. À partir de la version 8.13, <code>Elastic.Clients.Elasticsearch</code> prend en charge presque toutes les fonctionnalités serveur d'Elasticsearch 8.</p><p>Une rupture importante, par exemple, est liée aux agrégations. Dans <code>NEST</code>, l'utilisation de l'API fluide se présente comme suit :</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
    .Children&lt;CommitActivity&gt;("name_of_child_agg", child =&gt; child
        .Aggregations(childAggs =&gt; childAggs
            .Average("average_per_child", avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor))
            .Max("max_per_child", max =&gt; max.Field(p =&gt; p.ConfidenceFactor))
            .Min("min_per_child", min =&gt; min.Field(p =&gt; p.ConfidenceFactor))
        )
    )
)
<p>tandis que le client v8 requiert la syntaxe suivante :</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
	.Add("name_of_child_agg", agg =&gt; agg
		.Children(_ =&gt; {})
		.Aggregations(childAggs =&gt; childAggs
			.Add("average_per_child", agg =&gt; agg.Avg(avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor)))
			.Add("max_per_child", agg =&gt; agg.Max(max =&gt; max.Field(p =&gt; p.ConfidenceFactor)))
			.Add("min_per_child", agg =&gt; agg.Min(min =&gt; min.Field(p =&gt; p.ConfidenceFactor)))
		)
	)
)
<h2>Migration de NEST v7 vers le client .NET v8</h2><p>Un guide de migration complet est disponible ici : <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.18/migration-guide.html">Guide de migration : De NEST v7 à .NET Client v8</a>.</p><h2>Ressources supplémentaires</h2><ul><li><p><a href="https://github.com/elastic/elasticsearch-net">Elastic.Clients.Elasticsearch v8 Client sur GitHub</a></p></li><li><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch">Elastic.Clients.Elasticsearch v8 Client sur NuGet</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/net-client-evolution</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/net-client-evolution</guid>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Florian Bernd]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac0adcdaa85d703f/6a17f5816df731db170a1079/d09f7f5cb468d5e84f7f4636d92b3476e6604e11-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>