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
Important: The information and the procedure herein are provided as is without warranty of any kind, either express or implied.
The following products need to be already installed on your machine:
- Microsoft .NET FrameWork
- Microsoft Visual Studio
Procedure
- Create a Visual Studio Project. In this example, the project is called
MyCustomMetadataBindingLibrary, and its type is Class Library. - 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.
- Browse to the project, then right-click References, and select Add Reference.
- To select the reference you want to add, click the Browse button.
- Select Trisoft.InfoShare.Plugins.SDK.dll, and click Add.
- Add a reference to the System.ComponentModel.Composition assembly.
- Add a reference to the System.Xml assembly.
- 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; } } } - Deploy the new handler.
- 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>