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