Kentico CMS Mapper functions using Reflection in C# .NET

Thu Oct 17 2013

The following post shows functionality on mapping a Kentico CMS TreeNode object to a strong type .NET model class. These functions use reflection to map a property in a class to a Kentico document type field.

The examples below assumes your Document Types in Kentico have a corresponding .NET model class.

Below is the base class BaseModel that all model classes inherit from:

using CMS.PortalControls;
using CMS.DocumentEngine;
using CMS.CMSHelper;
using CMS.SettingsProvider;

public abstract class BaseModel
{
    // define document id
    public int id
    {
        get { return Document == null ? 0 : Document.DocumentID; }
    }

    // define node id
    public int NodeId
    {
        get { return Document != null ? Document.NodeID : 0; }
    }

    // define name
    public string DocumentName
    {
        get { return Document != null ? Document.DocumentName : ""; }
    }

    // define url
    public string URL
    {
        get { return Document == null ? "" : Document.RelativeURL.Replace("~",""); }
    }

    // define url with extension
    public string URLWithExtension
    {
        get { return URL + (Document == null ? "" : Document.DocumentExtensions); }
    }

    // external url
    public string ExternalURL
    {
        get { return Document == null ? "" : Document.AbsoluteURL; }
    }        

    // path for searching
    public string Path
    {
        get { return Document == null ? "" : URL + (!URL.EndsWith("/") ? "/" : "") + "%"; }
    }

    // define document
    public TreeNode Document { get; set; }


    // define function that maps extra properties
    public abstract void MapExtras();
}

The following are helper types to define an Image and a Generic Document:

public class Image
{
    // fields
    public string Guid { get; set; }

    // function that checks if an image
    public bool HasImage { get { return !String.IsNullOrEmpty(Guid); } } 

    // define path for the image
    public string Path
    {
        get { return String.Format("/getfile/{0}/img/", Guid); }
    }

    // function that returns the html tag for this image
    public string ToHtml(string alt)
    {
        // check if no guid
        if (String.IsNullOrEmpty(Guid))
            return "";

        // return the html tag for this image
        return String.Format("", Path, alt);            
    }
}
public class GenericDocument : BaseModel
{
    public override void MapExtras() { }
}

Below is a generic mapper function to map a given TreeNode and return the requested type:

public static T MapTo(TreeNode document)
    where T : BaseModel, new()
{
    // init item
    T item = new T();

    // check if a document passed
    if (document != null)
    {
        // set the document
        item.Document = document;

        // go through each of this model's properties
        foreach (var p in item.GetType().GetProperties())
        {
            // check if a writeable property
            if (p.CanWrite)
            {
                // check primitive types
                if (p.PropertyType == typeof(string))
                {
                    p.SetValue(item, document.GetStringValue(p.Name, ""), null);
                }
                else if (p.PropertyType == typeof(int))
                {
                    p.SetValue(item, document.GetIntegerValue(p.Name, 0), null);
                }
                else if (p.PropertyType == typeof(bool))
                {
                    p.SetValue(item, document.GetBooleanValue(p.Name, false), null);
                }
                else if (p.PropertyType == typeof(double))
                {
                    p.SetValue(item, document.GetDoubleValue(p.Name, 0), null);
                }
                else if (p.PropertyType == typeof(Guid))
                {
                    p.SetValue(item, document.GetGuidValue(p.Name, new Guid()), null);
                }
                else if (p.PropertyType == typeof(DateTime))
                {
                    p.SetValue(item, document.GetDateTimeValue(p.Name, DateTime.Parse("1/1/1900").Date), null);
                }

                // check complex types
                else if (p.PropertyType == typeof(Image))
                {
                    p.SetValue(item, new Image { Guid = document.GetStringValue(p.Name, "") }, null);
                }                
                else if (p.PropertyType == typeof(GenericDocument))
                {
                    p.SetValue(item, MapTo(GetDocumentFromGUID(document.GetStringValue(p.Name, ""))), null);
                }            
            }
        }

        // map extras
        item.MapExtras();
    }

    // return the item
    return item;
}

Below is a generic mapper function to map a given TreeNodeDataSet and return a list of the requested type:

public static List MapToList(TreeNodeDataSet documentList)
    where T : BaseModel, new()
{
    // define return list
    List list = new List();

    // check if a document list
    if (documentList != null)
    {
        // go through each
        foreach (var n in documentList)
        {
            list.Add(MapTo(n));
        }
    }

    // return 
    return list;
}

Below are helper functions needed for mapping:

// function that gets a tree node from guid
public static TreeNode GetDocumentFromGUID(string guidStr)
{
    // check if no guid
    Guid guid;
    if (Guid.TryParse(guidStr, out guid))
    {
        // get a tree provider
        var th = new TreeProvider();

        // find the node
        var n = th.SelectSingleNode(guid, CultureCode, CMSContext.CurrentSiteName);

        // check if a node
        if (n != null)
        {
            return DocumentHelper.GetDocument(n, th);
        }
    }

    // if here, return nothing
    return null;
}

// function that gets a tree node from id
public static TreeNode GetDocumentFromID(int id)
{
    // check if no id            
    if (id > 0)
    {
        // get a tree provider
        var th = new TreeProvider();
        return DocumentHelper.GetDocument(id, th);                
    }

    // if here, return nothing
    return null;
}

// function that trys to find the current culture, falls back to default
public static string CultureCode
{
    get
    {
        // return nothing if blank
        return CMSContext.CurrentDocumentCulture == null ? "en-US"
            : CMSContext.CurrentDocumentCulture.CultureCode;
    }
}

Below is an example on using this functionality:

// blog post model
public class BlogPost : BaseModel
{
    // fields
    public int BlogPostID { get; set; }
    public string Title { get; set; }
    public DateTime PostDate { get; set; }
    public string PostURL { get; set; }
    public Image Photo { get; set; }
    public string Author { get; set; }
    public string AuthorURL { get; set; }
    public string ReadMoreText { get; set; }

    public override void MapExtras()
    {
    }
}

// example
public class Example
{
    public static void GetSinglePost()
    {
        // get a single post from the current document
        BlogPost post = Mapper.MapTo(CurrentDocument);                
    }

    public static void GetPostList()
    {
        // get a list of blog posts in the path        
        var th = new TreeProvider();

        // find the nodes
        var nodes = th.SelectNodes(
            CMSContext.CurrentSiteName, "/%",
            Globals.CultureCode, false,
            "EXAMPLE.BlogPost", "", "NodeOrder", -1, true, -1);

        // get the list            
        List posts = Mapper.MapToList(nodes).OrderBy(x => x.Document.NodeOrder).ToList();
    }
}