Knowledge Base

Working With Custom Hooks: OnCategoryCreated

In the Taxonomy Tree users can create, rename, move, and delete categories. We explore how the OnCategoryCreated custom hook can be employed to maintain consistency in your taxonomy when working in two different CMS installations.


The Ingeniux CMS taxonomy system allows contributors to categorize pages, components, and assets using a hierarchical structure of categories that are created and managed from within the Administration Section of the CMS in the Taxonomy Management System. Here, users can create, rename, move, and delete categories as well as rapidly categorize content. 

Users trigger this hook when they take the following actions in the CMS: 

  • Create a new category in Administration > Taxonomy. 
  • Take an action that uses the API to create a category in the Taxonomy Management System. 

Permissions can be applied to users and groups by System Administrators that will help control who can make changes in the Taxonomy Management System. Additionally, security parameters can be set to control where in the taxonomy hierarchy a user can create a new taxonomy category.  

CMS managers might be interested in using this hook to keep complex systems in sync with the CMS Taxonomy Management System (such as third-party tools or outside databases), to verify that categories follow a naming convention, or to create other corresponding content in the CMS. 

Considerations  

Avoid creating new categories with this hook unless you have a solid termination condition, as the hook might trigger itself.  

Consider the entire architecture of the Taxonomy Tree and how your categories layer. In many taxonomy systems category names might be used more than once in different structures. For example, in a Taxonomy Tree, you might see: 

  • Parent Taxonomy Category: Audiences 
  • Child Taxonomy Category: Alumni 

And: 

  • Parent Taxonomy Category: Topics 
  • Child Taxonomy Category: Alumni 

Be wary of using the category name as the sole identifier for logic in your category creation scripts. 

Example 

Create a matching category in another CMS using Ingeniux REST API 

There are often scenarios where you may have two CMS installations that you want to keep in sync with each other. You would be familiar with this if you have a Development CMS (Dev) and a Production CMS (Prod).  

The most common way that a Dev CMS is put in sync with Prod is to replace the full database. This can be a large process and sometimes isn’t always preferable because all content will be replaced, including any test content the developers might be working on. Instead, using custom hooks might be a good option if you wish to isolate and move specific content to sync Dev and Prod.  

In this example, the user has created a new category and this OnCategoryCreated hook triggers.  The script then uses the CMS REST API to connect to another CMS and create a matching category to the new category. The REST API is most often used when you need to run a process from outside of the CMS. This includes the scenario created here where content changes in one CMS need to map to another.  

To prepare, some NuGet packages need to be put in place for the project. The custom hooks file has all it needs to perform regular CMS tasks, but when you start to explore advanced functions of .Net, you will need to add packages to the project to extend it. You can make these packages available to the custom hooks file by opening the API_Extensions_Development_Harness from [site location]\custom hooks articles\site\App_Data\xml\Custom and using Manage NuGet packages to download them. 

  • Microsoft.AspNet.MVC   (latest) 
  • Newtonsoft.Json (latest) 
  • RestSharp (106.15.0) 

These will let you use the REST API calls needed to communicate with the other CMS directly. You will need to create a user account in the CMS that the API will access to perform this action. 

Use these namespaces at the top of the custom hooks file: 

using Newtonsoft.Json; 

using System.Web.Mvc; 

using RestSharp; 

Connect to the CMS that you want to copy the categories to using the REST API. Make a class, outside of the custom hooks classes, to store your connection and the other commands needed to process the data. 

