Kentico 9 Certified Developer

I haven't done the Kentico certified exam for over two years - but this doesn't make me any less of an awesome and competent Kentico Developer. Over the last two years, a lot has changed within the Kentico realm resulting in the subject matter becoming a little more of a challenge to keep up to speed with. Afterall, two years ago we saw the dawn of a new age - the end of Kentico 7 and the start of Kentico 8. I am seeing the same thing happening again, except this time we're just about seeing Kentico 10 making it's appearance over the horizon.

What I noticed this time round was the increased number of questions evolving around macro's. It felt like I was bombarded at the time of carrying out the exam. I think the only thing that got me through is remembering the approach I took to the macro's I wrote for a recent EMS project.

The Kentico Certification Preparation Guide has greatly improved compared to previous versions where in the past questions were pretty simple and a vast contrast to the real thing. This allowed me to gauge a lot more on the type of questions that would potentially be presented, but I did notice quite a few questions from the preparation guide cropped up in the real exam - although slightly re-worded.

I highly recommend anyone who is interested in becoming a Kentico Certified Developer to read the following post by Jeroen Furst prior to taking the exam called: Tips for becoming a Kentico Certified Developer. Jeroen brings up some really good points and guidance to prepare yourself. If only I came across this post two years ago when I wasn't too sure what to expect (being my first Kentico exam), I would have felt more comfortable in understanding what was required.

I was expecting there to be some questions relating to MVC due to all the effort made by the Kentico development team to make the MVC integration seamless within Kentico 9. Alas, this was not the case. Jeroen also states the following:

If you are planning to take any older v6, v7 or v8 exams (if still possible) you might run into questions regarding Azure and the Intranet solution. These topics are not part of the v9 exam anymore.

The Kentico 9 exam purely focuses on the core Kentico features as well as the platform architecture every Kentico developer should know in order to build high quality sites. You will also find yourself learning some new things in the process of preparing for the exam as well as brushing up on your existing knowledge.

If you succeed, you can proudly display this badge in all its glory! ;-)

Kentico 9 Certified Developer

My Development Overview of Kentico 9 MVC

When Kentico offered the option to build websites using MVC, I was one of the many developers who jumped at the chance to utilise the new programming model. I've been building websites using the MVC programming model ever since it was first made available in Kentico 7 and with each version, the MVC implementation just got better and better. So much so, I even built my very own website (currently in Kentico 8) in MVC.

MVC in Kentico has always been a bit of a hybrid being in the sense that it wasn't true MVC to the core, which is to be expected when you have to accommodate vast array of features the Kentico platform offers. Luckily for us Kentico 9 has wholeheartedly embraced MVC with open arms and things can only get better with subsequent versions.

I have listed a few observations I thought would be good to write about from my initial experience of using MVC in Kentico 9 whilst working on a client project. I will be talking (very high level) about the changes from previous versions of Kentico MVC as well as the new development approaches for Kentico 9.

1) Goodbye "Pages", Hello "Content-only Pages" Page Types

"Content-only pages" is a new addition to Kentico 9 and is another form of Page Type, with its primary job (as the name suggests) to store content. The main difference between "Pages" and "Content-only Pages", is:

  • Aren't based on Page Templates.
  • Provides a simplified interface when managing content.
  • Does not have a presentation URL. URL patterns now need to be specified which handles the presentation to the MVC site via routing.
  • Lacks the ability to create many Page Aliases.

"Content-only pages" is a requirement to developing MVC sites in Kentico 9. Overall, I actually found "Content-only pages" quite restrictive and useful key page properties are no longer available, such as the URLs and Navigation tabs. I really do wish that these features were left in.

Kentico 9 MVC Missing Page Properties

I will be talking more about the removal of the URLs in my next point, the missing Navigation property is easier to get around. I created a base content page called "Kentico Core Content" that contained all the fields that you would normally find under Navigation properties and inherited this page type on all my content-only pages, such as Articles. You'll then have to just make the customisations to inherit these fields at code level. Easy!

Kentico 9 Core Content Page Inheritance

2) No Document Aliases

There's no option to allow the site administrator to add multiple document aliases for a page. This alone was nearly a deal breaker for me and was tempted to either go down Portal or ASPX templates route. The ability to create multiple document aliases in the URLs section is very powerful feature, especially if you plan on adding 301 redirects.

