EpiSever Search: search for both pages and media

Vote:
 

Hello! I'm implementing a serch page which is using EpiServer Search, for creating the search result I'm using the CreateQuery method from the Alloy-project. This works fine when searching for pages, but when I'm searching for an image I get no hits at all.

Our image-class is inheriting from MedaData, so I don't get why we don't get any hits. But, If I change,

var query = new GroupQuery(LuceneOperator.AND);

to LuceneOperator.OR and search for an image all the images from the site are displayed in the search result. So, I at least know that they ar indexed.

How can I change this code so that I can search for both pages and files?

These are the searchRoots that I use as argument:

new[] { ContentReference.StartPage, ContentReference.GlobalBlockFolder, ContentReference.SiteBlockFolder, SiteDefinition.Current.GlobalAssetsRoot },

CreateQuery-method:

        private IQueryExpression CreateQuery(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch)
        {
            //Main query which groups other queries. Each query added
            //must match in order for a page or file to be returned.
            var query = new GroupQuery(LuceneOperator.AND);

            //Add free text query to the main query
            query.QueryExpressions.Add(new FieldQuery(searchText));

            //Search for pages using the provided language
            var pageTypeQuery = new GroupQuery(LuceneOperator.AND);
            pageTypeQuery.QueryExpressions.Add(new ContentQuery());
            pageTypeQuery.QueryExpressions.Add(new FieldQuery(languageBranch, Field.Culture));

            //Search for media without languages
            var contentTypeQuery = new GroupQuery(LuceneOperator.OR);
            contentTypeQuery.QueryExpressions.Add(new ContentQuery());
            contentTypeQuery.QueryExpressions.Add(pageTypeQuery);

            query.QueryExpressions.Add(contentTypeQuery);

            //Create and add query which groups type conditions using OR
            var typeQueries = new GroupQuery(LuceneOperator.OR);
            query.QueryExpressions.Add(typeQueries);

            foreach (var root in searchRoots)
            {
                var contentRootQuery = new VirtualPathQuery();
                contentRootQuery.AddContentNodes(root, _contentLoader);
                typeQueries.QueryExpressions.Add(contentRootQuery);
            }

            var accessRightsQuery = new AccessControlListQuery();
            accessRightsQuery.AddAclForUser(PrincipalInfo.Current, context);
            query.QueryExpressions.Add(accessRightsQuery);

            return query;
        }
#140676
Oct 27, 2015 10:39
Vote:
 

Anyone? I also noticed this method for searching for files, see code below. 

http://world.episerver.com/Documentation/Class-library/?documentId=cms/7/c6827903-02d8-c5f4-cdc0-7e6ba478f9d8

But since the images etc. are resided in SiteDefinition.Current.GlobalAssetsRoot which is a ContentReference, I'm not sure how to use this method since searchRoot is of type VersioningDirectory. I tried rewriting it to use "AddContentNodes" instead of "AddDirectoryNodes" and passing in GlobalAssetsRoot, but without success.

I would really appreciate some help with this, have a nice weekend!

public IEnumerable<string> FindFiles(string searchQuery, VersioningDirectory searchRoot, int pagingNumber, int pagingSize)
    {
        // The group query is needed to combine the different criteria
        GroupQuery groupQuery = new GroupQuery(LuceneOperator.AND);

        // The unified file query makes sure we only get hits that are files
        groupQuery.QueryExpressions.Add(new UnifiedFileQuery());

        // The field query contains the search phrase
        groupQuery.QueryExpressions.Add(new FieldQuery(searchQuery));

        // The virtual path query makes sure that we only get hits for children of the specified search root
        VirtualPathQuery pathQuery = new VirtualPathQuery();
        pathQuery.AddDirectoryNodes(searchRoot);
        groupQuery.QueryExpressions.Add(pathQuery);

        // The access control list query will remove any files the user doesn't have read access to
        AccessControlListQuery aclQuery = new AccessControlListQuery();
        aclQuery.AddAclForUser(PrincipalInfo.Current, HttpContext.Current);
        groupQuery.QueryExpressions.Add(aclQuery);

        SearchResults results = Locate.SearchHandler().GetSearchResults(groupQuery, pagingNumber, pagingSize);

        foreach (var hit in results.IndexResponseItems)
        {
            // Return the virtual path for each matching file.
            yield return hit.Uri.ToString();
        }
    }
#140824
Oct 30, 2015 16:08
Vote:
 

Hi Tobias,

