Creando una aplicacion web con .NET MVC que consuma Azure Search

Durante los ultimos dos meses he publicado ya varios videos sobre Azure Search y en el ultimo video explico como crear una aplicacion web sencilla para buscar en Azure Search.

En este caso el codigo esta configurado para acceder un servicio ya provisionado por Microsoft para pruebas.

appsettings.json

{
  "SearchServiceName": "azs-playground",
  "SearchServiceQueryApiKey": "EA4510A6219E14888741FCFC19BFBB82"
}

En el codigo anterior solo configuramos el nombre del servicio y el API key que deseamos utilizar para conectarnos al servicio.

Lo siguiente, como es MVC, es definir el modelo de la pagina (o Vista) que nos permitira realizar la consulta con Azure Search

  public class SearchData
    {
        // The text to search for.
        public string searchText { get; set; }

        // The list of results.
        public DocumentSearchResult<Hotel> resultList;
    }

Como pueden ver es simplemente un campo de tipo string para la cadena de consulta y un campo de tipo DocumentSearchResult donde se almaceneran los resultados que se van a mostrar en la vista. Este objeto es un generic, en el cual se define su tipo, es decir un POCO (Plan Old CLR Object).

public partial class Hotel  
    {
        [System.ComponentModel.DataAnnotations.Key]
        [IsFilterable]
        public string HotelId { get; set; }

        [IsSearchable, IsSortable]
        public string HotelName { get; set; }

        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.EnLucene)]
        public string Description { get; set; }

        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.FrLucene)]
        [JsonProperty("Description_fr")]
        public string DescriptionFr { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string Category { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string[] Tags { get; set; }

        [IsFilterable, IsSortable, IsFacetable]
        public bool? ParkingIncluded { get; set; }

        [IsFilterable, IsSortable, IsFacetable]
        public DateTimeOffset? LastRenovationDate { get; set; }

        [IsFilterable, IsSortable, IsFacetable]
        public double? Rating { get; set; }

        public Address Address { get; set; }

        [IsFilterable, IsSortable]
        public GeographyPoint Location { get; set; }

        public Room[] Rooms { get; set; }
    }

En este objeto se definen los campos que hay en el indice ya configurados, pero a su vez definimos los atributos de cada campo, es decir, si se puede ordenar, si se puede usar el campo para filtros, si se puede retornar el campo en los resultados, etc, estos conceptos ya habian sido explicandos en uno de los videos anteriores.

Luego pasamos al controlador, en el contrador creamos una accion que sera ejecutada desde la vista, esta accion sera de tipo Post, y solo contiene la cadena de texto que queremos consultar.

  [HttpPost]
        public async Task<ActionResult> Index(SearchData model)
        {
            try
            {
                // Ensure the search string is valid.
                if (model.searchText == null)
                {
                    model.searchText = "";
                }

                // Make the Azure Search call.
                await RunQueryAsync(model);
            }

            catch
            {
                return View("Error", new ErrorViewModel { RequestId = "1" });
            }
            return View(model);
        }

Como pueden ver la logica es sencilla, si el campo searchtext es nulo, le asignamos una cadena de caracteres vacia y luego realizamos la busqueda utilizando el siguiente metodo:

   private async Task<ActionResult> RunQueryAsync(SearchData model)
        {
            InitSearch();           

            var parameters = new SearchParameters
            {
                // Enter Hotel property names into this list so only these values will be returned.
                // If Select is empty, all values will be returned, which can be inefficient.
                Select = new[] { "HotelName", "Description" }
            };

            // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search.
            model.resultList = await _indexClient.Documents.SearchAsync<Hotel>(model.searchText, parameters);

            // Display the results.
            return View("Index", model);
        }

En este metodo definimos primero las variables globales de Azure Search, es decir el serviceclient y el indexclient, luego definimos el objeto SearchParameters, quiza el objeto mas importante cuando estemos realizando busquedas, ya que nos permite definir las consultas, que campos utilizar para el ordenamiento, paginacion, y otras configuraciones avanzadas.

Y por ultimo realizamos la busqueda con el SearchAsync, que como pueden ver es un generic fuertemente tipado a Hotel, de esta manera podremos retornar el modelo a la vista y asi renderizar los resultados en pantalla.

Antes de mostrar el codigo de la vista que es realmente sencillo, es neceario explicar el metodo InitSearch

      private void InitSearch()
        {
            // Create a configuration using the appsettings file.
            _builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
            _configuration = _builder.Build();

            // Pull the values from the appsettings.json file.
            string searchServiceName = _configuration["SearchServiceName"];
            string queryApiKey = _configuration["SearchServiceQueryApiKey"];

            // Create a service and index client.
            _serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(queryApiKey));
            _indexClient = _serviceClient.Indexes.GetClient("hotels");
        }

Este metodo lee el archivo de configuracion que mostramos al principio de este articulo y luego obtiene los parametros necesarios para instanciar el SearchServiceClient. Estos son el nombre y el API key.

Una vez se ha instanciado el SearchServiceClient, procedemos con este mismo objeto a instancia el IndexClient, es este objeto el responsable de realizar las busquedas en Azure Search contra un indice especifico.

Ya por ultimo mostramos los resultados en la pagina, es decir pasamos a HTML los resultados de nuestro objeto Hotel.

<body>  
    <h1 class="sampleTitle">
        <img src="~/images/azure-logo.png" width="80" />
        Hotels Search
    </h1>

    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {
        // Display the search text box, with the search icon to the right of it.
        <div class="searchBoxForm">
            @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox" }) <input class="searchBoxSubmit" type="submit" value="">
        </div>

        @if (Model != null)
        {
            // Show the result count.
            <p class="sampleText">
                @Html.DisplayFor(m => m.resultList.Results.Count) Results
            </p>

            @for (var i = 0; i < Model.resultList.Results.Count; i++)
            {
                // Display the hotel name and description.
                @Html.TextAreaFor(m => Model.resultList.Results[i].Document.HotelName, new { @class = "box1" })
                @Html.TextArea($"desc{i}", Model.resultList.Results[i].Document.Description, new { @class = "box2" })
            }
        }
    }
</body>