Go Back

Working with Sitefinity Search

These are the notes for the “Working with Sitefinity Search” webinar.

Webinar Outline

  • Overview of Sitefinity Search
  • Creating new Search Indexes
  • Using Sitefinity Search Controls
  • Overview of Search Engine
  • Advanced Search Queries
  • Enabling wildcard & fuzzy searches
  • Emphasizing / Deemphasizing search results
  • Creating a Custom Search Provider

What is Lucene.Net?

Lucene is a text search engine library originally created in Java.  Lucene has been ported to other programming languages including Delphi, Perl, C#, C++, Python, Ruby and PHP.

Lucene Luke

http://www.getopt.org/luke/

Lucene Queries

http://lucene.apache.org/java/2_3_2/queryparsersyntax.html 

  • webinar
  • (title:webinar)
  • (title:webinar)(content:webinar)
  • (title:webinar)(content:webinar)^4
  • web?nar
  • webi*
  • tool~
  • sitefinity cms
  • “sitefinity cms”
  • sitefinity OR feature

Enabling Wildcard and Fuzzy Searches / Sample Queries

For the SearchResults control, change the EscapeSpecialChars property to False.

Stripping / Allowing Special Characters

  • Strip special characters.
  • Allow wildcards & quotes to be used.
<searchInputValidation>
  <add matchPattern="\A[\*\?\~][^\s]*" replacementString="" enabled="true" matchAlert="You can not start your query using wildcards, modify your query and try again.."/>
  <add matchPattern="[\!\^\(\)\{\}\[\]]" replacementString="" enabled="true" matchAlert="Strange characters should be enclosed between double quotation, modify your query and try again.."/>
</searchInputValidation>

Weighting Content Types

~/App_Data/Search/[Search_Index]/fieldsInfoProvider.xml

<?xml version="1.0" encoding="utf-8"?>
<fields>
  <field name="title" weight="1" indexAttribute="" filterTag="title" filterAttributes="" />
  <field name="keywords" weight="1" indexAttribute="content" filterTag="meta" filterAttributes="name:keywords;" />
  <field name="description" weight="1" indexAttribute="content" filterTag="meta" filterAttributes="name:description;" />
  <field name="script" weight="-1" indexAttribute="" filterTag="script" filterAttributes="" />
  <field name="style" weight="-1" indexAttribute="" filterTag="style" filterAttributes="" />
</fields>

Sample filter:

<fields>
  <field name="important" weight="2" indexAttribute="content" filterTag="div" filterAttributes="class:important;" />
</fields>

Getting Started with Custom Index Providers

Index Clients are configured in the ~/web.config

<indexClients>
  <add name="PageIndex" type="Telerik.Cms.Search.PageIndexProvider, Telerik.Cms" settingsControl="Telerik.Cms.Web.UI.PageIndexSettings, Telerik.Cms" viewSettingsControl="Telerik.Cms.Web.UI.SearchViewControl, Telerik.Cms" description="Provides indexing services for CMS Pages."/>
</indexClients>

~/App_Code/CustomIndex/CustomIndexProvider.cs

using System;
using System.Collections.Generic;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexProvider : IIndexingServiceClient
    {
        public CustomIndexProvider()
        {
        }

        public string Description
        {
            get { return "This is my description"; }
        }

        public IIndexerInfo[] GetContentToIndex()
        {
            return new IIndexerInfo[0];
        }

        public string[] GetUrlsToIndex()
        {
            return new string[0];
        }

        public event EventHandler<IndexEventArgs> Index;

        public void Initialize(IDictionary<string, string> settings)
        {
        }

        public string Name
        {
            get { return "CustomIndex"; }
        }
    }
}

~/App_Code/CustomIndex/CustomIndexSettings.cs

using System.Collections.Generic;
using System.Web.UI.WebControls;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexSettings : CompositeControl, ISettingsControl
    {
        public CustomIndexSettings()
        {
        }

        public IDictionary<string, string> GetSettings()
        {
            return settings; 
        }

        public void InitSettings(IDictionary<string, string> indexSettings)
        {
            settings = indexSettings;
        }

        private IDictionary<string, string> settings;
    }
}

~/App_Code/CustomIndex/CustomViewControl.cs

using System.Collections.Generic;
using System.Web.UI.WebControls;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomViewControl : CompositeControl, ISearchViewControl
    {
        public CustomViewControl() 
        {
        }

        public void InitializeSettings(IDictionary<string, string> indexSettings)
        {
        }
    }
}

~/web.config

<indexClients>
  <add name="CustomIndex" type="CustomIndex.CustomIndexProvider, App_Code" settingsControl="CustomIndex.CustomIndexSettings, App_Code" viewSettingsControl="CustomIndex.CustomViewControl, App_Code" />
</indexClients>

Indexing Content by URL

~/App_Code/CustomIndex/CustomIndexProvider.cs:

public string[] GetUrlsToIndex()
{
    // You must use FULL URLs
    var urls = new string[] 
        {
            "[url]/blog.aspx",
            "[url]/notes.aspx"
        };

    return urls;
}