Did you find a solution?

Marshall

#174736
Feb 02, 2017 19:16
Vote:
 

Hi! My solution was to do one search for media and one for pages and then combine them using AddRange. Pasted all code below, it's a bit extensive but if you start looking in the method Search in PageSearchService, there is the method you would call from your SearchPageController or similar, and there you can see the combining of the two searches and the underlying logic.

public class SearchService
    {
        private readonly SearchHandler _searchHandler;
        private readonly IContentLoader _contentLoader;

        public SearchService(SearchHandler searchHandler, IContentLoader contentLoader)
        {
            _searchHandler = searchHandler;
            _contentLoader = contentLoader;
        }

        public virtual bool IsActive
        {
            get { return SearchSettings.Config.Active; }
        }

        public virtual SearchResults SearchForContent(string searchText, IEnumerable<ContentReference> contentSearchRoots, HttpContextBase context, string languageBranch, int maxResults)
        {
            var query = CreateContentQuery(searchText, contentSearchRoots, context, languageBranch);
            return _searchHandler.GetSearchResults(query, 1, maxResults);
        }

        private IQueryExpression CreateContentQuery(string searchText, IEnumerable<ContentReference> searchRoots,
            HttpContextBase context, string languageBranch)
        {
            var query = new GroupQuery(LuceneOperator.AND);

            float x = 0.5f;
            query.QueryExpressions.Add(new FuzzyQuery(searchText, x));

            //Create and add query which groups type conditions using OR
            var typeQueries = new GroupQuery(LuceneOperator.OR);
            query.QueryExpressions.Add(typeQueries);

            foreach (var root in searchRoots)
            {
                var contentRootQuery = new VirtualPathQuery();
                contentRootQuery.AddContentNodes(root, _contentLoader);
                typeQueries.QueryExpressions.Add(contentRootQuery);
            }

            var accessRightsQuery = new AccessControlListQuery();
            accessRightsQuery.AddAclForUser(PrincipalInfo.Current, context);
            query.QueryExpressions.Add(accessRightsQuery);

            return query;
        }

        public virtual SearchResults SearchForPages(string searchText, IEnumerable<ContentReference> searchRoots, HttpContextBase context, string languageBranch, int maxResults)
        {
            var query = CreatePageQuery(searchText, searchRoots, context, languageBranch);
            return _searchHandler.GetSearchResults(query, 1, maxResults);
        }

        private IQueryExpression CreatePageQuery(string searchText, IEnumerable<ContentReference> searchRoots, HttpContextBase context, string languageBranch)
        {
            //Main query which groups other queries. Each query added
            //must match in order for a page or file to be returned.
            var query = new GroupQuery(LuceneOperator.AND);

            //Add free text query to the main query
            query.QueryExpressions.Add(new FieldQuery(searchText));

            //Search for pages using the provided language
            var pageTypeQuery = new GroupQuery(LuceneOperator.AND);
            pageTypeQuery.QueryExpressions.Add(new ContentQuery<PageData>());
            pageTypeQuery.QueryExpressions.Add(new FieldQuery(languageBranch, Field.Culture));

            //Search for media without languages
            var contentTypeQuery = new GroupQuery(LuceneOperator.OR);
            contentTypeQuery.QueryExpressions.Add(new ContentQuery<MediaData>());
            contentTypeQuery.QueryExpressions.Add(pageTypeQuery);

            query.QueryExpressions.Add(contentTypeQuery);

            //Create and add query which groups type conditions using OR
            var typeQueries = new GroupQuery(LuceneOperator.OR);
            query.QueryExpressions.Add(typeQueries);

            foreach (var root in searchRoots)
            {
                var contentRootQuery = new VirtualPathQuery();
                contentRootQuery.AddContentNodes(root, _contentLoader);
                typeQueries.QueryExpressions.Add(contentRootQuery);
            }

            var accessRightsQuery = new AccessControlListQuery();
            accessRightsQuery.AddAclForUser(PrincipalInfo.Current, context);
            query.QueryExpressions.Add(accessRightsQuery);

            return query;
        }
    }