To get around this excluded feature, you will either have to use URL Rewriting at web.config level or add additional routes at controller level to carry out all specific page redirects.

So before deciding whether to choose the MVC approach, ask yourself if this is pressing feature for you and your client.

3) Separation of the CMS and Presentation Layer

Kentico 8 stepped up the MVC integration by allowing the developer to build their sites using MVC through the CMSApp_MVC project. This created a basic form of separation at project level that was much better suited compared to mixing key MVC components (Controllers/Models/Views) into what is a Web Form powered site in it's infancy in Kentico 7.

Now there is complete separation between the CMS Admin and Presentation layer (or MVC site). Since the CMSApp_MVC approach has been made obselete in Kentico 9, you now have the full ability to create an MVC site as you would do normally in a non-Kentico web application. The only way Kentico and your MVC website can talk to one another is through a Web Farm configuration.

Kentico 9 MVC Architecture

I personally love this setup. My website can be kept light as possible and still harness the power of what Kentico has to offer through using conventional API calls from the Kentico library. I can tell you this for sure, the website itself performs better than ever and no one can tell what CMS is powering the site. Good for security.

4) Licensing and Environment Setup

Due to the need for Web Farm setup to allow syncronisation of content between the CMS and MVC site, the licensing requirements have changed. Based upon how you want to setup your separate sites, Kentico provides different licensing approaches, which pretty much covers all scenarios.

My preferred setup is to run the Kentico and the MVC application under two different sites, on separate domains. Again, my reasoning comes down to catering for that additional level of security where the management of your site is on a sub-domain and not so obvious where the administration area resides. In this case, two licenses will be required. For example:

  • MVC Website: www.surinderbhomra.com
  • Kentico Admin: admin.surinderbhomra.com

You will get a free license for the Kentico site as long as the subdomain is "admin".

The only sad part (for me personally) is that Kentico CMS Free Edition license does not allow for MVC websites. I really do hope that this changes at some point. I'd love to utilise full Kentico 9 MVC on future personal projects that are currently on the free edition. Otherwise they will forever be stuck in version 8.2.

5) Page Templates

The ability to use Page Templates alongside standard page types is still available within the Kentico interface, but you can only develop an MVC site this way by creating a (now obsolete) "CMSApp_MVC" project. Kentico 9 MVC is still fully backwards compatible with this approach.

6) Retrieving Content In MVC Controllers

In Kentico 8, a controller acted as the code-behind to your Page Template where you could get all the information about a current page by calling DocumentContext.CurrentDocument. In Kentico 9, this is no longer the case and it is recommended content should be retrieved using its auto-generated code. I generally don't go down the route of using the auto-generated code. I instead like to create my own custom methods so I have the freedom to pull out the exact data my data needs by passing the Node Alias Path into my control from the URL route patten. Personal preference.

7) Friendly URL's Should Include A Node ID

Kentico recommends all page URL's should consist of NodeID and Page alias, to ensure optimum search engine optimisation on the event the page alias of pages changes on the live site. Kentico's documentation states:

Typically, a page alias will be part of a macro expression that makes up a URL pattern in content only page types. For example, a URL pattern can be specified like this /Articles/{%NodeID%}/{%NodeAlias%}. Where {%NodeAlias%} accesses the page alias.

I've gone down the route of creating a custom route contraint in my MVC project, to allow me to retrieve TreeNode for the current document via HttpContext just from passing the Node Alias only. I could go into more detail, but this is probably best suited for another blog post. 

Conclusion

The MVC integration in Kentico is like a fine wine. It gets better and better every year (in this case release) and they should be congratulated for undertaking such a humongous task. Would I choose it over Portal or ASPX pages for every project? Probably not, because I can see clients expecting functionality that is not quite present in an MVC installation as of yet.

I do like the freedom MVC gives me whilst harnessing the power of Kentico and it works great on those projects that require maximum flexibility and the seperation of code-levels allows me to do that. In addition, if my site requires scaling, I can easily move it to Azure. I am very much looking forward to what Kentico has in store for feature releases.

If there is anything I have listed in my initial observation that are incorrect, please leave a comment and I will update this post.

Kentico Contact Management: Update Visitor Information In Code

Over the last few months, I've been working away on a Kentico 9 EMS project. Very exciting indeed! Really pulling out all the stops on trying to make an amazing site in an EMS framework alongside the whole team at Syndicut.