This includes: 

  • sending the username and password to the CMS and getting back a connection token, 
  • modeling data for messages and errors 
  • modeling category properties 
  • checking logged in user permissions 
  • creating categories 

 public class LoginWrapper 

    { 

        private string _userName; 

        private string _password; 

        private string _siteURL; 

        private string _authToken; 

  

        public LoginWrapper() 

        { 

            _userName = "username"; 

            _password = "password"; 

            _siteURL = "http://yoursite"; 

            _authToken = _getToken(); 

        } 

  

        private string _getToken() 

        { 

            RestClient client = new RestClient(_siteURL + "/rest/membershipprovidersservices.svc/login"); 

            client.Timeout = -1; 

  

            var request = new RestRequest(Method.POST); 

            request.AddHeader("Content-Type", "application/json"); 

  

            object data = new 

            { 

                userName = _userName, 

                password = _password, 

                MembershipProviderName = "IngeniuxMembershipProvider" 

            }; 

            string body = JsonConvert.SerializeObject(data); 

            request.AddParameter("application/json", body, ParameterType.RequestBody); 

            IRestResponse<CMSLoginResponse<string>> response = client.Execute<CMSLoginResponse<string>>(request); 

  

            string token = string.Empty; 

  

            if (response.IsSuccessful) 

            { 

                token = response.Data.message; 

            } 

  

            return token; 

        } 

  

        public bool CreateNewCategory(string name, string idPrefix, string description, string externalId, string typeName, string parentId = "0") 

        { 

            if (!string.IsNullOrWhiteSpace(name)) 

            { 

                RestClient client = new RestClient(_siteURL + "/rest/TaxonomyTreeServices.svc/CreateNewCategory"); 

                client.Timeout = -1; 

  

  

                var request = new RestRequest(Method.POST); 

  

                request.AddHeader("Content-Type", "application/json"); 

                request.AddHeader("X-IGXAToken", _authToken); 

                object data = new 

                { 

                    data = new 

                    { 

                        parentId = parentId, 

                        idPrefix = idPrefix, 

                        name = name, 

                        description = description, 

                        externalId = externalId, 

                        typeName = typeName 

                    }, 

                    locale = "en-us" 

                }; 

                string body = JsonConvert.SerializeObject(data); 

  

                request.AddParameter("application/json", body, ParameterType.RequestBody); 

  

                var response = client.Execute(request); 

                var json = JsonConvert.DeserializeObject<CMSResponse<string>>(response.Content); 

  

                var errorResponse = json.error != null ? json.error : ""; 

  

                if (string.IsNullOrWhiteSpace(errorResponse)) 

                { 

                    return true; 

                } 

                else 

                { 

                    return false; 

                } 

            } 

            return false; 

  

        } 

  

        public class CMSLoginResponse<T> 

        { 

            public int code { get; set; } 

            public string error { get; set; } 

            public string message { get; set; } 

        } 

  

        public class CMSResponse<T> 

        { 

            public int code { get; set; } 

            public string error { get; set; } 

            public Message message { get; set; } 

        } 

        public class Message 

        { 

            public string objectId { get; set; } 

            public string widgetId { get; set; } 

            public bool isFolder { get; set; } 

            public Props props { get; set; } 

            public string title { get; set; } 

        } 

        public class Permission 

        { 

            public string name { get; set; } 

            public string id { get; set; } 

            public int accessLevel { get; set; } 

        } 

        public class Props 

        { 

            public object associatedPages { get; set; } 

            public string creationLocale { get; set; } 

            public int depth { get; set; } 

            public string description { get; set; } 

            public string everyoneName { get; set; } 

            public string externalId { get; set; } 

            public string id { get; set; } 

            public string idPrefix { get; set; } 

            public string originalDescription { get; set; } 

            public string originalTitle { get; set; } 

            public string originalTypeName { get; set; } 

            public string path { get; set; } 

            public List<Permission> permissions { get; set; } 

            public bool permissionsInherited { get; set; } 

            public List<object> synonyms { get; set; } 

            public bool translated { get; set; } 

            public string typeName { get; set; } 

            public int visibility { get; set; } 

        } 

    } 

Finally, in your hook, call the login and create the matching category. Make sure to check that the parent category exists so you can either create it in the same place or throw an error if the parent doesn’t exist. 

            var wrapper = new LoginWrapper(); 

  

            var result = wrapper.CreateNewCategory(category.Name, category.Id, category.Description, category.ExternalID, category.TypeName, category.ParentId); 

  

            if (!result) 

            { 

                throw new Exception("Error Creating category: the parent category does not exist."); 

            } 

Possible Enhancements 

If you started from a base copy of your CMS and the first levels of the Taxonomy Tree have matching IDs, you may want to check parent IDs instead of just names to match category content creation with more precision. 

Consider giving more detailed failure messages to your users, as there are many reasons a new category might not be able to create in the destination CMS. These might be messages from the destination CMS, or system messages for failed connections. 

If your users create too many categories, this might be prohibitive to run. You might consider moving this to an automated task that runs once a day, or a custom tab that is run on demand instead. The code is transportable to either of those with some modification. 

  • VERSION: CMS 10
  • RELEASE: 10.x
  • Published: April 14, 2023
  • LAST UPDATED: September 18, 2023
  • Comments: 0

Please login to comment

Comments


There are no comments yet.