public class PagesSearchService
    {
        private readonly SearchService _searchService;
        private readonly ContentSearchHandler _contentSearchHandler;
        private readonly UrlResolver _urlResolver;
        private readonly TemplateResolver _templateResolver;

        public PagesSearchService(SearchService searchService,
            ContentSearchHandler contentSearchHandler,
            TemplateResolver templateResolver,
            UrlResolver urlResolver)
        {
            _searchService = searchService;
            _contentSearchHandler = contentSearchHandler;
            _templateResolver = templateResolver;
            _urlResolver = urlResolver;
        }

        /// <summary>
        /// Performs a search for pages and media and maps each result to the view model class SearchHit.
        /// </summary>
        /// <remarks>
        /// The search functionality is handled by the injected SearchService in order to keep the controller simple.
        /// Uses EPiServer Search. For more advanced search functionality such as keyword highlighting,
        /// facets and search statistics consider using EPiServer Find.
        /// </remarks>
        public IEnumerable<SearchPageViewModel.SearchHit> Search(string searchText, IEnumerable<ContentReference> searchRoots, IEnumerable<ContentReference> contentSearchRoots, HttpContextBase context, string languageBranch, int maxHits)
        {
            var pageSearchResults = _searchService.SearchForPages(searchText, searchRoots, context, languageBranch, maxHits);
            var contentSearchResults = _searchService.SearchForContent(searchText, contentSearchRoots, context,
                languageBranch, maxHits);

            pageSearchResults.IndexResponseItems.AddRange(contentSearchResults.IndexResponseItems);

            return pageSearchResults.IndexResponseItems.GroupBy(p => p.DisplayText).Select(g => g.First()).SelectMany(CreateHitModel);
        }

        private IEnumerable<SearchPageViewModel.SearchHit> CreateHitModel(IndexResponseItem responseItem)
        {
            var content = _contentSearchHandler.GetContent<IContent>(responseItem);

            if (content != null)
            {
                if (content is ArticleBasePage || content is MediaData)
                {
                    yield return CreatePageHit(content, responseItem);
                }
            }
        }

        private bool HasTemplate(IContent content)
        {
            return _templateResolver.HasTemplate(content, TemplateTypeCategories.Page);
        }

        private bool IsPublished(IVersionable content)
        {
            if (content == null)
                return true;
            return content.Status.HasFlag(VersionStatus.Published);
        }

        private SearchPageViewModel.SearchHit CreatePageHit(IContent content, IndexResponseItem item)
        {            
            var contentPage = content as ContentPage;
            var mediaData = content as MediaData;

            if (contentPage != null)
            {
                return GetContentPageSearchHit(item, contentPage);
            }
            else if (mediaData != null)
            {
                return GetMediaDataSearchHit(item, mediaData);
            }

            return null;
        }

        private SearchPageViewModel.SearchHit GetContentPageSearchHit(IndexResponseItem item, ContentPage contentPage)
        {
            return new SearchPageViewModel.SearchHit
            {
                Title = (contentPage != null ? contentPage.Heading : item.Title),
                Url = (contentPage != null ? _urlResolver.GetUrl(contentPage.ContentLink) : ""),
                Excerpt = item.DisplayText,
                PageLink = (contentPage != null ? contentPage.ContentLink.ToPageReference() : null),
                PublishDate = contentPage != null ? contentPage.StartPublish : DateTime.MinValue
            };
        }

        private SearchPageViewModel.SearchHit GetMediaDataSearchHit(IndexResponseItem item, MediaData mediaData)
        {
            var mmxImage = mediaData as MMXImageFile;
            string excerptText = "";

            if (mmxImage != null)
            {
                excerptText = GetMmxImageExcerptText(mmxImage);
            }

            if (string.IsNullOrEmpty(excerptText)) excerptText = item.DisplayText;           

            return new SearchPageViewModel.SearchHit
            {
                Title = item.Title,
                Url = _urlResolver.GetUrl(mediaData.ContentLink),
                Excerpt = excerptText,
                PageLink = mediaData.ContentLink.ToPageReference(),
                PublishDate = mediaData.StartPublish ?? DateTime.MinValue
            };
        }

        private string GetMmxImageExcerptText(MMXImageFile image)
        {
            StringBuilder excerpt = new StringBuilder();

            if (!string.IsNullOrEmpty(image.Title)) excerpt.Append(image.Title);
            if (!string.IsNullOrEmpty(image.Title) && !string.IsNullOrEmpty(image.AlternativeText)) excerpt.Append(". ");
			if (!string.IsNullOrEmpty(image.AlternativeText)) excerpt.Append(image.AlternativeText);

			return excerpt.ToString();
        }

        public bool IsSearchActive
        {
            get { return _searchService.IsActive; }
        }
    }

[Pasting files is not allowed]

#174747
Feb 03, 2017 7:49
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.