This week, I had to develop a custom built form to grab some user's information. Unfortunately, I was unable to use Kentico's Form Builder due to the requirement where the details had to be submitted to an external API, as well as utilising a custom design.

One of the many benefits you get out-the-box from using the Kentico Form Builder is the ability to log marketing activity to the Contact Management area to store information on the visitors who view and interact with your site. By building a custom form, I was losing losing key data, such as: First Name, Last Name Email Address and Telephone No. - The base key criteria of turning an anonymous visitor to a person with some context.

To avoid this loss of data, I created a method that uses the CMS.OnlineMarketingContext class to allow you to manually update the current visitor information:

/// <summary>
/// Creates/Updates a contact in Kentico's Contact Management system.
/// </summary>
/// <param name="firstName">Visitors first name</param>
/// <param name="lastName">Visitors last name</param>
/// <param name="emailAddress">Visitors email address</param>
/// <param name="businessPhone">Visitors business phone number</param>
/// <param name="allowContactOverwrite">Overwrite existing contact fields with new information.</param>
public static void SetMarketingContact(string firstName, string lastName, string emailAddress, string businessPhone, bool allowContactOverwrite = false)
{
    ContactInfo existingContact = OnlineMarketingContext.GetCurrentContact();
            
    // Update an existing contact or create a new one if no existing contact exists.
    if (existingContact != null)
    {
        existingContact.ContactFirstName = firstName;
        existingContact.ContactLastName = lastName;
        existingContact.ContactEmail = emailAddress;
        existingContact.ContactBusinessPhone = businessPhone;

        if (allowContactOverwrite)
            existingContact.Update();
    }
}

My code only shows a handful of fields that I am updating, but the "ContactInfo" class provides access to many more.

Extending Kentico DataHelper GetDataRowValue() and GetDataRowViewValue()

After a long blogging hiatus, I decided to make my somewhat grand return! So here we go...

There are times when you need to retrieve documents from using the TreeProvider.SelectNodes() that can return DataRowViews or DataRows of node information. Now, Kentico provides "DataHelper.GetDataRowViewValue()" and "DataHelper.DataRowValue()" methods to output your required fields of information. But I find these DataHelper methods quite tedious when you have a massive collection of node data. I am lazy and would do anything to make my life easier.

So I created a DataHelperExtension class object that would allow me to get my commonly used DataRow and DataRowView fields with ease:

  • Document ID
  • Class Name
  • Node Alias Path
  • Get String Value (for custom fields)
  • Get Integer Value (for custom fields)
  • Page Menu Name
  • Node Has Children
public static class DataHelperExtensions
{
    /// <summary>
    /// Get the Document ID.
    /// </summary>
    /// <param name="dr"></param>
    /// <returns></returns>
    public static int DocumentId(this DataRow dr)
    {
        return int.Parse(DataHelper.GetDataRowValue(dr, "DocumentID").ToString());
    }

    /// <summary>
    /// Get the document Class Name.
    /// </summary>
    /// <param name="dr"></param>
    /// <returns></returns>
    public static string ClassName(this DataRow dr)
    {
        return DataHelper.GetDataRowValue(dr, "ClassName").ToString();
    }
        
    /// <summary>
    /// Get Node Alias Path.
    /// </summary>
    /// <param name="dr"></param>
    /// <param name="fullUrlPath"></param>
    /// <returns></returns>
    public static string NodeAliasPath(this DataRow dr, bool fullUrlPath = true)
    {
        if (!fullUrlPath)
            return DataHelper.GetDataRowValue(dr, "NodeAliasPath").ToString();
        else
            return DocumentURLProvider.GetUrl(DataHelper.GetDataRowValue(dr, "NodeAliasPath").ToString());
    }

    /// <summary>
    /// Get custom string field.
    /// </summary>
    /// <param name="dr"></param>
    /// <param name="fieldName"></param>
    /// <returns></returns>
    public static string GetStringValue(this DataRow dr, string fieldName)
    {
        if (DataHelper.GetDataRowValue(dr, fieldName) != null)
            return DataHelper.GetDataRowValue(dr, fieldName).ToString();
        else
            return String.Empty;
    }
        
    /// <summary>
    /// Get custom integer field.
    /// </summary>
    /// <param name="dr"></param>
    /// <param name="fieldName"></param>
    /// <returns></returns>
    public static int GetIntegerValue(this DataRow dr, string fieldName)
    {
        if (DataHelper.GetDataRowValue(dr, fieldName) != null)
            return int.Parse(DataHelper.GetDataRowValue(dr, fieldName).ToString());
        else
            return 0;
    }
        
