Creating a search extension handler in C#

This example shows how to create a custom search extension handler to connect the metadata fields to an external metadata source

Before you begin

The following products need to be already installed on your machine:
  • Microsoft .NET FrameWork
  • Microsoft Visual Studio

Procedure

  1. Create a Visual Studio Project. In this example, the project is called MyCustomSearchExtensionLibrary, and its type is Class Library.
  2. Add a reference to a plugin SDK (Trisoft.InfoShare.Plugins.SDK.dll). For example, you can copy the file from \Applications\Common\Trisoft.InfoShare.Plugins.SDK.dll.
    1. Browse to the project, then right-click References, and select Add Reference.
    2. To select the reference you want to add, click the Browse button.
    3. Select Trisoft.InfoShare.Plugins.SDK.dll, and click Add.
  3. Add a reference to the System.ComponentModel.Composition assembly.
  4. Add a reference to the System.Xml assembly.
  5. Create a new MyCustomSearchExtensionHandler class.
    • The name of the handler corresponds to the value assigned to ExportAttribute, and it needs to be unique.
    • The LogService on IQueryEnhanceHandlerConfiguration manages all log entries as part of the standard log chaining, as configured in NLog.config. If you want to create a separate logging chain, you need to set up a new Logger
    
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Linq;
    using System.Xml.XPath;
    using Trisoft.InfoShare.Plugins.SDK;
    using Trisoft.InfoShare.Plugins.SDK.Extensions.Search;
    
    namespace MyCustomSearchExtensionLibrary
    {
        /// <summary>
        /// Custom search extension handler that adds an additional full text search clause with the corresponding labels 
        /// if there are certain term ids found as values for the configured field in the users submitted search query
        /// The full search text clause is added by adding an extra "or" between the original field and a new ISHANYWHERE field clause (full text search field) 
        /// when the field operator is not provided or is "equal" or is "contains".
        /// </summary>
        // This attribute is used to make the class discoverable by InfoShare.
        [Export("MyCustomSearchExtensionHandler", typeof(IQueryEnhanceHandler))]
        [PartCreationPolicy(CreationPolicy.NonShared)]    
        public class MyCustomSearchExtensionHandler : IQueryEnhanceHandler
        {
            /// <summary>
            /// Field element name of the CONTINENTS field
            /// </summary>
            private string _continentsFieldName;
            /// <summary>
            /// Field level of the CONTINENTS field
            /// </summary>
            private string _continentsFieldLevel;
            /// <summary>
            /// Set to true when all the necessary configuration parameters are present.
            /// </summary>
            private bool _mustTryToEnhanceQuery = false;
    
            /// <summary>
            /// This method will be called by the business code after the instance of the handler is created.
            /// </summary>
            /// <param name="configuration">A <see cref="IQueryEnhanceHandlerConfiguration"/> containing the query enhance configuration.</param>
            public void Initialize(IQueryEnhanceHandlerConfiguration configuration)
            {
                if (configuration.Parameters.TryGetValue("continentsfieldname", out _continentsFieldName) && configuration.Parameters.TryGetValue("continentsfieldlevel", out _continentsFieldLevel))
                {
                    _mustTryToEnhanceQuery = true;
                    // You could also throw an exception here
                }
            }
    
            /// <summary>
            /// This method can be used to enhance the xml query
            /// </summary>
            /// <param name="context">The arguments of the method containing the xml query to enhance.</param>
            /// <returns>The result with the enhanced xml query.</returns>
            public IEnhanceQueryResult EnhanceQuery(IEnhanceQueryContext context)
            {
                if (_mustTryToEnhanceQuery)
                {
                    var continentQueryElements = context.Query.XPathSelectElements(String.Format("ishquery//ishfield[@name='{0}'][@level='{1}'][text()]", _continentsFieldName, _continentsFieldLevel)).ToList();
                    if (continentQueryElements.Any())
                    {
                        foreach (var continentQueryElement in continentQueryElements)
                        {
                            if (continentQueryElement.Attribute("ishoperator") == null || continentQueryElement.Attribute("ishoperator").Value == "equal" || continentQueryElement.Attribute("ishoperator").Value == "contains")
                            {
                                var continentIds = new List<string>(continentQueryElement.Value.Split(new string[] {", "}, StringSplitOptions.None));
                                var continents = new List<string>();
    
                                // For all the ids that we passed, we try to resolve it into a tag
                                foreach (string id in continentIds)
                                {
                                    switch (id)
                                    {
                                        case "SOA":
                                            continents.Add(@"""South America""");
                                            break;
                                        case "NOA":
                                            continents.Add(@"""North America""");
                                            break;
                                        case "AFR":
                                            continents.Add(@"""Africa""");
                                            break;
                                        case "EUR":
                                            continents.Add(@"""Europe""");
                                            break;
                                        case "ASA":
                                            continents.Add(@"""Asia""");
                                            break;
                                        case "AUS":
                                            continents.Add(@"""Australia""");
                                            break;
                                        case "OCE":
                                            continents.Add(@"""Oceania""");
                                            break;
                                        case "ANT":
                                            continents.Add(@"""Antarctica""");
                                            break;
                                        default:
                                            // If id does not match - simply skip it
                                            break;
                                    }
                                }
    
                                if (continents.Any())
                                {
                                    // We will replace the field with (field OR ISHANYWHERE), so if the users query would contain
                                    // <ishfield name="FTESTCONTINENTS" level="logical" ishoperator="equal">OCE, AFR, ANT</ishfield>
                                    // it is enhanced to
                                    // <or>
                                    //     <ishfield name="FTESTCONTINENTS" level="logical" ishoperator="equal">OCE, AFR, ANT</ishfield>
                                    //     <ishfield name="ISHANYWHERE" level="none" ishoperator="contains">"Oceania", "Africa", "Antarctica"</ishfield>
                                    // </or>
                                    var anywhereField =
                                        new XElement("ishfield",
                                            new XAttribute("name", "ISHANYWHERE"),
                                            new XAttribute("level", "none"),
                                            new XAttribute("ishoperator", "contains"),
                                            String.Join(", ", continents));
                                    continentQueryElement.ReplaceWith(
                                        new XElement("or",
                                            continentQueryElement,
                                            anywhereField));
                                }                            
                            }
                        }
                    }
                }
    
                // Always return a query, either the original one or an enhanced one
                return new EnhanceQueryResult()
                {
                    Query = context.Query
                };
            }
    
            /// <summary>
            /// Explicitly release any disposable resources
            /// </summary>
            public void Dispose()
            {
                // In our example we don't need a cleanup
            }
        }
    
    
        /// <summary>
        /// Simplistic and minimal implementation of the <code>EnhanceQueryResult</code> interface.
        /// </summary>
        public class EnhanceQueryResult : IEnhanceQueryResult
        {
            /// <summary>
            /// Enhanced xml query
            /// </summary>
            public XDocument Query { get; set; }
        }
    
    }
    
  6. Deploy the new handler.
  7. Configure the new handler.
    
    <infoShareExtensionConfig version='1.0'>
      <search>
        <queryenhance sourceref="MyCustomSearchEnhancer" />
      </search>
      <sources>
        <source id='MyCustomSearchEnhancer' handler='MyCustomSearchExtensionHandler'>
          <initialize>
            <parameters>
              <parameter name='continentsfieldname'>FCONTINENTS</parameter>
              <parameter name='continentsfieldlevel'>logical</parameter>
            </parameters>
          </initialize>
        </source>
      </sources>
    </infoShareExtensionConfig>