<?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/es/search-labs/blog/category/dot-net-programming</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/dot-net-programming</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/dot-net-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 10:23:02 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Cómo usar el Conector de Almacenamiento de Vectores de Elasticsearch para el Kernel Semántico de Microsoft para el desarrollo de agentes de IA]]></title>
    <description><![CDATA[Microsoft Semantic Kernel es un kit de desarrollo ligero y de código abierto que te permite construir agentes de IA con facilidad e integrar los últimos modelos de IA en tu base de código en C#, Python o Java. Con el lanzamiento del Conector de Almacenamiento Vectorial de Semantic Kernel Elasticsearch, los desarrolladores que emplean Semantic Kernel para construir agentes de IA pueden ahora integrar Elasticsearch como un almacén vectorial escalable de nivel empresarial, mientras continúan empleando abstracciones de Semantic Kernel.]]></description>
    <content:encoded><![CDATA[<p>En colaboración con el equipo <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel</a> , anunciamos la disponibilidad del <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a>, para usuarios <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel</a> (.NET). Semantic Kernel simplifica la construcción de agentes de IA de nivel empresarial, incluyendo la capacidad de mejorar grandes modelos de lenguaje (LLMs) con respuestas más relevantes y basadas en datos desde una Vector Store. Semantic Kernel proporciona una capa de abstracción fluida para interactuar con Vector Stores como Elasticsearch, ofreciendo funciones esenciales como crear, listar y eliminar colecciones de registros, así como subir, recuperar y eliminar registros individuales.</p><p>El conector de <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">almacenamiento vectorial de Semantic Kernel Elasticsearch, ya de fábrica</a> , soporta las <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">abstracciones de almacenamiento vectorial</a> Semantic Kernel, lo que facilita mucho a los desarrolladores integrar Elasticsearch como un almacén vectorial mientras crean agentes de IA.</p><p>Elasticsearch tiene una estable base en la comunidad de código abierto y recientemente adoptó la <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">licencia AGPL</a>. Combinadas con el kernel semántico de código abierto de Microsoft, estas herramientas ofrecen una solución poderosa y preparada para compañías. Puedes empezar localmente activando Elasticsearch en unos minutos ejecutando este <code>curl -fsSL https://elastic.co/start-local | sh </code>de comandos (consulta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> para más detalles) y pasar a versiones <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">alojadas en la nube</a> o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">autoalojadas</a> mientras produces tus agentes de IA.</p><p>En este blog analizamos cómo usar <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a> al usar Semantic Kernel. En el futuro se pondrá a disposición una versión en Python del conector.</p><h2>Escenario de alto nivel: Construcción de una aplicación RAG con Kernel Semántico y Elasticsearch</h2><p>En la siguiente sección repasamos un ejemplo. A un nivel general, estamos construyendo una aplicación RAG (Recuperación Generación Aumentada) que toma la pregunta del usuario como entrada y devuelve una respuesta. Usaremos Azure OpenAI (también se puede usar <a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">un LLM local</a> ) como LLM, Elasticsearch como almacén vectorial y Semantic Kernel (.net) como marco para unir todos los componentes.</p><p>Si no estás familiarizado con las arquitecturas RAG, puedes hacer una breve introducción con este artículo: <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 respuesta la genera el LLM, que se alimenta con el contexto relevante para la pregunta, recuperado de Elasticsearch vectorstore. La respuesta también incluye la fuente que el LLM empleó como contexto.</p><h3>Ejemplo RAG</h3><p>En este ejemplo concreto, construimos una aplicación que permite a los usuarios hacer preguntas sobre hoteles almacenados en una base de datos interna de hoteles. El usuario podría, por ejemplo, Busca un hotel específico, según diferentes criterios, o pide una lista de hoteles.</p><p>Para la base de datos de ejemplo, generamos una <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">lista de hoteles</a> con 100 entradas. El tamaño de la muestra es intencionadamente pequeño para que puedas probar la demo del conector lo más fácilmente posible. En una aplicación real, el conector Elasticsearch mostraría sus beneficios sobre otras opciones, como la implementación de almacenamiento vectorial 'InMemory', especialmente cuando se trabaja con cantidades extremadamente grandes de datos.</p><p>La aplicación de demostración completa se puede encontrar en el <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">repositorio</a> de conectores de almacenamiento vectorial de Elasticsearch.</p><p>Empecemos agregando los paquetes NuGet necesarios y aplicando directivas a nuestro proyecto:</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>Ahora podemos crear nuestro modelo de datos y proporcionarle atributos específicos del Núcleo Semántico para definir el esquema del modelo de almacenamiento y algunas pistas para la búsqueda de texto:</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>Los atributos del Esquema del Modelo de Almacenamiento ('VectorStore*') son los más relevantes para el uso real del Conector de Almacenamiento Vectorial Elasticsearch, a saber:</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> para marcar una propiedad en una clase de registro como la clave bajo la cual el registro se almacena en un almacén vectorial.</p></li><li><p><code>VectorStoreRecordData</code> para marcar una propiedad en una clase de registro como 'datos'.</p></li><li><p><code>VectorStoreRecordVector</code> para marcar una propiedad en una clase de registro como vector.</p></li></ul><p>Todos estos atributos aceptan varios parámetros opcionales que pueden emplear para personalizar aún más el modelo de almacenamiento. En el caso de <code>VectorStoreRecordKey </code>, por ejemplo, es posible especificar una función de distancia diferente o un tipo de índice distinto.</p><p>Los atributos de búsqueda de texto (<code>TextSearch*</code>) serán importantes en el último paso de este ejemplo. Volveremos a ellos más tarde.</p><p>En el siguiente paso, inicializamos el motor Semantic Kernel y obtenemos referencias a los servicios principales. En una aplicación real, se debe usar <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">inyección de dependencias</a> en lugar de acceder directamente a la colección de servicios. Lo mismo se aplica a la configuración y secretos codificados en fija, que deberían leer usando un <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">proveedor de configuración</a> en su lugar:</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>El servicio de <code>vectorStoreCollection</code> ahora puede usar para crear la colección e incorporar algunos <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">discos demo</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>Esto muestra cómo Semantic Kernel reduce el uso de un almacén vectorial con toda su complejidad a unas pocas llamadas a métodos simples.</p><p>En el fondo, se crea un nuevo índice en Elasticsearch y se generan todas las asignaciones de propiedades necesarias. Nuestro conjunto de datos se mapea de forma completamente transparente en el modelo de almacenamiento y finalmente se almacena en el índice. A continuación se muestra cómo se ven los mapeos en 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>El <code>embeddings.GenerateEmbeddingsAsync()</code> llama de forma transparente al servicio configurado Azure AI Embeddings Generation.</p><p>Se puede observar aún más magia en el último paso de esta demo.</p><p>Con una sola llamada a <code>InvokePromptAsync</code>, se realizan todas las siguientes operaciones cuando el usuario hace una pregunta sobre los datos:</p><p>1. Se genera una incrustación para la pregunta del usuario</p><p>2. Se busca en el almacén vectorial las entradas relevantes</p><p>3. Los resultados de la consulta se insertan en una plantilla de prompt</p><p>4. La consulta real en forma de prompt final se envía al servicio de finalización de chat con 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>¿Recuerdas los atributos <code>TextSearch*</code> que definimos previamente en nuestro modelo de datos? Estos atributos nos permiten usar marcadores de posición correspondientes en nuestra plantilla de prompts, que se llenan automáticamente con la información de nuestras entradas en el almacén vectorial.</p><p>La respuesta final a nuestra pregunta "Por favor, muéstrame todos los hoteles que tengan un bar en la azotea" es la siguiente:</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 respuesta correcta se refiere a la siguiente entrada de nuestro 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>Este ejemplo muestra muy bien cómo el uso de Microsoft Semantic Kernel logra una reducción significativa de la complejidad gracias a sus abstracciones bien pensadas, además de permitir un nivel muy alto de flexibilidad. Por ejemplo, al cambiar una sola línea de código, el almacén vectorial o los servicios de IA empleados pueden ser reemplazados sin necesidad de refactorizar ninguna otra parte del código.</p><p>Al mismo tiempo, el framework proporciona un enorme conjunto de funcionalidades de alto nivel, como la función 'InvokePrompt', o el sistema de plantillas o plugins de búsqueda.</p><p>La aplicación de demostración completa se puede encontrar en el repositorio de conectores de almacenamiento vectorial de Elasticsearch.</p><h2>¿Qué más es posible con Elasticsearch?</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Mapeado de nuevas semantic_text de Elasticsearch: Simplificación de la búsqueda semántica</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Reclasificación semántica en Elasticsearch con los retrievers</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">Técnicas avanzadas de RAG parte 1: Procesamiento de datos</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Técnicas avanzadas de RAG parte 2: Consultas y pruebas</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Construyendo RAG con Llama 3 de código abierto y Elastic</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">Un tutorial para construir agentes locales usando LangGraph, LLaMA3 y almacenamiento vectorial de Elasticsearch desde cero</a></p></li></ul><h2>Elasticsearch &amp; Semantic Kernel: ¿Qué sigue?</h2><ul><li><p>Mostramos cómo la tienda vectorial de Elasticsearch puede conectarse fácilmente a Semantic Kernel mientras se construyen aplicaciones GenAI en .NET. Estad atentos para la próxima integración de Python.</p></li><li><p>A medida que Semantic Kernel construye abstracciones para funciones avanzadas de búsqueda como <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">la búsqueda híbrida</a>, el Elasticsearch Connect permitirá a los desarrolladores .NET implementarlas fácilmente usando 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[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[Base de datos vectorial]]></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[Crear una aplicación de búsqueda con Blazor y Elasticsearch]]></title>
    <description><![CDATA[Aprende a construir una aplicación de búsqueda usando Blazor y Elasticsearch, y cómo emplear el cliente Elasticsearch .NET para búsqueda híbrida.]]></description>
    <content:encoded><![CDATA[<p>En este artículo, aprenderás cómo aprovechar tus habilidades en C# para crear una aplicación de búsqueda usando Blazor y Elasticsearch. Vamos a usar el cliente <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a> para ejecutar consultas <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">de texto completo</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">semánticas</a> y <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">búsquedas híbridas</a> .</p><p><strong>NOTA</strong> Si conoces la versión anterior <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">del cliente</a> Nest de Elasticsearch en C#, lee esta <a href="https://www.elastic.co/search-labs/blog/net-client-evolution">entrada del blog</a> sobre la deprecación del cliente NEST y las nuevas funciones. <em>NEST fue la generación anterior del cliente .NET que fue reemplazado por el </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="Build app Blazor: esre con diagrama Blazor" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">¿Qué es Blazor?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">¿Qué es ESRE?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">Configuración de ELSER</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">Datos de indexación</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">Aplicación en edificios</a></p></li></ul><h2>¿Qué es Blazor?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Servidor Blazor" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a> es un framework sitio web de código abierto basado en HTML, CSS y C# creado por Microsoft para permitir a los desarrolladores crear aplicaciones sitio web que se ejecuten en el cliente o en el servidor. Blazor también permite crear componentes reutilizables para construir aplicaciones más rápido; permite a los desarrolladores construir la vista HTML y las acciones en C# dentro del mismo archivo, lo que ayuda a mantener un código legible y limpio. Además, con Blazor Hybrid puedes crear aplicaciones móviles nativas accediendo a las capacidades nativas de la plataforma mediante código .NET.</p><p>Algunas de las características que hacen de Blazor un excelente marco para trabajar:</p><ul><li><p>Opciones de renderizado tanto en el lado servidor como en el cliente</p></li><li><p>Componentes reutilizables de la interfaz</p></li><li><p>Actualizaciones en tiempo real con SignalR</p></li><li><p>Gestión del estado integrada</p></li><li><p>Sistema de enrutamiento integrado</p></li><li><p>Comprobaciones fuertes de tipado y tiempo de compilación</p></li></ul><h3>¿Por qué Blazor?</h3><p>Blazor ofrece varios beneficios sobre otros frameworks y librerías: permite a los desarrolladores usar C# tanto para el código cliente como para el servidor, proporcionando una fuerte verificación de tipado y compilación que mejora la fiabilidad. Se integra perfectamente con el ecosistema .NET, permitiendo la reutilización de librerías y herramientas .NET, y ofrece un soporte robusto para depuración.</p><h2>¿Qué es ESRE?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">El Elasticsearch Relevance Engine™ (ESRE)</a> es un conjunto de <a href="https://www.elastic.co/guide/en/esre/current/learn.html">herramientas para construir aplicaciones de búsqueda</a> empleando aprendizaje automático e inteligencia artificial sobre el poderoso motor de búsqueda Elasticsearch.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="Esre" /><p>Para saber más sobre ESRE, puedes leer nuestra interesante entrada de blog <a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">aquí</a></p><h2>Configuración de ELSER</h2><p>Para aprovechar las capacidades <a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a> de Elastic, vamos a emplear <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> como nuestro proveedor de modelos.</p><p><em>Nota: para usar los modelos ELSER de Elasticsearch, debes tener una licencia Platinum o Enterprise, y disponer de un nodo mínimo dedicado de Machine Lerning (ML) de 4GB de tamaño. Lee más</em> <a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>sobre esto aquí.</em></a></p><p>Empieza creando el punto final de inferencia:</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>Si es la primera vez que usas ELSER, puede que te encuentres con un error 502 de Mal Gateway mientras el modelo se carga en segundo plano. Puedes consultar el estado del modelo en <code>Machine Learning &gt; Trained Models</code> en Kibana. Una vez desplegado, puedes pasar al siguiente paso.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="Modelos capacitados comprobando" /><h2>Datos de indexación</h2><p>Puedes descargar el conjunto <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">de datos aquí</a> y luego importar los datos usando Kibana. Para ello, ve a la página principal y haz clic en "Subir datos". Luego, sube el archivo y haz clic <code>Import</code>. Por último, ve a la pestaña <code>Advanced</code> y pega los siguientes mapeos:</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="Datos importados" /><p>Vamos a crear un índice capaz de ejecutar consultas semánticas y de texto completo. El tipo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">de campo semantic_text</a> se encargará del fragmento de datos y la incrustación. <em>Ten en cuenta que estamos indexando</em> <em><code>longDescription</code></em> <em>como</em> <em><code>semantic_text</code></em><em>, puedes usar copy_to si quieres indexar un campo tanto como</em> <em><code>semantic_text</code></em> <em>como texto'</em>.</p><h2>Construcción de la app con Blazor &amp; Elasticsearch</h2><h3>Clave API</h3><p>Lo primero que tenemos que hacer es crear una clave API para autenticar nuestras solicitudes a Elasticsearch. La clave API debe ser de solo lectura y solo permitir consultar el índice de <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>Verás algo así:
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>Almacena el valor del campo de respuesta <code>encoded</code> cuando sea necesario más adelante. Si usas <a href="https://www.elastic.co/cloud/">Elastic Cloud</a>, también necesitarás tu ID de la nube. (Puedes <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">encontrarlo aquí</a>).</p><h4>Creación del proyecto Blazor</h4><p>Empieza instalando Blazor y creando un proyecto de muestra siguiendo las <a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">instrucciones oficiales</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Tutorial de Blazor - Creando el proyecto Blazor" /><p>Una vez que tengas el proyecto creado, la estructura de carpetas y los archivos deberían ver así:</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>La aplicación plantilla incluye <a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a> para peinar.</p><p>Termina la configuración del proyecto instalando el cliente <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>Una vez que completes este paso, tu página debería ver así:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor, hola mundo" /><h3>Estructura de carpetas</h3><p>Ahora, vamos a organizar nuestras carpetas de la siguiente manera:</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>Archivos explicados:</p><ul><li><p>Components/Pages/Search.razor: página principal que contiene la barra de búsqueda, los resultados y los filtros.</p></li><li><p>Componentes/Páginas/Search.razor.css: estilos de página.</p></li><li><p>Components/Elasticsearch/SearchBar.razor: componente de la barra de búsqueda.</p></li><li><p>Components/Elasticsearch/Results.razor: componente de resultados.</p></li><li><p>Components/Elasticsearch/Facet.razor: componente de filtros.</p></li><li><p>Components/Svg/GlassIcon.razor: icono de búsqueda.</p></li><li><p>Components/_Imports.razor: esto importará todos los componentes.</p></li><li><p>Modelos/Book.cs: esto almacenará el esquema del campo libro.</p></li><li><p>Modelos/Response.cs: esto almacenará el esquema de respuesta, incluyendo los resultados de búsqueda, facetas y resultados totales.</p></li><li><p>Servicios/ElasticsearchService.cs: Servicio Elasticsearch. Se encargará de la conexión y las consultas a Elasticsearch.</p></li></ul><h4>Configuración inicial</h4><p>Empecemos con una limpieza.</p><p>Elimina los archivos:</p><ul><li><p>Componentes/Páginas/Counter.razor</p></li><li><p>Componentes/Páginas/Weather.razor</p></li><li><p>Componentes/Páginas/Home.razor</p></li><li><p>Componentes/Layout/NavMenu.razor</p></li><li><p>Componentes/Diseño/NavMenu.razor.css</p></li></ul><p>Revisa el archivo <code>/Components/_Imports.razor</code> . Deberías tener las siguientes importaciones:</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>Integración de Elastic en el proyecto</h4><p>Ahora, importemos los componentes de 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>Vamos a eliminar la barra lateral predeterminada para tener más espacio para nuestra aplicación eliminándola del archivo <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="Barra de navegación eliminada del diseño" /><p>Ahora introduzcamos las credenciales de Elasticsearch para los <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">secretos de usuario</a>:</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>Con este enfoque, .Net 8 almacena datos sensibles en una ubicación separada, fuera de la carpeta del proyecto, y los hace accesibles mediante la interfaz <code>IConfiguration</code> . Estas variables estarán disponibles para cualquier proyecto .Net que emplee los mismos secretos de usuario.</p><p>Luego, modifiquemos el archivo <code>Program.cs</code> para leer los secretos y montar el cliente Elasticsearch:</p><p>Primero, importa las bibliotecas necesarias:</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services: contiene el servicio Elasticsearch.</p></li><li><p>Elastic.Clients.Elasticsearch: importa la biblioteca cliente .Net 8 de Elasticsearch.</p></li><li><p>Elastic.Transport: importa la biblioteca de transporte Elasticsearch, que nos permite usar la clase ApiKey para autenticar nuestras solicitudes.</p></li></ul><p>Segundo, inserta el siguiente código antes de la línea <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>Este código leerá las credenciales de Elasticsearch de los secretos de usuario y creará una instancia cliente de Elasticsearch.</p><p>Tras la inicialización del cliente ElasticSearch, agrega la siguiente línea para registrar el servicio Elasticsearch:</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>El siguiente paso será construir la lógica de búsqueda en el archivo <code>/Services/ElasticsearchService.cs</code> :</p><p>Primero, importa las bibliotecas y modelos necesarios:</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>Segundo, agregar la clase <code>ElasticsearchService</code>, constructor y 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>Configuración de búsqueda</h4><p>Ahora, vamos a construir nuestra lógica de búsqueda:</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> construirá los filtros para la consulta de búsqueda usando las facetas seleccionadas por el usuario.</p></li><li><p><code>BuildHybridQuery</code> construirá una consulta <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">de búsqueda híbrida</a> que combine texto completo y búsqueda semántica.</p></li></ul><p>A continuación, agrega el método de búsqueda:</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>: realizará la búsqueda usando la consulta híbrida y devolverá los resultados incluidas agregaciones para construir las facetas.</p></li><li><p><code>FormatFacets</code>: formateará la respuesta de agregación en un diccionario.</p></li><li><p><code>ConvertFacetDictionary</code>: convertirá el diccionario de facetas en un formato más legible.</p></li></ul><p>El siguiente paso es crear los modelos que representarán los datos devueltos en el <code>hits</code> de la consulta de Elasticsearch que se imprimirán como resultados en nuestra página de búsqueda.</p><p>Comenzamos creando el <code>/Models/Book.cs</code> de archivo y agregando lo siguiente:</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>Luego, configurar la respuesta Elastic en el archivo <code>/Models/Response.cs</code> y agregar lo siguiente:</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>Configuración de una interfaz básica</h4><p>A continuación, agrega el componente Barra de búsqueda. En el archivo <code>/Components/Elasticsearch/SearchBar.razor</code> y agrega lo siguiente:</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>Este componente contiene una barra de búsqueda y un botón para realizar la búsqueda.</p><p>Blazor ofrece una gran flexibilidad al permitir generar HTML dinámicamente usando código C# dentro del mismo archivo.</p><p>Después, en el archivo <code>/Components/Elasticsearch/Results.razor</code> construiremos el componente de resultados que mostrará los resultados de búsqueda:</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>Finalmente, necesitaremos crear facetas para filtrar los resultados de búsqueda.</p><p><em>Nota: Los facetos son filtros que permiten a los usuarios reducir los resultados de búsqueda en función de atributos o categorías específicas, como el tipo de producto, el rango de precio o la marca. Estos filtros suelen presentar como opciones clicables, a menudo en forma de casillas para marcar, para ayudar a los usuarios a refinar su búsqueda y encontrar resultados relevantes con mayor facilidad. En el contexto de Elasticsearch, las facetas se crean mediante</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>agregaciones</em></a><em>.</em></p><p>Configuramos facetas poniendo el siguiente código en el archivo <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>Este componente lee de una agregación <code>terms</code> en los campos <code>author</code>, <code>categories</code>y <code>status</code> , y luego produce una lista de filtros para enviar de vuelta a Elasticsearch.</p><p>Ahora, vamos a juntar todo.</p><p>En <code>/Components/Pages/Search.razor</code> expediente:</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>¡Nuestra página funciona!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Ejemplo de página en Blazor" /><p>Como puedes ver, la página es funcional pero carece de estilos. Vamos a agregar algo de CSS para que parezca más organizado y receptivo.</p><p>Empecemos a reemplazar los estilos de distribución. En el archivo <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>Agrega los estilos de la página de búsqueda en el archivo <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>Nuestra página empieza a ver mejor:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="Página Blazor luego de agregar estilos para la página de búsqueda" /><p>Vamos a darle los toques finales:</p><p>Crea los siguientes archivos:</p><ul><li><p>Componentes/Elasticsearch/Facet.razor.css</p></li><li><p>Components/Elasticsearch/Results.razor.css</p></li></ul><p>Y agrega los estilos para <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>Para <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>Resultado final:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="Resultado final de la creación de la página de la app Blazor" /><p>Para ejecutar la aplicación puedes usar el siguiente comando:</p><p><code>dotnet watch</code></p><p>¡Lo conseguiste! Ahora puedes buscar libros en tu índice de Elasticsearch usando la barra de búsqueda y filtrar los resultados por autor, categoría y estado.</p><h3>Realización de búsqueda en texto completo y semántica</h3><p>Por defecto, nuestra app realiza una <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">búsqueda híbrida</a> usando tanto <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">texto completo</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">como búsqueda semántica</a>. Puedes cambiar la lógica de búsqueda creando dos métodos separados, uno para texto completo y otro para búsqueda semántica, y luego seleccionar uno para construir la consulta basar en la entrada del usuario.</p><p>Agrega los siguientes métodos a la clase <code>ElasticsearchService</code> en el archivo <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>Ambos métodos funcionan de forma similar al método <code>BuildHybridQuery</code> , pero solo realizan búsqueda en texto completo o semántica.</p><p>Puedes modificar el método <code>SearchBooksAsync</code> para usar el método de búsqueda seleccionado:</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>Puedes encontrar la solicitud completa <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">aquí</a></p><h2>Conclusión</h2><p>Blazor es un framework eficaz que te permite crear aplicaciones sitio web usando C#. Elasticsearch es un poderoso motor de búsqueda que te permite crear aplicaciones de búsqueda. Combinando ambos, puedes construir fácilmente aplicaciones de búsqueda robustas, aprovechando el poder de ESRE para crear una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">experiencia de búsqueda semántica</a> en poco tiempo.</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 de datos vectorial]]></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[Evolución del cliente Elasticsearch .NET: De NEST a Elas&shy;tic.Cli&shy;ents.Elastic&shy;search]]></title>
    <description><![CDATA[Descubre la evolución del cliente .NET de Elasticsearch y la transición de NEST a Elastic.Clients.Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introducción al cliente .NET y NEST</h2><p>En el mundo .NET, la integración con Elasticsearch fue facilitada durante mucho tiempo por la biblioteca <code>NEST</code> , que sirve como una interfaz robusta para que los desarrolladores interactúen con las poderosas capacidades de búsqueda y análisis de Elasticsearch. <code>NEST</code>, nacida de la necesidad de un cliente .NET nativo para Elasticsearch, ganó rápidamente popularidad entre los desarrolladores por su amplio conjunto de funciones y sus integraciones fluidas.</p><p>Durante casi <a href="https://github.com/elastic/elasticsearch-net/commit/724f932ba598915c8c3c35a19827fdfa4f782c1d">14</a> años y solo 8 meses después <a href="https://github.com/elastic/elasticsearch/commit/ec72ca8b7a115f9b2eea3c76c518062b99a1d015">del primer commit de Elasticsearch</a> , NEST estuvo siguiendo fielmente las versiones de Elasticsearch.</p><h2>Transición de NEST a Elastic.Clients.Elasticsearch</h2><p>A medida que Elasticsearch evolucionó, mantener la compleja base de código de <code>NEST</code>se volvió cada vez más difícil. Reconocimos la necesidad de un enfoque más sostenible en el desarrollo de clientes y emprendimos un camino para rediseñar el cliente .NET desde cero. Nos llevó casi un año lanzar la primera versión beta y otro año más acercarnos a dar soporte a todos y cada uno de los endpoints de servidor. Una de las decisiones más difíciles fue reducir el alcance de la biblioteca para priorizar la mantenibilidad.</p><p>Dado el tamaño actual de la superficie de la API de Elasticsearch, ya no es práctico mantener manualmente más de 450 endpoints y casi 3000 tipos (solicitudes, respuestas, consultas, agregaciones, etc.). Para cerciorar una alineación consistente, precisa y oportuna entre los clientes del lenguaje y Elasticsearch, los clientes 8.x y muchos de los tipos asociados ahora se generan automáticamente por código a partir de una <a href="https://github.com/elastic/elasticsearch-specification">especificación compartida</a>. Esta es una solución común para mantener la alineación entre cliente y servidor entre SDKs y librerías, como los de Azure, AWS y la Google Cloud Platform.</p><p>La especificación Elasticsearch se creó hace más de 8 años exportando los mapeos de tipos de <code>NEST</code> y, gracias al arduo trabajo del equipo de clientes, ahora podemos usar la misma especificación para crear un nuevo cliente .NET (y clientes para varios otros lenguajes como Java, Go, etc.).</p><p>Con el lanzamiento de la versión 8.13, se anunció oficialmente la desaparición de <code>NEST</code> . A medida que Elasticsearch se transforma en <code>Elastic.Clients.Elasticsearch</code>, <code>NEST</code> irá desapareciendo gradualmente, alcanzando su fin de vida útil al final del año. Se anima encarecidamente a los promotores a iniciar los esfuerzos de migración con antelación para garantizar una transición fluida y mitigar posibles interrupciones. Adoptar <code>Elastic.Clients.Elasticsearch</code> no solo garantiza compatibilidad con las últimas funciones del servidor, sino que también protege a futuro las aplicaciones contra funcionalidades obsoletas.</p><h2>Elastic.Clients.Elasticsearch: resumen de características y cambios</h2><p>Cambiar al cliente v8 <code>Elastic.Clients.Elasticsearch</code> permite acceder a todas las nuevas funciones de Elasticsearch 8 y también aporta numerosas modernizaciones a la propia biblioteca, pero implica una reducción en las características de comodidad en comparación con su predecesor. Algunas de las nuevas características principales incluyen el lenguaje de consultas <code>ES|QL</code>, capacidades modernas de aprendizaje automático (ML) y diagnósticos mejorados en forma de actividades compatibles con OpenTelemetry. A partir de la versión 8.13, <code>Elastic.Clients.Elasticsearch</code> soporta casi todas las funciones de servidor de Elasticsearch 8.</p><p>Un cambio importante de ruptura, por ejemplo, está relacionado con las agregaciones. En <code>NEST</code>, el uso fluido de la API es así:</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>mientras que el cliente v8 requiere la siguiente sintaxis:</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>Migración de NEST v7 a .NET client v8</h2><p>Aquí está disponible una guía completa de migración: <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.18/migration-guide.html">Guía de migración: De NEST v7 a .NET Client v8</a>.</p><h2>Recursos adicionales</h2><ul><li><p><a href="https://github.com/elastic/elasticsearch-net">Cliente Elastic.Clients.Elasticsearch v8 en GitHub</a></p></li><li><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch">Cliente Elastic.Clients.Elasticsearch v8 en 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>