    /// <summary>
    /// Get Menu Caption of a page, otherwise default to the Document Name.
    /// </summary>
    /// <param name="dr"></param>
    /// <returns></returns>
    public static string PageMenuName(this DataRow dr)
    {
        string menuCaption = DataHelper.GetDataRowValue(dr, "DocumentMenuCaption").ToString();

        if (String.IsNullOrEmpty(menuCaption))
            menuCaption = DataHelper.GetDataRowValue(dr, "DocumentName").ToString();

        return menuCaption;
    }

    /// <summary>
    /// Check if node has children.
    /// </summary>
    /// <param name="dr"></param>
    /// <returns></returns>
    public static bool NodeHasChildren(this DataRow dr)
    {
        return bool.Parse(DataHelper.GetDataRowValue(dr, "NodeHasChildren").ToString());
    }

    /// <summary>
    /// Get the Document ID.
    /// </summary>
    /// <param name="drv"></param>
    /// <returns></returns>
    public static int DocumentId(this DataRowView drv)
    {
        return int.Parse(DataHelper.GetDataRowViewValue(drv, "DocumentID").ToString());
    }

    /// <summary>
    /// Get the document Class Name.
    /// </summary>
    /// <param name="drv"></param>
    /// <returns></returns>
    public static string ClassName(this DataRowView drv)
    {
        return DataHelper.GetDataRowViewValue(drv, "ClassName").ToString();
    }

    /// <summary>
    /// Get Node Alias Path.
    /// </summary>
    /// <param name="drv"></param>
    /// <param name="fullUrlPath"></param>
    /// <returns></returns>
    public static string NodeAliasPath(this DataRowView drv, bool fullUrlPath = true)
    {
        if (!fullUrlPath)
            return DataHelper.GetDataRowViewValue(drv, "NodeAliasPath").ToString();
        else
            return DocumentURLProvider.GetUrl(DataHelper.GetDataRowViewValue(drv, "NodeAliasPath").ToString());
    }

    /// <summary>
    /// Get custom string field.
    /// </summary>
    /// <param name="drv"></param>
    /// <param name="fieldName"></param>
    /// <returns></returns>
    public static string GetStringValue(this DataRowView drv, string fieldName)
    {
        if (DataHelper.GetDataRowViewValue(drv, fieldName) != null)
            return DataHelper.GetDataRowViewValue(drv, fieldName).ToString();
        else
            return String.Empty;
    }

    /// <summary>
    /// Get custom integer field.
    /// </summary>
    /// <param name="drv"></param>
    /// <param name="fieldName"></param>
    /// <returns></returns>
    public static int GetIntegerValue(this DataRowView drv, string fieldName)
    {
        if (DataHelper.GetDataRowViewValue(drv, fieldName) != null)
            return int.Parse(DataHelper.GetDataRowViewValue(drv, fieldName).ToString());
        else
            return 0;
    }

    /// <summary>
    /// Get Menu Caption of a page, otherwise default to the Document Name.
    /// </summary>
    /// <param name="drv"></param>
    /// <returns></returns>
    public static string PageMenuName(this DataRowView drv)
    {
        string menuCaption = DataHelper.GetDataRowViewValue(drv, "DocumentMenuCaption").ToString();

        if (String.IsNullOrEmpty(menuCaption))
            menuCaption = DataHelper.GetDataRowViewValue(drv, "DocumentName").ToString();

        return menuCaption;
    }

    /// <summary>
    /// Check if node has children.
    /// </summary>
    /// <param name="drv"></param>
    /// <returns></returns>
    public static bool NodeHasChildren(this DataRowView drv)
    {
        return bool.Parse(DataHelper.GetDataRowViewValue(drv, "NodeHasChildren").ToString());
    }
}

Here's an example on how to put this DataHelperExtension class into use:

if (!DataHelper.DataSourceIsEmpty(data))
{
    DataView sortedData = data.Tables[0].DefaultView;

    foreach (DataRowView drvTestimonial in sortedData)
    {
        int docId = drvTestimonial.DocumentId(); // Get the Document ID
        string title = drvTestimonial.GetStringValue("Title"); // Custom field

        // Do something with the fields of data.
    }
}

Simple and effective!