Manually Supplying Data for Indexing

~/App_Code/CustomIndex/CustomIndexerInfo.cs

using System;
using System.Text;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexerInfo : IIndexerInfo
    {
        public CustomIndexerInfo()
        {
        }

        public string Culture
        {
            get { return string.Empty; }
        }

        public System.Text.Encoding Encoding
        {
            get { return Encoding.UTF8; }
        }

        public byte[] GetData()
        {
            string text = "Hello world!" +
                "<title>My Title</title>" +
                "<meta name=\"description\">My Description</meta>" +
                "<customField>My CustomField</customField>";

            return Encoding.GetBytes(text);
        }

        public Guid ItemID
        {
            get { return Guid.Empty; }
        }

        public string MimeType
        {
            get { return "text/html"; }
        }

        public string Path
        {
            get { return _url; }
        }

        public string ResolveIndexPath()
        {
            return Path;
        }

        private string _url = "~/fakeURL.html";
    }
}

~/App_Code/CustomIndex/CustomIndexProvider.cs

public IIndexerInfo[] GetContentToIndex()
{
    return new IIndexerInfo[] { new CustomIndexerInfo() };
}

~/App_Data/Search/CustomIndex/fieldsInfoProvider.xml

<?xml version="1.0" encoding="utf-8"?>
<fields>
  <field name="title" weight="1" indexAttribute="" filterTag="title" filterAttributes="" />
  <field name="keywords" weight="1" indexAttribute="content" filterTag="meta" filterAttributes="name:keywords;" />
  <field name="description" weight="1" indexAttribute="content" filterTag="meta" filterAttributes="name:description;" />
  <field name="script" weight="-1" indexAttribute="" filterTag="script" filterAttributes="" />
  <field name="style" weight="-1" indexAttribute="" filterTag="style" filterAttributes="" />
  <field name="customField" weight="2" indexAttribute="" filterTag="customField" filterAttributes="" />
</fields>

Final Custom Provider Code

~/App_Code/CustomIndex/CustomIndexProvider.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Telerik.Cms.Engine;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexProvider : IIndexingServiceClient
    {
        public CustomIndexProvider()
        {
        }

        /// <summary>
        /// Used by Sitefinity to pass the settings associated with the Search Provider.
        /// </summary>
        public void Initialize(IDictionary<string, string> indexSettings)
        {
            settings = indexSettings;
        }

        /// <summary>
        /// Defines the name of the provider. This name is used to mange providers within Indexing Service.
        /// </summary>
        public string Name
        {
            get { return "CustomIndexProvider"; }
        }

        /// <summary>
        /// Provides detailed description of the client
        /// </summary>
        public string Description
        {
            get { return "Description for CustomIndexProvider"; }
        }

        /// <summary>
        /// Gets content to index.
        /// </summary>
        public IIndexerInfo[] GetContentToIndex()
        {
            IList posts;
            var manager = new ContentManager("Blogs");

            if (SelectedBlogID == Guid.Empty)
            {
                // Retrieve all published blog posts sorted by publication date.
                posts = manager.GetContent(0, 0, "Publication_Date", ContentStatus.Published);
            }
            else
            {
                // Retrieve all published blog posts sorted by publication date with a specified parent blog ID.
                var ParentIDs = new Guid[] { SelectedBlogID };
                posts = manager.GetContent(0, 0, "Publication_Date", ContentStatus.Published, ParentIDs);
            }   // Loop through blog posts, add each to content index
            var contentItems = new List<IIndexerInfo>();
            foreach (IContent post in posts)
            {
                // Create IIndexerInfo object
                var contentInfo = new CustomIndexerInfo();
                contentInfo.ID = post.ID;
                contentInfo.Title = post.GetMetaData("Title").ToString();
                contentInfo.Content = post.Content.ToString();
                contentInfo.Url = post.UrlWithExtension;
                contentInfo.Tags = GetTags(manager.GetTags(post.ID));

                // Add content item to List
                contentItems.Add(contentInfo);
            }

            // Return content index
            return contentItems.ToArray();
        }

        /// <summary>
        /// Gets URLs to be indexed.
        /// </summary>
        public string[] GetUrlsToIndex()
        {
            return new string[0];
        }

        public event EventHandler<IndexEventArgs> Index;

        /// <summary>
        /// Get tags associated with a blog post and return them as a comma-separated string
        /// </summary>
        private string GetTags(IList listofTags)
        {
            string tags = "";
            if (listofTags.Count > 0)
            {
                var tagsBuilder = new StringBuilder();
                foreach (ITag tag in listofTags)
                {
                    tagsBuilder.Append(tag.TagName + ",");
                }
                tags = tagsBuilder.ToString().Remove(tagsBuilder.ToString().Length - 1, 1);
            }
            return tags;
        }

        /// <summary>
        /// Converts the "SelectedBlogID" setting from a string to a Guid.
        /// </summary>
        private Guid SelectedBlogID
        {
            get
            {
                try
                {
                    return new Guid(settings["SelectedBlogID"]);
                }
                catch
                {
                    return Guid.Empty;
                }
            }
        }

        IDictionary<string, string> settings;
    }
}

