How to add a searcher to Accelerator

This article describes how to add a new searcher to Accelerator

Accelerator has a Quick search and Search result page and for both of them, the search must be implemented.
Before how to implement a search in the Accelerator all steps must be done from the How to add a search index article.

For both searches need to add OrganizationSearchService class to Litium.Accelerator application and his implementation to Litium.Accelerator.Elasticsearch application.

OrganizationSearchService

using Litium.Accelerator.ViewModels.Search;
using Litium.Framework.Search;
using Litium.Runtime.DependencyInjection;

namespace Litium.Accelerator.Search
{
    [Service(ServiceType = typeof(OrganizationSearchService), Lifetime = DependencyLifetime.Singleton)]
    public abstract class OrganizationSearchService
    {
      public abstract SearchResponse Search(SearchQuery searchQuery);
      public abstract SearchResult Transform(SearchQuery searchQuery, SearchResponse searchResponse, bool includeScore = false);
    }
}

OrganizationSearchServiceImpl

using System;
using System.Collections.Generic;
using System.Linq;
using Litium.Accelerator.Search;
using Litium.Accelerator.ViewModels.Search;
using Litium.Foundation.Modules.ExtensionMethods;
using Litium.Framework.Search;
using Litium.Search;
using Nest;

namespace Litium.Accelerator.Searching
{
    internal class OrganizationSearchServiceImpl : OrganizationSearchService
    {
        private readonly SearchClientService _searchClientService;

        public OrganizationSearchServiceImpl(SearchClientService searchClientService)
        {
            _searchClientService = searchClientService;
        }

        public override SearchResponse Search(SearchQuery searchQuery)
        {
            if (!_searchClientService.IsConfigured)
            {
                //ES is not configured
                return new SearchResponse();
            }
            var fuzziness = searchQuery.Text.Length > 2 ? Fuzziness.EditDistance(2) : Fuzziness.Auto;
            return new ElasticSearchResponse<OrganizationDocument>(_searchClientService
                .Search<OrganizationDocument>(null, selector => selector
                    .From((searchQuery.PageNumber - 1) * searchQuery.PageSize)
                    .Size(searchQuery.PageSize)
                    .Query(q =>
                        q.Match(x => x.Field(z => z.Name).Query(searchQuery.Text).Fuzziness(fuzziness).Boost(2).SynonymAnalyzer())
                        || q.Match(x => x.Field(z => z.Content).Query(searchQuery.Text).Fuzziness(fuzziness)))
                )
            );
        }

        public override SearchResult Transform(SearchQuery searchQuery, SearchResponse searchResponse, bool includeScore = false)
        {
            if (!_searchClientService.IsConfigured)
            {
                //ES is not configured
                return new SearchResult();
            }
            return new SearchResult
            {
                Items = new Lazy<IEnumerable<SearchResultItem>>(() =>
                {
                    var organizations = ((ElasticSearchResponse<OrganizationDocument>)searchResponse).Response.Hits.Select(x => x.Source.OrganizationSystemId.GetOrganization());
                    return organizations.Where(c => c != null).Select(x => new SearchResultItem
                    {
                        Id = x.SystemId,
                        Name = x.Name,
                        Score = includeScore ? searchResponse.Hits.Where(z => z.Id == x.SystemId.ToString()).Select(z => z.Score).FirstOrDefault() : default
                    });
                }),
                PageSize = searchQuery.PageSize.GetValueOrDefault(100000),
                Total = searchResponse.TotalHitCount
            };
        }
    }
}


Quick search
QuickSearchResultViewModelBuilder uses BaseSearcher to get all searchers for the quick search.

OrganizationSearcher

using System.Collections.Generic;
using System.Linq;
using Litium.Accelerator.Routing;
using Litium.Accelerator.Search;
using Litium.Accelerator.ViewModels.Search;
using Litium.Runtime.AutoMapper;

namespace Litium.Accelerator.Searchers
{
    public class OrganizationSearcher : BaseSearcher
    {
        private readonly RequestModelAccessor _requestModelAccessor;
        private readonly OrganizationSearchService _organizationSearchService;

        public OrganizationSearcher(OrganizationSearchService organizationSearchService, RequestModelAccessor requestModelAccessor)
        {
            _requestModelAccessor = requestModelAccessor;
            _organizationSearchService = organizationSearchService;
        }

        public override int SortOrder => 200;

        public override int PageSize => 10;

        public override string ModelKey => "Organizations";

        public override SearchResult QueryCompact(string query, bool includeScore = false)
        {
            var searchQuery = _requestModelAccessor.RequestModel.SearchQuery.Clone();
            searchQuery.Text = query;
            searchQuery.PageNumber = 1;
            searchQuery.PageSize = PageSize;

            var searchResponse = _organizationSearchService.Search(searchQuery);
            if (searchResponse == null)
            {
                return null;
            }

            return _organizationSearchService.Transform(searchQuery, searchResponse, includeScore: includeScore);
        }

        public override IEnumerable<SearchItem> ToSearchItems(IEnumerable<SearchResultItem> items)
        {
            return items.Select(x => x.MapTo<SearchItem>()); 
        }
    }
}

Now you can search the organization on a site in Quick search

Quick search.PNG

Search result page
SearchResultViewModelBuilder
is used to build the search result page.
To include organizations in the search result need to call OrganizationSearchService.Search method inside the SearchResultViewModelBuilder.Build() method.

SearchResultViewModelBuilder

using System;
using System.Collections.Generic;
using System.Linq;
using Litium.Accelerator.Builders.Product;
using Litium.Accelerator.Constants;
using Litium.Accelerator.Routing;
using Litium.Accelerator.Search;
using Litium.Accelerator.ViewModels;
using Litium.Accelerator.ViewModels.Search;

namespace Litium.Accelerator.Builders.Search
{
    public class SearchResultViewModelBuilder : IViewModelBuilder<SearchResultViewModel>
    {
        private readonly RequestModelAccessor _requestModelAccessor;
        private readonly ProductSearchService _productSearchService;
        private readonly ProductItemViewModelBuilder _productItemBuilder;
        private readonly PageSearchService _pageSearchService;
        private readonly CategorySearchService _categorySearchService;
        private readonly OrganizationSearchService _organizationSearchService;

        public SearchResultViewModelBuilder(RequestModelAccessor requestModelAccessor, ProductSearchService productSearchService, ProductItemViewModelBuilder productItemBuilder,
           PageSearchService pageSearchService, CategorySearchService categorySearchService, OrganizationSearchService organizationSearchService)
        {
            _requestModelAccessor = requestModelAccessor;
            _productSearchService = productSearchService;
            _productItemBuilder = productItemBuilder;
            _pageSearchService = pageSearchService;
            _categorySearchService = categorySearchService;
            _organizationSearchService = organizationSearchService;
        }

        public virtual SearchResultViewModel Build()
        {
            var searchQuery = _requestModelAccessor.RequestModel.SearchQuery.Clone();
            var searchResult = new SearchResultViewModel() { SearchTerm = searchQuery.Text, ContainsFilter = searchQuery.ContainsFilter() };

            if (!string.IsNullOrWhiteSpace(searchQuery.Text))
            {
                BuildProducts(searchResult, searchQuery);
                BuildOtherSearchResult(searchResult, searchQuery);
            }

            return searchResult;
        }

        private void BuildProducts(SearchResultViewModel searchResult, SearchQuery searchQuery)
        {
            if (searchQuery.PageSize == null)
            {
                var pageSize = _requestModelAccessor.RequestModel.WebsiteModel.Fields.GetValue<int?>(AcceleratorWebsiteFieldNameConstants.ProductsPerPage) ?? DefaultWebsiteFieldValueConstants.ProductsPerPage;
                searchQuery.PageSize = pageSize;
            }

            var searchResponse = _productSearchService.Search(searchQuery, searchQuery.Tags, true, true, true);
            var searchResults = searchResponse == null ? null : _productSearchService.Transform(searchQuery, searchResponse);

            if (searchResults != null)
            {
                searchResult.Products = searchResults.Items.Value.Cast<ProductSearchResult>().Select(c => _productItemBuilder.Build(c.Item)).ToList();
                searchResult.Pagination = new PaginationViewModel(searchResults.Total, searchQuery.PageNumber, searchResults.PageSize);
            }
        }

        private void BuildOtherSearchResult(SearchResultViewModel searchResult, SearchQuery searchQuery)
        {
            var p = searchQuery.PageSize;
            searchQuery.PageNumber = 1;
            searchQuery.PageSize = 100;

            var searchResponse = _pageSearchService.Search(searchQuery);
            var pageSearchResult = searchResponse != null ? _pageSearchService.Transform(searchQuery, searchResponse, true) : null;

            searchResponse = _categorySearchService.Search(searchQuery);
            var categorySearchResult = searchResponse != null ? _categorySearchService.Transform(searchQuery, searchResponse, true) : null;

            searchResponse = _organizationSearchService.Search(searchQuery);
            var organizationSearchResult = searchResponse != null ? _organizationSearchService.Transform(searchQuery, searchResponse, true) : null;

            searchQuery.PageSize = p;
            searchResult.OtherSearchResult = new SearchResult
            {
                PageSize = 100,
                Total = (pageSearchResult?.Total ?? 0) + (categorySearchResult?.Total ?? 0) + (organizationSearchResult?.Total ?? 0),
                Items = new Lazy<IEnumerable<SearchResultItem>>(() => (pageSearchResult == null ? new SearchResultItem[0] : pageSearchResult.Items.Value)
                    .Concat(categorySearchResult == null ? new SearchResultItem[0] : categorySearchResult.Items.Value)
                    .Concat(organizationSearchResult == null ? new SearchResultItem[0] : organizationSearchResult.Items.Value)
                    .OrderByDescending(x => x.Score))
            };
        }
    }
}