Upgrading to Kentico 8.x? Some Important Steps To Not Overlook

There are many things that impress me about Kentico, especially when I compare my experiences to other CMS providers from previous walks of life. But the one thing that impresses me above all is how easy the guys at Kentico make upgrading to newer versions of their CMS platform. So I wasn't daunted when I had the job to upgrade a site from Kentico 5.5 all the way up to 8.2.

Everything went smoothly. I was in the last leg of the upgrade process where the site had been upgraded to version 7 and was about to make the transition to 8. At this point, I started encoutering issues...

Upgrading from version 7 to 8 alone is a very big jump and you will find that getting your site fully functional will require more effort than all the previous upgrades combined - depending on the size and complexity of your Kentico instance. Take a look at the "Upgrade Overview" section in the Kentico upgrade documentation for a list of important changes.

I decided to list some quite important steps based upon information I have collated from issues others have experienced as well as key points covered within the Kentico Upgrade documentation. Following the points listed below resolved my upgrade issues, so it will more than likely help you too.

1) Clear Browser Cache

After each upgrade, remember to always clear your browser of all temporary files stored in cache and old cookies prior to logging into the Administration Area. Otherwise you will more than likely see a mish-mash of old/new graphical elements, as well as an Internal Server Error popup.

Kentico 8 Upgrade - Internal Server Error

2) Run The Site After Each Upgrade

This is something I've had a tendency to forget. It is imperative that you run the site after each upgrade before moving onto the next, since Kentico requires code to be executed as well as database tasks.

3) Update Macro Signatures

This is an easy one. You'll probably see a bunch of Macro security errors in Kentico's Event Log post upgrade like these:

Kentico 8 Upgrade - Macro Resolver Error

Luckily, this is easily resolved by simply updating the macro signatures in the System > Macros > Signatures area of within the CMS Administration.

Kentico 8 Upgrade - Sign Macros

The system then resigns all macros. The new security signatures of all macros contain the username of your administrator account.

4) Re-save All Page Types

This is a strange one. For some odd reason, I experienced the same Internal Server Error popup message when logged into the CMS as described in point 1. In addition, I found when attempting to navigate directly to the website, I would get an Object not set to an instance of an object .NET error whenever a "DocumentContext.CurrentDocument" call was made.

So I decided to randomly try something Kentico master Juraj suggested from one of his forum responses, which was to add and then remove a field from a document type. Instead, I just went to the Field section of each Page Type and clicked the "Save" button.

I have no idea what difference this makes within the Kentico setup but this seems to do the trick.

5) Custom Modules Created In Version 7

If you have developed any custom modules, ensure you have marked them as "custom" before upgrading to version 8. I had numerous upgrade failures when Kentico Installation Manager was trying to upgrade the database. The error occurred in the CMS_UIElement table due to duplicated Element Resource ID's.

You can mark your custom module as "custom" in version 7 by going to: Site Manager > Development > Modules > Your Custom Module > User Interface.

Kentico 7 - Setting Element Is Custom

6) Check Data & DB Versions

After you have run an upgrade for each major version (6.0, 7.0, 8.0, 8.2), make sure you run the following SQL query against your Kentico database:

SELECT
    KeyName, KeyValue
FROM
    CMS_SettingsKey
WHERE
    KeyName IN ('CMSDBVersion', 'CMSDataVersion')

If both values for "CMSDBVersion" and "CMSDataVersion" are the same, you know the upgrade has successfully completed and you're on the right track. When I made my first attempt to upgrade a site from 7 to 8, I found the Data Version was 7.0 and the DB Version was 8.2. Not good.

Useful Links

So I Rebuilt My Site Again

Welcome to my new and improved website built in Kentico 8 and MVC Razor 5.

My old site was crying for an upgrade and now seemed like a good opportunity to make quite a few modifications, such as:

  • Upgrading to Kentico 8
  • Ditch ASP.NET Web Forms for MVC Razor 5
  • Refresh the front-end (designed by yours truly!) ;-)
  • Responsive support using Bootstrap
  • Refactored all code to improve website performance and caching

The new build has been a bit of a pet project and allowed me to put into practice everything I've learnt from over the years since my last build.

Still work in progress and more refinements are in the pipeline.

Some New Custom Form Controls I've Been Working On In Kentico...

