Document Retrieval Macro

0
Comments
2
Votes
Login to vote

With the introduction of the K# syntax in Kentico CMS version 6, the capability of macros has been greatly enhanced. Now, rather than returning simple strings, you can return objects and collections that can be further processed with K#. In this post I will show you how to create a custom macro that will enable you to query and retrieve documents from the Kentico CMS. This macro can be used in correlation with the ApplyTransformation method to render the contents of the documents. This is useful in situations when you need to intermix document content in a static text or HTML web part.

Registering Custom Macros

First, we need to set up the structure for registering the custom macro with Kentico. To do this we will create a CMSModuleLoader that will register the macros when Kentico is initialized.

Step 1:

In the App_Code folder create a new folder named ‘Global’. The name and location of the folder is important because Kentico will recognize this folder when performing a site export. To see a full list of folders that are included in site exports, take a look at the export/import folder structure documentation (http://devnet.kentico.com/docs/devguide/index.html?folder_structure_and_importexport.htm).

Step 2:

Next, create a class file in the newly created folder called CustomMacros.cs. This file will house both the CMSModuleLoader and the macro code.

Step 3:

Add the following code to the CustomMacros.cs file.

using CMS.SettingsProvider;

public static class CustomMacros
{
    /// <summary>
    /// Registers custom macro methods with the Kentico Macro Resolver
    /// </summary>
    public static void RegisterMethods()
    {
    }
}

/// <summary>
/// This class calls the registration of the custom macros when the CMS is initialized
/// </summary>
[CustomMacrosLoader]
public partial class CMSModuleLoader
{
    private class CustomMacrosLoaderAttribute : CMSLoaderAttribute
    {
        public override void Init()
        {
            CustomMacros.RegisterMethods();
        }
    }
}

Here you can see a basic implementation of the CMSModuleLoader. First, we create a partial class of the CMSModuleLoader. Next, we nest a custom class inside that inherits from CMSLoaderAttribute. Now we override the Init method and this is where we place the code that will run when Kentico starts. In this case we are calling the static RegisterMethods method on the CustomMacros class defined above. Lastly, we decorate the CMSModuleLoader class with the custom attribute class we just created. This forms the basic structure for creating module loaders in Kentico.

Creating the Document Retrieval Macro

All methods registered with Kentico’s macro engine must adhere to the same method signature shown here:

public static object MethodName(params object[] parameters)

Adhering to this interface allows Kentico to dynamically call registered methods having a variable number of parameters.

Step 4:

Add the following methods to the CustomMacros class:

public static object GetDocuments(params object[] parameters)
{
    var classNames = ValidationHelper.GetString(parameters.SafeGet(0), "");
    var aliasPath = CMSContext.ResolveCurrentPath(ValidationHelper.GetString(parameters.SafeGet(1), "./%"));
    var orderBy = ValidationHelper.GetString(parameters.SafeGet(2), "NodeOrder");

    var documents = TreeHelper.SelectNodes(aliasPath, true, classNames, "", orderBy, -1, true);
    if (DataHelper.DataSourceIsEmpty(documents))
        return null;

    return documents.Tables[0].Rows;
}

public static object SafeGet(this object[] items, int index)
{
    if (index >= items.Length)
        return null;

    return items[index];
}

First, let’s look at the SafeGet helper method. Since some of the parameters will be optional, this method checks the length of the array of parameters before it tries to access an index, preventing a ArgumentOutOfRangeException. Next, let’s look at the GetDocuments method. The first part of this method is gathering the values of the method parameters: classNames, aliasPath, and orderBy. You can see that we are using Kentico’s CMS.GlobalHelper.ValidationHelper to resolve the value to a specific type and providing a default value if the parameter’s value is null. Now we get to the heart of the method where we call the CMS.CMSHelper.TreeHelper.SelectNodes to query the document repository. What's interesting here is that we are not merely returning a string, but retuning an object (DataRowCollection). Returning objects allows us to do further processing of the data outside of this method. For example, having a DataRowCollection, we can now apply a transformation to each row to render the document contents to the client.

Step 5:

To complete the macro we need to register it with Kentico. To do this, add the following code to the RegisterMethods method:

public static void RegisterMethods()
{
    MacroMethods.RegisterMethod("GetDocuments", GetDocuments, typeof(DataRowCollection), "Returns documents meeting the specified criteria.", null, 1,
        new object[,]
            {
                { "classNames", typeof(string), "List of class names to select separated by a semicolon." },
                { "aliasPath", typeof(string), "Path to documents (defaults to the current path)." },
                { "orderBy", typeof(string), "Order by clause (defaults to NodeOrder)." }
            }
        , null);
}

Kentico version 7 has changed the way macro methods are register, so if you are running version 7 use the following code to register your macros:

public static void RegisterMethods()
{
    MacroMethod getDocuments = new MacroMethod("GetDocuments", GetDocuments)
    {
        Comment = "Returns documents meeting the specified criteria.",
        Type = typeof(DataRowCollection),
        MinimumParameters = 1
    };
    getDocuments.AddParameter("classNames", typeof(string), "List of class names to select separated by a semicolon.");
    getDocuments.AddParameter("aliasPath", typeof(string), "Path to documents (defaults to the current path).");
    getDocuments.AddParameter("orderBy", typeof(string), "Order by clause (defaults to NodeOrder).");
    MacroMethods.RegisterMethod(getDocuments);
}

This will make Kentico aware of the custom macro. We provide the name of our method, the return type, the parameters and their types, and intellisense/code completion documentation. Once the custom macro is register you will then see the method available in the code completion of the macro editor. To see a full description of the MacroMethods.RegisterMethod method refer to the Kentico documentation http://devnet.kentico.com/docs/devguide/index.html?registering_cutom_macro_methods.htm.

The competed CustomMacros.cs file should look like the following:

using System.Data;
using CMS.CMSHelper;
using CMS.GlobalHelper;
using CMS.SettingsProvider;

public static class CustomMacros
{
    /// <summary>
    /// Registers custom macro methods with the Kentico Macro Resolver
    /// </summary>
    public static void RegisterMethods()
    {
        MacroMethods.RegisterMethod("GetDocuments", GetDocuments, typeof(DataRowCollection), "Returns documents meeting the specified criteria.", null, 1,
            new object[,]
                {
                    { "classNames", typeof(string), "List of class names to select separated by a semicolon." },
                    { "aliasPath", typeof(string), "Path to documents (defaults to the current path)." },
                    { "orderBy", typeof(string), "Order by clause (defaults to NodeOrder)." }
                }
            , null);

        // Use the following for Kentico 7 in place of the code above
        /*MacroMethod getDocuments = new MacroMethod("GetDocuments", GetDocuments)
        {
            Comment = "Returns documents meeting the specified criteria.",
            Type = typeof(DataRowCollection),
            MinimumParameters = 1
        };
        getDocuments.AddParameter("classNames", typeof(string), "List of class names to select separated by a semicolon.");
        getDocuments.AddParameter("aliasPath", typeof(string), "Path to documents (defaults to the current path).");
        getDocuments.AddParameter("orderBy", typeof(string), "Order by clause (defaults to NodeOrder).");
        MacroMethods.RegisterMethod(getDocuments);*/
    }

    public static object GetDocuments(params object[] parameters)
    {
        var classNames = ValidationHelper.GetString(parameters.SafeGet(0), "");
        var aliasPath = CMSContext.ResolveCurrentPath(ValidationHelper.GetString(parameters.SafeGet(1), "./%"));
        var orderBy = ValidationHelper.GetString(parameters.SafeGet(2), "NodeOrder");

        var documents = TreeHelper.SelectNodes(aliasPath, true, classNames, "", orderBy, -1, true);
        if (DataHelper.DataSourceIsEmpty(documents))
            return null;

        return documents.Tables[0].Rows;
    }

    public static object SafeGet(this object[] items, int index)
    {
        if (index >= items.Length)
            return null;

        return items[index];
    }
}

/// <summary>
/// This class calls the registration of the custom macros when the CMS is initialized
/// </summary>
[CustomMacrosLoader]
public partial class CMSModuleLoader
{
    private class CustomMacrosLoaderAttribute : CMSLoaderAttribute
    {
        public override void Init()
        {
            CustomMacros.RegisterMethods();
        }
    }
}

Now you can see the GetDocuments method with code completion in the macro editor.

Code completion for custom macro method

With the finished macro statement we are able to render documents to the page. Note that the transformation specified in the ApplyTransformation method must be a macro based transformation type which include: Text/XML, HTML, and jQuery transformation types.

Custom macro satement

Results of custom macro statement

As you can see, macros can be easily employed and are a great way to add functionality to your Kentico CMS site.

 
Posted by Ryan Williams on 10/9/2012 10:27:15 AM
  
Comments
Blog post currently doesn't have any comments.