Documentation Center

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, 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 MyCustomMetadataBindingHandler class.
    • The name of the handler corresponds to 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; }
    
        }
    }
    
  6. Deploy the new handler.
  7. 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>