~/App_Code/CustomIndex/CustomIndexerInfo.cs

using System;
using System.Text;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexerInfo : IIndexerInfo
    {
        public CustomIndexerInfo()
        {
        }

        public Guid ID { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public string Tags { get; set; }
        public string Url { get; set; }

        /// <summary>
        /// Called by Sitefinity to get Data to be associated with a Search Content Item.
        /// </summary>
        public byte[] GetData()
        {
            string text = Content +
                "<title>" + Title + "</title>" +
                "<tags>" + Tags + "</tags>";

            return Encoding.GetBytes(text);
        }

        public Guid ItemID
        {
            get { return ID; }
        }

        public string MimeType
        {
            get { return "text/html"; }
        }

        public string Path
        {
            get { return Url; }
        }

        public string ResolveIndexPath()
        {
            return Url;
        }

        public string Culture
        {
            get { return string.Empty; }
        }

        public Encoding Encoding
        {
            get { return Encoding.UTF8; }
        }
    }
}

~/App_Code/CustomIndex/CustomSettings.cs

using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telerik.Blogs;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomIndexSettings : CompositeControl, ISettingsControl
    {
        public CustomIndexSettings()
        {
        }

        /// <summary>
        /// Creates the controls that will create the UI for this Settings Control.
        /// </summary>
        protected override void CreateChildControls()
        {
            // Get a list of all blogs.
            var manager = new BlogManager();
            var blogs = manager.GetBlogs();

            // Create a DropDownList
            BlogDropDown = new DropDownList();
            BlogDropDown.Items.Add(new ListItem("All blogs", Guid.Empty.ToString()));

            // Loop through each blog post and add to dropdownlist.
            foreach (IBlog blog in blogs)
            {
                var item = new ListItem(blog.Name, blog.ID.ToString());
                // Preselect blog post.
                if (SelectedBlogID == blog.ID)
                {
                    item.Selected = true;
                }

                BlogDropDown.Items.Add(item);
            }

            // This is an ugly mix of HTML & code.  In most cases, it would be better to create
            // an external template and load the template.
            Controls.Add(new LiteralControl("<div>"));
            Controls.Add(new LiteralControl("Blog: "));
            Controls.Add(BlogDropDown);
            Controls.Add(new LiteralControl("<br /><br /></div>"));
        }

        /// <summary>
        /// This method is called by Sitefinity to get the settings to be associated
        /// with this Search Provider.
        /// </summary>
        public IDictionary<string, string> GetSettings()
        {
            settings = new Dictionary<string, string>();
            settings.Add("SelectedBlogID", BlogDropDown.SelectedValue);
            return settings;
        }

        /// <summary>
        /// This method is executed by Sitefinity and used to load existing settings.
        /// </summary>
        public void InitSettings(IDictionary<string, string> indexSettings)
        {
            settings = indexSettings;
        }

        /// <summary>
        /// Converts the "SelectedBlogID" setting from a string to a Guid.
        /// </summary>
        private Guid SelectedBlogID
        {
            get
            {
                try
                {
                    return new Guid(settings["SelectedBlogID"]);
                }
                catch
                {
                    return Guid.Empty;
                }
            }
        }

        private IDictionary<string, string> settings;
        private DropDownList BlogDropDown;
    }
}

~/App_Code/CustomIndex/CustomViewControl.cs

using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telerik.Cms.Engine;
using Telerik.Blogs;
using Telerik.Framework.Search;

namespace CustomIndex
{
    public class CustomViewControl : CompositeControl, ISearchViewControl
    {
        public CustomViewControl()
        {
        }

        /// <summary>
        /// Used by Sitefinity to pass the settings associated with the Search Provider.
        /// </summary>
        public void InitializeSettings(IDictionary<string, string> indexSettings)
        {
            settings = indexSettings;
        }

        /// <summary>
        /// Creates the controls that will create the UI for this View Settings Control.
        /// </summary>
        protected override void CreateChildControls()
        {
            Controls.Clear();
            Controls.Add(new LiteralControl("<ul>"));
            Controls.Add(new LiteralControl("<li>"));
            Controls.Add(new LiteralControl("Blog: " + SelectedBlog));
            Controls.Add(new LiteralControl("</li>"));
            Controls.Add(new LiteralControl("</ul>"));
        }

        IDictionary<string, string> settings;

        /// <summary>
        /// Converts a Blog ID (guid) into the name associated with the Blog.
        /// </summary>
        private string SelectedBlog
        {
            get
            {
                try
                {
                    var BlogID = new Guid(settings["SelectedBlogID"]);
                    var manager = new BlogManager();
                    var blog = manager.GetBlog(BlogID);
                    return blog.Name;
                }
                catch
                {
                    return "All blogs";
                }
            }
        }
    }
}

 

Reference Links

Post a comment!
  1. Formatting options
       
     
     
     
     
       
  2. I'm sorry for the CAPTCHA. You have spammers to thank for this: