Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jason D 66 posts 218 karma points
    Sep 01, 2020 @ 03:30
    Jason D
    0

    Advanced Examine Search Implementation

    I followed Paul's article regarding an advanced search functionality, which I'm working to replace our previous predictive search, and I can't seem to get it to bring back results. It seems really close, as it's compiling, running criteria, etc. Any ideas?

    I have a working Ajax form, view models, etc. It just seems to not get any results at the level of SearchUsingExamine.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Examine;
    using Examine.Search;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Examine;
    using USD.CMS.Models;
    
    namespace USD.CMS.Helpers
        {
        /// <summary>
        /// A helper class giving you everything you need for searching with Examine.
        /// </summary>
        public class SearchHelper
            {
    
            private string _docTypeAliasFieldName { get { return "nodeTypeAlias"; } }
            private Umbraco.Web.UmbracoHelper _uHelper { get; set; }
    
            /// <summary>
            /// Default constructor for SearchHelper
            /// </summary>
            /// <param name="uHelper">An umbraco helper to use in your class</param>
            public SearchHelper(Umbraco.Web.UmbracoHelper uHelper)
                {
                _uHelper = uHelper;
                }
    
            public SearchHelper()
                {
                }
    
            /// <summary>v
            /// Gets the search results model from the search term/// 
            /// </summary>
            /// <param name="searchModel">The search model with search term and other settings in it</param>
            /// <param name="allKeys">The form keys that were submitted</param>
            /// <returns>A SearchResultsModel object loaded with the results</returns>
            public SearchResultsModel GetSearchResults(SearchViewModel searchModel, string[] allKeys)
                {
                SearchResultsModel resultsModel = new SearchResultsModel();
                resultsModel.SearchTerm = searchModel.SearchTerm;
                resultsModel.PageNumber = GetPageNumber(allKeys);
    
                ISearchResults allResults = SearchUsingExamine(searchModel.DocTypeAliases.Split(','), searchModel.SearchGroups);
                resultsModel.TotalItemCount = (int)allResults.TotalItemCount;
                resultsModel.Results = GetResultsForThisPage(allResults, resultsModel.PageNumber, searchModel.PageSize);
    
                resultsModel.PageCount = Convert.ToInt32(Math.Ceiling((decimal)resultsModel.TotalItemCount / (decimal)searchModel.PageSize));
                resultsModel.PagingBounds = GetPagingBounds(resultsModel.PageCount, resultsModel.PageNumber, searchModel.PagingGroupSize);
                return resultsModel;
                }
    
            /// <summary>
            /// Takes the examine search results and return the content for each page
            /// </summary>
            /// <param name="allResults">The examine search results</param>
            /// <param name="pageNumber">The page number of results to return</param>
            /// <param name="pageSize">The number of items per page</param>
            /// <returns>A collection of content pages for the page of results</returns>
            private IEnumerable<IPublishedContent> GetResultsForThisPage(ISearchResults allResults, int pageNumber, int pageSize)
                {
                return allResults.Skip((pageNumber - 1) * pageSize).Take(pageSize).Select(x => _uHelper.ContentQuery.Content(Convert.ToInt32(x.Id)));
                }
    
            /// <summary>
            /// Performs a lucene search using Examine.
            /// </summary>
            /// <param name="documentTypes">Array of document type aliases to search for.</param>
            /// <param name="searchGroups">A list of search groupings, if you have more than one group it will apply an and to the search criteria</param>
            /// <returns>Examine search results</returns>
            public Examine.ISearchResults SearchUsingExamine(string[] documentTypes, List<SearchGroup> searchGroups)
                {
                IBooleanOperation queryNodes = null;
                if (ExamineManager.Instance.TryGetIndex("ExternalIndex", out var index))
                    {
                    var searcher = index.GetSearcher();
                    var searchCriteria = searcher.CreateQuery(IndexTypes.Content);
    
    
                    queryNodes = searchCriteria.GroupedOr(new string[] { "" }, "0", "");
    
    
                    if (documentTypes != null && documentTypes.Length > 0)
                        {
                        //only get results for documents of a certain type
                        queryNodes = queryNodes.And().GroupedOr(new string[] { _docTypeAliasFieldName }, documentTypes);
                        }
                    if (searchGroups != null && searchGroups.Any())
                        {
                        foreach (SearchGroup searchGroup in searchGroups)
                            {
                            queryNodes = queryNodes.And().GroupedOr(searchGroup.FieldsToSearchIn, searchGroup.SearchTerms);
                            }
                        }
    
                    }
                return queryNodes.Execute();
                }
    
            /// <summary>
            /// Gets the page number from the form keys
            /// </summary>
            /// <param name="formKeys">All of the keys on the form</param>
            /// <returns>The page number</returns>
            public int GetPageNumber(string[] formKeys)
                {
                int pageNumber = 1;
                const string NAME_PREFIX = "page";
                const char NAME_SEPARATOR = '-';
                if (formKeys != null)
                    {
                    string pagingButtonName = formKeys.Where(x => x.Length > NAME_PREFIX.Length && x.Substring(0, NAME_PREFIX.Length).ToLower() == NAME_PREFIX).FirstOrDefault();
                    if (!string.IsNullOrEmpty(pagingButtonName))
                        {
                        string[] pagingButtonNameParts = pagingButtonName.Split(NAME_SEPARATOR);
                        if (pagingButtonNameParts.Length > 1)
                            {
                            if (!int.TryParse(pagingButtonNameParts[1], out pageNumber))
                                {
                                //pageNumber already set in tryparse
                                }
                            }
                        }
                    }
                return pageNumber;
                }
    
            /// <summary>
            /// Works out which pages the paging should start and end on
            /// </summary>
            /// <param name="pageCount">The number of pages</param>
            /// <param name="pageNumber">The current page number</param>
            /// <param name="groupSize">The number of items per page</param>
            /// <returns>A PagingBoundsModel containing the paging bounds settings</returns>
            public PagingBoundsModel GetPagingBounds(int pageCount, int pageNumber, int groupSize)
                {
                int middlePageNumber = (int)(Math.Ceiling((decimal)groupSize / 2));
                int pagesBeforeMiddle = groupSize - (int)middlePageNumber;
                int pagesAfterMiddle = groupSize - (pagesBeforeMiddle + 1);
                int startPage = 1;
                if (pageNumber >= middlePageNumber)
                    {
                    startPage = pageNumber - pagesBeforeMiddle;
                    }
                else
                    {
                    pagesAfterMiddle = groupSize - pageNumber;
                    }
                int endPage = pageCount;
                if (pageCount >= (pageNumber + pagesAfterMiddle))
                    {
                    endPage = (pageNumber + pagesAfterMiddle);
                    }
                bool showFirstButton = startPage > 1;
                bool showLastButton = endPage < pageCount;
                return new PagingBoundsModel(startPage, endPage, showFirstButton, showLastButton);
                }
    
            }
        }
    
  • Dee 118 posts 338 karma points
    Sep 01, 2020 @ 08:32
    Dee
    1

    Hey Jason,

    this is my approach, also triggered via Ajax call, and it's working fine. Maybe you get an idea, whats a bit wrong in your approach and I hope it helps you:

            [HttpGet]
            public SearchResponse Search(string query, int page, int siteId, string culture, int amount = 0)
            {
                query = query.Trim();
                string city = determineCity(ref query);
                query = removeFillwords(query);
    
                SearchResponse searchResponse = new SearchResponse()
                {
                    Result = new List<AjaxSearchResultsModel>(),
                    ResultCount = 0
                };
    
                IPublishedContent publishedContent = this.Umbraco.Content(siteId);
                IIndex index;
    
                if (!ExamineManager.Instance.TryGetIndex("ExternalIndex", out index) || !(index is IUmbracoIndex))
                    throw new InvalidOperationException("The required index ExternalIndex was not found");
    
                string[] fields = new string[3]
                {
                    //string.IsNullOrEmpty(culture) ? "nodeName" : "nodeName_" + culture.ToLower()
                    "nodeName",
                    "metaDescription",
                    "metaKeywords"
                };
    
                //string fieldName = string.IsNullOrEmpty(culture) ? "__Published" : "__Published_" + culture.ToLower();
                string fieldName = "__Published";
    
                getAndSetSearchResults(query, page, culture, searchResponse, publishedContent, index, fields, fieldName, SearchType.Content, amount);
                getAndSetSearchResults(query, page, culture, searchResponse, publishedContent, index, fields, fieldName, SearchType.Category, amount);
                getAndSetMemberSearchResults(query, city, searchResponse, amount);
    
                return searchResponse;
            }
    
            private void getAndSetSearchResults(string query, int page, string culture, SearchResponse searchResponse, IPublishedContent publishedContent, IIndex index, string[] fields, string fieldName, SearchType searchType, int amount)
            {
                var booleanOperation = index.GetSearcher().CreateQuery("content", BooleanOperation.And).ManagedQuery(query.Length == 3 ? query + "*" : query, fields).And().Field(fieldName, "y").And().GroupedOr((IEnumerable<string>)new string[1]
                {
                    "__NodeTypeAlias"
                }, nameof(page), "home", "blogPost", "blog", "subscription", "submitReview", "registration");
    
                if (searchType == SearchType.Category)
                {
                    booleanOperation = index.GetSearcher().CreateQuery("content", BooleanOperation.And).ManagedQuery(query.Length == 3 ? query + "*" : query, fields).And().Field(fieldName, "y").And().GroupedOr((IEnumerable<string>)new string[1]
                    {
                        "__NodeTypeAlias"
                    }, nameof(page), "providerOverview");
                }
                else if (searchType == SearchType.Faq)
                {
                    booleanOperation = index.GetSearcher().CreateQuery("content", BooleanOperation.And).ManagedQuery(query.Length == 3 ? query + "*" : query, fields).And().Field(fieldName, "y").And().GroupedOr((IEnumerable<string>)new string[1]
                    {
                        "__NodeTypeAlias"
                    }, nameof(page), "faqItem");
                }            
    
                if (publishedContent != null)
                {
                    booleanOperation = booleanOperation.And().GroupedOr((IEnumerable<string>)new string[1]
                    {
                        "__Path"
                    }, (publishedContent.Path + ",*").MultipleCharacterWildcard(), (publishedContent.Path ?? "").Escape());
                }
    
                if (amount == 0)
                {
                    if (searchType == SearchType.Category)
                        amount = 4;
                    else
                        amount = 3;
                }
    
    
                long totalRecords;
                IEnumerable<PublishedSearchResult> publishedContentSearchResults = this.Umbraco.ContentQuery.Search((IQueryExecutor)booleanOperation.Not().Field("hideFromSearch", "1"), (page - 1) * amount, amount, out totalRecords);
                searchResponse.ResultCount = totalRecords;
                foreach (PublishedSearchResult publishedSearchResult in publishedContentSearchResults)
                {
                    IPublishedContent content = publishedSearchResult.Content;
    
                    if (!searchResponse.Result.Where(x => x.Id == content.Id).Any())
                    {
                        searchResponse.Result.Add(new AjaxSearchResultsModel()
                        {
                            Id = content.Id,
                            Title = content.Name(culture),
                            Url = content.Url(culture, UrlMode.Auto),
                            UmbracoUrlAlias = content.AliasUrl<string>(),
                            //Description = content.Value<string>("MetaDescription", culture, (string)null, new Fallback(), (string)null),
                            Description = content.Value<string>("MetaDescription"),
                            ImageUrl = content.Value<IPublishedContent>("metaImage", (string)null, (string)null, new Fallback(), (IPublishedContent)null)?.Url,
                            Category = content.ContentType.Alias
                        });
                    }                
                }
            }
    
  • Jason D 66 posts 218 karma points
    Sep 01, 2020 @ 18:45
    Jason D
    1

    Thanks Dee! Helps a ton!

  • Jason D 66 posts 218 karma points
    Sep 01, 2020 @ 19:24
    Jason D
    0

    If I may ask, is the "SearchType" a model? Ie., "SearchType.Content, SearchType.Categories"...

  • Dee 118 posts 338 karma points
    Sep 01, 2020 @ 19:27
    Dee
    0

    Yes, its a custom class for me to differ between content, category and member

Please Sign in or register to post replies

Write your reply to:

Draft