Creating a metadata binding handler in C#

This example shows how to create a custom metadata binding 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 MyCustomMetadataBindingLibrary, 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, use the Browse button.
    3. Select Trisoft.InfoShare.Plugins.SDK.dll, then Add.
    4. To be able to view the code documentation of the Trisoft.InfoShare.Plugins.SDK.dll, also copy Trisoft.InfoShare.Plugins.SDK.xml.
  3. Add a reference to a plugin common helper (Trisoft.InfoShare.Plugins.Common.dll). For example, you can copy the file from \Applications\Plugins\Trisoft.InfoShare.Plugins.Common.dll.
    1. Browse to the project, then right-click References, and select Add Reference.
    2. To select the reference you want to add, use the Browse button.
    3. Select Trisoft.InfoShare.Plugins.Common.dll, then Add.
    4. To be able to view the code documentation of the Trisoft.InfoShare.Plugins.Common.dll, also copy Trisoft.InfoShare.Plugins.Common.xml.
  4. Add a reference to the System.ComponentModel.Composition assembly.
  5. Add a reference to the System.Xml assembly.
  6. Create a new MyCustomMetadataBindingHandler class.
    • The name of the handler matches the value assigned to ExportAttribute, and it needs to be unique.
    • The LogService on IHandlerConfiguration 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 Trisoft.InfoShare.Plugins.SDK;
    using Trisoft.InfoShare.Plugins.SDK.Extensions.MetadataBinding;
    
    namespace MyCustomMetadataBindingLibrary
    {
        // This attribute is used to make the class discoverable by ContentManager.
        [Export("MyCustomMetadataBindingHandler", typeof(IHandler))]
        [PartCreationPolicy(CreationPolicy.NonShared)]
        /// <summary>
        /// Custom metadata binding handler that is used to connect the metadata fields to the external metadata source.
        /// This handler mimics the behavior by allowing to choose between "Apple", "Orange" and "Banana".
        /// </summary>
        public class MyCustomMetadataBindingHandler : IHandler
        {
            /// <summary>
            /// The handler configuration.
            /// </summary>
            private IHandlerConfiguration _configuration;
            /// <summary>
            /// The root node label that will be used for the 'tree mode'
            /// </summary>
            private string _rootNode;
    
            /// <summary>
            /// This method will be called by the business code after the instance of the handler is created.
            /// </summary>
            /// <param name="configuration">Configuration with the parameters provided in the XML Extension Settings.</param>
            public void Initialize(IHandlerConfiguration configuration)
            {
                // Cache the passed configuration for the future use
                _configuration = configuration;
    
                // Access parameters
                if (!configuration.Parameters.TryGetValue("RootNode", out _rootNode))
                {
                    _rootNode = "Fruits";
                }
            }
    
            /// <summary>
            /// Used to convert ids (stored in the database) into tags (including labels visible in UI)
            /// </summary>
            /// <param name="context">The arguments of the method.</param>
            /// <returns>The result with the resolved tags.</returns>
            public IResolveIdsResult ResolveIds(IResolveIdsContext context)
            {
                List<Tag> tags = new List<Tag>();
    
                // For all the ids that we passed, we try to resolve it into a tag
                foreach (string id in context.Ids)
                {
                    switch (id)
                    {
                        case "01":
                            tags.Add(new Tag
                            {
                                Id = "01",
                                Label = "Apple",
                                AnnotatedLabel = "Apple",
                                Description = "An apple is an edible fruit"
                            });
                            break;
                        case "02":
                            tags.Add(new Tag
                            {
                                Id = "02",
                                Label = "Orange",
                                AnnotatedLabel = "Orange",
                                Description = "An orange is an edible fruit"
                            });
                            break;
                        case "03":
                            tags.Add(new Tag
                            {
                                Id = "03",
                                Label = "Banana",
                                AnnotatedLabel = "Banana",
                                Description = "A banana is an edible fruit"
                            });
                            break;
                        default:
                            // If id cannot be resolved - simply skip it
                            break;
                    }
                }
    
                return new ResolveIdsResult()
                {
                    Tags = tags
                };
            }
    
            /// <summary>
            /// Returns the limited amound of tags matching the user input for showing the autosuggest options in UI.
            /// </summary>
            /// <param name="context">The arguments of the method.</param>
            /// <returns>The result with the tags matching the input filter.</returns>
            public IRetrieveTagsResult RetrieveTags(IRetrieveTagsContext context)
            {
                List<Tag> tags = new List<Tag>();
                if ("Apple".Contains(context.InputFilter))
                {
                    tags.Add(new Tag
                    {
                        Id = "01",
                        Label = "Apple",
                        // Use "<em>" tags to highlight the part of the term that matches the input filter, 
                        // for example "<em>App</em>le" for input filter value "app"
                        AnnotatedLabel = "Apple",
                        Description = "An apple is an edible fruit"
                    });
                }
                if ("Orange".Contains(context.InputFilter))
                {
                    tags.Add(new Tag
                    {
                        Id = "02",
                        Label = "Orange",
                        AnnotatedLabel = "Orange",
                        Description = "An orange is an edible fruit"
                    });
                }
                if ("Banana".Contains(context.InputFilter))
                {
                    tags.Add(new Tag
                    {
                        Id = "03",
                        Label = "Banana",
                        AnnotatedLabel = "Banana",
                        Description = "A banana is an edible fruit"
                    });
                }
    
                return new RetrieveTagsResult()
                {
                    Tags = tags,
                    Messages = new List<IMessage>()
                };
            }
    		
            /// <summary>
            /// Returns the tags and their relations to allow to reconstruct the tree in UI.
            /// Since this method is called only once, the tree should be complete, meaning that it should contain
            /// all the subnodes you would like to be able to show in UI. There is no lazy load concept support, 
            /// so there will be no second call once you expand the node.
            /// </summary>
            /// <param name="context">The arguments of the method.</param>
            /// <returns>The result with the tags and the structure.</returns>
            public IRetrieveTagStructureResult RetrieveTagStructure(IRetrieveTagStructureContext context)
            {
                return new RetrieveTagStructureResult()
                {
                    Tags = new List<IStructureTag>
                    {
                        new StructureTag(){
                            Id = "00",
                            Label = _rootNode,
                            AnnotatedLabel = _rootNode,
                            Description = "This is your tree root",
                            IsSelectable = false // Notice that the root is not selectable!
                        },
                        new StructureTag()
                        {
                            Id = "01",
                            Label = "Apple",
                            AnnotatedLabel = "Apple",
                            Description = "An apple is an edible fruit",
                            IsSelectable = true
                        },
                        new StructureTag()
                        {
                            Id = "02",
                            Label = "Orange",
                            AnnotatedLabel = "Orange",
                            Description = "An orange is an edible fruit",
                            IsSelectable = true
                        },
                        new StructureTag()
                        {
                            Id = "03",
                            Label = "Banana",
                            AnnotatedLabel = "Banana",
                            Description = "A banana is an edible fruit",
                            IsSelectable = true
                        }
                    },
                    Relations = new List<ITagRelation>
                    {
                        // Root node is the one referenced by the relation without from id
                        new TagRelation()
                        {
                            ToId = "00" // root node
                        },
                        new TagRelation()
                        {
                            FromId = "00", // from root node...
                            ToId = "01" // ...to apple
                        },
                        new TagRelation()
                        {
                            FromId = "00", // from root node...
                            ToId = "02" // ...to orange
                        },
                        new TagRelation()
                        {
                            FromId = "00", // from root node...
                            ToId = "03" // ...to banana
                        }
                    },
                    Messages = new List<IMessage>()
                };
            }
    
            /// <summary>
            /// Validates the passed ids against the external taxonomy.
            /// For each id that is not valid, should return an error message.
            /// </summary>
            /// <param name="context">The arguments of the method.</param>
            /// <returns>The result with the validation messages.</returns>
            public IValidateResult Validate(IValidateContext context)
            {
                IDictionary<string, IMessage> messages = new Dictionary<string, IMessage>();
    
                foreach (string id in context.Ids)
                {
                    if ((id != "01") && (id != "02") && (id != "03"))
                    {
                        messages.Add(id, new Message()
                        {
                            ResouceLib = "MyCustomMetadataBinding",
                            Level = MessageLevel.Error,
                            Number = -600000,
                            ResourceId = "TermNotFound",
                            Description = String.Format(@"The tag '{0}' is not valid.", id),
                            Parameters = new List<IMessageParam>()
                        });
                    }
                }
    
                return new ValidateResult()
                {
                    Messages = messages
                };
            }
    
            /// <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>IRetrieveTagsResult</code> interface.
        /// </summary>
        public class RetrieveTagsResult : IRetrieveTagsResult
        {
            /// <summary>
            /// Collection of tags
            /// </summary>
            public IEnumerable<ITag> Tags { get; set; }
            /// <summary>
            /// Collection of messages
            /// </summary>
            public IEnumerable<IMessage> Messages { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>IResolveIdsResult</code> interface.
        /// </summary>
        public class ResolveIdsResult : IResolveIdsResult
        {
            /// <summary>
            /// Collection of tags
            /// </summary>
            public IEnumerable<ITag> Tags { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>ValidateResult</code> interface.
        /// </summary>
        public class ValidateResult : IValidateResult
        {
            /// <summary>
            /// Collection of validation messages
            /// </summary>
            public IDictionary<string, IMessage> Messages { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>IRetrieveTagStructureResult</code> interface.
        /// </summary>
        public class RetrieveTagStructureResult : IRetrieveTagStructureResult
        {
            /// <summary>
            /// Collection of tags
            /// </summary>
            public IEnumerable<IStructureTag> Tags { get; set; }
            /// <summary>
            /// Collection of relations
            /// </summary>
            public IEnumerable<ITagRelation> Relations { get; set; }
            /// <summary>
            /// Collection of validation messages
            /// </summary>
            public IEnumerable<IMessage> Messages { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>ITag</code> interface.
        /// </summary>
        public class Tag : ITag
        {
            /// <summary>
            /// The tag label (the user-readable value shown in UI)
            /// </summary>
            public string Label { get; set; }
            /// <summary>
            /// Annotated label (the user-readable value shown in UI with some extra HTML markup,
            /// allowing highlighting inside the auto-suggest list).
            /// </summary>
            public string AnnotatedLabel { get; set; }
            /// <summary>
            /// The tag description (visible in UI)
            /// </summary>
            public string Description { get; set; }
            /// <summary>
            /// The id of the tag (comes from the external taxonomy)
            /// Should be a unique value that allows to unambiguously link the tag to the taxonomy
            /// </summary>
            public string Id { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>IStructureTag</code> interface.
        /// Comparing to a tag, contains an additional information required for the tree mode.
        /// </summary>
        public class StructureTag : Tag, IStructureTag
        {
            /// <summary>
            /// Defines whether the tag is selectable
            /// </summary>
            public bool IsSelectable { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>ITagRelation</code> interface.
        /// Allows to recreate the complete tree on the client side.
        /// </summary>
        public class TagRelation : ITagRelation
        {
            /// <summary>
            /// The id of the parent. 
            /// When not set, the relation is interpreted as pointing to the root node(s).
            /// </summary>
            public string FromId { get; set; }
            /// <summary>
            /// The id of the child.
            /// When FromId is empty, contains id of the root node(s).
            /// </summary>
            public string ToId { get; set; }
        }
        /// <summary>
        /// Simplistic and minimal implementation of the <code>IMessage</code> interface.
        /// Allows to return some feedback to teh client side.
        /// </summary>
        public class Message : IMessage
        {
            /// <summary>
            /// Readable description in English.
            /// Serves as a fallback when the message is not found in the resource file with server errors.
            /// </summary>
            public string Description { get; set; }
            /// <summary>
            /// The message level
            /// </summary>
            public MessageLevel Level { get; set; }
            /// <summary>
            /// The error number
            /// </summary>
            public int Number { get; set; }
            /// <summary>
            /// Additional error parameters
            /// </summary>
            public IEnumerable<IMessageParam> Parameters { get; set; }
            /// <summary>
            /// The resource functional area reference. Links the message to the translatable resource
            /// </summary>
            public string ResouceLib { get; set; }
            /// <summary>
            /// The resource id. Links the message to the translatable resource
            /// </summary>
            public string ResourceId { get; set; }
    
        }
    }
    
  7. Deploy the new handler.
  8. Configure the new handler.
    
    <infoShareExtensionConfig version='1.0'>
      <metadatabindings>
        <metadatabinding ishfieldname='FTESTMDBOUNDFIELD' sourceref='MyCustomMetadataBindingHandler' />
      </metadatabindings>
      <sources>
        <source id='MyCustomMetadataBindingHandler' handler='MyCustomMetadataBindingHandler'>
          <initialize>
            <parameters>
              <parameter name='RootNode'>All values</parameter>
            </parameters>
          </initialize>
        </source>
      </sources>
    </infoShareExtensionConfig>