Over the last few projects I've been working on, I started to notice that clients are requiring increased integration with social platforms that give them the ability to display key elements from well known social platforms, such as YouTube, Twitter and Instagram. Hence the reason why in the past I created a YouTube Form Control that would easily retrieve all information information about a video along with their screen caps by simply entering the YouTube URL.

Recently, I've been working on two more form controls involving Twitter and Instagram to further enhance social platform integration within Kentico from a user standpoint, which (I think) is quite neat!

Twitter

Using a combination of OAuth and Twitter's GET statuses/show/:id API endpoint, I was able to store within Kentico all JSON data relating to a tweet by just entering the ID. One major advantage to storing JSON data is that we can display the tweet as many times as we want throughout the site without worrying about breaching API limits.

In addition, individual elements of a tweet, such as the embedded image, hash tags and author information can be used when required.

Tweet Custom Form Control

As you can see (above), I am using Twitter's Embedded Tweet plugin to display the tweet in a nice graphical representation so that the site administrator is able to view contents of the tweet prior to publishing live.

Instagram

Like the Twitter control above, details of an Instagram image is retrieved in a similar fashion, whereby I'm storing all the details in JSON format.  In this case, I'm using a combination of two Instagram API endpoint's:

  • /oembed - to get the core information of an image by passing in the full URL of where the image resides. This will return important piece of information: the true ID of the image.
  • /media/media-id - to get all information about the media or video object. This information will be stored in Kentico.

Unlike Twitter, Instagram's API is a breeze to implement. There are no API limits (at time of writing) and all you need to access the endpoints is to generate a one time only access token.

Instagram Custom Form Control

By copying and pasting the link of an Instagram image will return all object information and display the image/video within CMS Desk.

I will at some point write a blog post on how I created these two controls once I have refactored all the code into one single control. At the moment, some key functionality is segregated into separate class libraries.

Kentico Certified Developer

A couple days ago I passed my Kentico exam. If anything, I think I've learnt more about Kentico and just how much the platform has to offer. The exam is filled with a wide range of questions from the very simple and straight-forward to the ones that require a more time for deep thought.

In fact, I found the first few set of questions so simple, it got me second guessing myself. I'll admit, I found the exam a little tricky and there are some questions you have to read very carefully, especially ones around K# syntax.

I dedicate this certification to all the awesome guys at Syndicut. I seriously couldn't have done it without them.

I guess I can now display this:

Kentico Certified Developer Logo

Custom Form Control To Select A Folder In Media Library

I had a need to have the ability to select a folder from within the site's media library. Not a file. A folder. The idea behind this requirement was to allow the site administrator to upload a bunch of images to a single directory in the media library, so that the contents (in this case images) could be output to the page.

Unfortunately, after contacting Kentico support, I was told that such a folder selector control does not exist and I would need to create one myself. So I did exactly that!

Step 1: Create A New User Control

I have created a user control in "/CMSFormControls/Surinder/" of my Kentico installation. I have named the user control: FolderSelector.ascx.

HTML

<table>
        <tr>
            <td class="EditingFormValueCell">
                <asp:TreeView ID="MediaLibraryTree" SelectedNodeStyle-BackColor="LightGray" ExpandDepth="0" ImageSet="Arrows" runat="server"></asp:TreeView>
            </td>
        </tr>
</table>

Code-behind

