Sample Custom LiveContent Indexer

The problem was that LiveContent keeps the data in XML format in a single database column. That makes it very difficult to operate and to extend the search capabilities. 

The sample plugin below will read all items in the LiveContent table, decode the XML and index each part. Furthermore, it also builds a custom html formatted description with an image tag. This will show perfectly well in the search results as an image, assuming the highlighter has been disabled (otherwise the description would get built automatically from the content).


public class IndexLiveContent : ISearchBoostIndexer
    {
        public void Index(NLog.Logger logger, LuceneStorage dataStore, 
                          GlobalConfiguration config, IndexingStatistics stats)
        {
            lock (typeof(IndexLiveContent)) {
                logger.Info("Indexing images from Live Content modules...");

                SearchBoostController sbCtrl = new SearchBoostController();
                ModuleController modCtrl = new ModuleController();

                // clear existing content; where "mycontent" identifies items generated by 
                //   this plugin; change it to anything relevant
                dataStore.ClearIndexedItems("SubType", "mycontent"); 

                using (IDataReader dr = SqlHelper.ExecuteReader(
                            DotNetNuke.Common.Utilities.Config.GetConnectionString(), 
                            System.Data.CommandType.Text, "SELECT * FROM LiveContent")) {
                    while (dr.Read()) {

                        int modId = Convert.ToInt32(dr["modID"].ToString());
                        ModuleInfo mod = modCtrl.GetModule(modId, -1);
                        if (mod == null)
                            continue;

                        logger.Debug("Inddexing module {0}...", modId);

                        XmlDocument xmlData = new XmlDocument();
                        xmlData.LoadXml(dr["DataV3"].ToString());

                        foreach (XmlNode xmlContentNode in 
                                 xmlData.DocumentElement.SelectNodes("Elements/anyType")) {

                            // check that it's searchable and it has image
                            if (xmlContentNode["DNNSearchable"] == null 
                             || xmlContentNode["DNNSearchable"].InnerText != "true" 
                             || xmlContentNode["PictureURL"] == null 
                             || xmlContentNode["ThumbURL"] == null 
                             || xmlContentNode["Heading"] == null 
                             || xmlContentNode["Caption"] == null) {
                                logger.Debug("Element skipped {0}...", 
                                    xmlContentNode["ElementID"].InnerText);
                                continue;
                            }

                            // build description
 
string desc = string.Format("<a href=\"/DesktopModules/LiveContent/Handlers/GetImage.ashx?mid={0}&eid={1}&Type=View&PortalId=0\" class=\"prettyPhoto\"><img src=\"/DesktopModules/LiveContent/Handlers/GetImage.ashx?mid={0}&eid={1}&PortalId=0\" /></a>", modId, xmlContentNode["ElementID"].InnerText ); logger.Debug("Built description for element {0}: {1}",                                 xmlContentNode["ElementID"].InnerText, desc); // ready to index dataStore.StoreSingleItem( "plugin", // don't change this "mycontent", // this make the same as at the beginning of the code "", "", null, 0, // portal.PortalId, mod.TabID, mod.ModuleID, "", xmlContentNode["Heading"]["Content"].InnerText, desc, -1, DateTime.Parse(xmlContentNode["DateUpdated"].InnerText), xmlContentNode["ElementID"].InnerText, xmlContentNode["Heading"]["Content"].InnerText + " " + xmlContentNode["Caption"]["Content"].InnerText, "&LCeid=" + modId + xmlContentNode["ElementID"].InnerText, "live-content-" + modId, "Live Content", 1.0f, new string[] { "All Users" }
                            );

                        }
                    }
                }

                logger.Info("Done indexing Live Content");

            }
        }
    }

Another important thing to note is that the StoreSingleItem method also accepts a dictionary of custom data which are automatically available in the XSL template. This means that instead of building our HTML description in the indexer, we could export the URL to the image in a custom field, then use it to display an image in the search results template.