using CMS.CMSHelper;
using CMS.FormControls;
using CMS.GlobalHelper;
using CMS.MediaLibrary;
using CMS.SettingsProvider;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class CMSFormControls_iSurinder_FolderSelector : FormEngineUserControl
{
    private string _Value;

    public override Object Value
    {
        get
        {
            return MediaLibraryTree.SelectedValue;
        }
        set
        {
            _Value = System.Convert.ToString(value);
        }
    }

    public string MediaLibraryPath
    {
        get
        {
            //Get filter where condition format or default format
            return DataHelper.GetNotEmpty(GetValue("MediaLibraryPath"), String.Empty);
        }
        set
        {
            SetValue("MediaLibraryPath", value);
        }
    }

    public override bool IsValid()
    {
        bool isControlValid = true;

        if ((FieldInfo != null) && !FieldInfo.AllowEmpty)
        {
            this.ValidationError = "Please select an Image Gallery directory";
            isControlValid = false;
        }

        return isControlValid;
    }

    protected void EnsureItems()
    {
        if (MediaLibraryPath != String.Empty)
        {
            string fullPath = Server.MapPath(String.Format("~/{0}", MediaLibraryPath));

            if (Directory.Exists(fullPath))
            {
                DirectoryInfo rootDir = new DirectoryInfo(fullPath);

                TreeNode treeNodes = OutputDirectories(rootDir, null);

                MediaLibraryTree.Nodes.Add(treeNodes);
            }
            else
            {
                this.ValidationError = "Directory path does not exist.";
            }
        }
        else
        {
            this.ValidationError = "Properties for this control have not been set.";
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
            EnsureItems();
    }
    
    TreeNode OutputDirectories(DirectoryInfo directory, TreeNode parentNode)
    {
        if (directory == null)
            return null;

        //Create a node for directory
        TreeNode dirNode = new TreeNode(directory.Name, directory.FullName);

        //Get subdirectories of the current directory
        DirectoryInfo[] subDirectories = directory.GetDirectories();

        //Mark node as selected
        if (dirNode.Value == _Value.ToString())
            dirNode.Selected = true;

        //Get all subdirectories
        for (int d = 0; d < subDirectories.Length; d++)
            OutputDirectories(subDirectories[d], dirNode);
                
        //If the parent node is null, return this node
        //otherwise add this node to the parent and return the parent
        if (parentNode == null)
        {
            return dirNode;
        }
        else
        {
            parentNode.ChildNodes.Add(dirNode);

            return parentNode;
        }
    }
}

I'm hoping that the code I've shown above is quite self-explanatory. But the only thing you need to be aware of is the "MediaLibraryPath" public property. You will see in the next steps that we will be using this property to contain a link to where our Media Library resides.

Step 2: Add New Control To Kentico

When creating a custom form control in Kentico, ensure we have the following form control settings:

Kentico Folder Selector Settings

Step 3: Add Form Control Property

Remember, from Step 1, we had a property called "MediaLibraryPath". Now we just need to create this property in our custom control settings.

Kentico Folder Selector Settings

Now, when our Folder Selector control is added to a document, we will need enter a map path to the location of our Media Library. For example, "/Surinder/media/Surinder/".

If this custom control has been implemented successfully, you should see something like this when creating a new page based on a document type:

Kentico Folder Selector Tree

A Jump Into Building A Kentico Site Using MVC!

As of late, I've been attempting to expand my .NET web application development skills by learning MVC and now have the understanding on how it all works. By Jove, I think I’ve got it!

After building a few small custom websites, I decided to utilise what I've learnt and start building a Kentico site in MVC. Ever since Kentico supported MVC Razor, I've been itching to try it out.

The main drive to build a Kentico site in MVC for me has been the ability to easily build a complex site by separating an application into the model, the view, and the controller to give me a lot more control over how I want the application to be built. But the best part has to be the clean unadulterated HTML mark up that rendered on the page!

I think the mark of a good web developer is based on not only how clean their HTML markup is but also (more importantly) their programming skills. But when visiting sites, we only get exposure to the HTML markup. Unfortunately, there's only so far you can go in cleaning the markup when building a site using Web Forms. Even with View state completely disabled and selectively using .NET controls the output can still be quite mucky.

As a web developer, whenever I visit a site that interests me from a technical level, I often look at the HTML mark up just to see how clean it is. I'm always intrigued to see if a site that looks and functions great is built just as well as it looks.

But I digress...

Kentico's support for MVC is definitely impressive. I felt quite at home when moving from a custom MVC web build to a Kentico build. Of course, there are some differences in terms of where your Models, Views and Controllers reside within the file structure of your website.

If you plan on building a Kentico site using MVC, take a look at this post by Martin Hejtmanek who gives a basic overview on the steps required to get you up and running.

From what I have built so far, I haven't noticed any limitations in the MVC framework. Just workarounds are required for some features (which I'll detail in future blog posts).

Question Raised...

As much as I like having the option of building an MVC site in Kentico, I ask myself the question: In reality, how many sites I build will actually be in MVC?

The reason why I ask this is because Kentico provides many useful ready to use features out-the-box, a website could be built in half the time of an MVC build. Just think of the number of web parts Kentico has freely available to use!

You couldn't justify to a paying client the additional time and cost will create a more scalable website that produces cleaner HTML markup. For smaller websites, an MVC site in Kentico could potentially be viable and any custom controls you do make (such as pagination and login controls) could be rolled out across future sites.

Nevertheless, I'm hoping to do more MVC in Kentico moving forward.