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'sGET 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.

Renaming A Document Type Table Using SQL

Don't you just hate it when you've created a Document Type in Kentico that is full of data and for some reason you need to rename it. I know I do.

Even though Kentico does give you the ability to rename your Document Types via its interface, the old table name still exists within the SQL database. This may not bother some developers but this makes me feel damn right dirty.

I came across an SQL script Matt Lee wrote in his blog, that takes the hassle of renaming the tables manually within a Kentico database.

Definitely check it out. It has saved me a lot of time.

Source: SQL Script To Rename A Document Type Table

Create A Custom YouTube Form Control In Kentico

It seems that I have a tendency to blog more about YouTube then any other Social API on this site. So here we go again... This time I want to show how to easily integrate a YouTube CMS Form Control within a Custom Table or Document Type within Kentico.

As far as I'm aware, Kentico only allows you to insert YouTube markup into their HTML Editable Regions via the CKEditor. But what if you wanted to take things a step further and have the ability to return a video Title, Description and Thumbnail within the comfort of the Form tab?

YouTube Custom CMS Form Control

As you can see from my custom form control, a user would paste the URL of a YouTube video and press the "Lookup Video" button that will return basic information about that video, ready for the user to carry out any further copy changes they require.

So let's get to it.

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: YouTubeLookup.ascx.

HTML

<table><tbody>  <tr>        <td class="TextColumn">         <label for="<%=YouTubeUrl.ClientID >">URL:</label> <asp:textbox id="YouTubeUrl" runat="server"></asp:textbox> <asp:button cssclass="ContentButton" id="LookupVideoDetail" onclick="LookupVideoDetail_Click" runat="server" text="Lookup Video"> </asp:button></td>  </tr>   <tr>        <td class="TextColumn">         <label for="<%=YouTubeTitle.ClientID >">Title:</label> <asp:textbox id="YouTubeTitle" runat="server" width="500"></asp:textbox></td>    </tr>   <tr>        <td class="TextColumn">         <label for="<%=YouTubeDescription.ClientID >">Description:</label> <asp:textbox height="100" id="YouTubeDescription" runat="server" textmode="MultiLine" width="500"></asp:textbox></td>    </tr>   <tr>        <td class="TextColumn">         <asp:image id="YouTubeThumbnail" runat="server"></asp:image></td>   </tr></tbody>
</table>

Code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using CMS.FormControls;
using CMS.GlobalHelper;
using System.Web.Script.Serialization;

public partial class CMSFormControls_Surinder_YouTubeLookup : FormEngineUserControl
{
    private string _jsonValue;

    public override Object Value
    {
        get
        {
            return GetJsonMarkup();
        }
        set
        {
            _jsonValue = System.Convert.ToString(value);
        }
    }

    private string GetJsonMarkup()
    {
        //Pass all user entered form values to the YouTubeDetail class for serialization in the JavaScriptSerializer
        if (!String.IsNullOrEmpty(YouTubeUrl.Text))
        {
            YouTubeDetail yt = new YouTubeDetail();
            yt.ID = YouTubeHelper.GetVideoID(YouTubeUrl.Text);
            yt.Title = YouTubeTitle.Text;
            yt.Description = YouTubeDescription.Text;
            yt.Url = YouTubeUrl.Text;
            yt.ImageUrl = YouTubeThumbnail.ImageUrl;

            JavaScriptSerializer jsSerialize = new JavaScriptSerializer();

            return jsSerialize.Serialize(yt);
        }
        else
        {
            return String.Empty;
        }
    }

    public override bool IsValid()
    {
        JavaScriptSerializer jsSerialize = new JavaScriptSerializer();
        var jsResult = jsSerialize.Deserialize<YoutubeDetail>(_jsonValue);

        if (jsResult != null && !String.IsNullOrEmpty(jsResult.ToString()))
        {
            if (String.IsNullOrEmpty(jsResult.Url))
                return false;
            else
                return true;
        }
        else
        {
            return true;
        }
    }

    protected void EnsureItems()
    {
        PopulateControls();
    }

    private void PopulateControls()
    {
        JavaScriptSerializer jsSerialize = new JavaScriptSerializer();
        var jsResult = jsSerialize.Deserialize<YoutubeDetail>(_jsonValue);

        //Check there if JSON is present to populate form controls
        if (jsResult != null && !String.IsNullOrEmpty(jsResult.ToString()))
        {
            if (!String.IsNullOrEmpty(jsResult.Url))
                YouTubeUrl.Text = jsResult.Url;

            if (!String.IsNullOrEmpty(jsResult.Title))
                YouTubeTitle.Text = jsResult.Title;

            if (!String.IsNullOrEmpty(jsResult.Description))
                YouTubeDescription.Text = jsResult.Description;

            if (!String.IsNullOrEmpty(jsResult.ImageUrl))
                YouTubeThumbnail.ImageUrl = jsResult.ImageUrl;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
            EnsureItems();
    }

    protected void LookupVideoDetail_Click(object sender, EventArgs e)
    {
        //If YouTube URL is present, get the information
        if (!String.IsNullOrEmpty(YouTubeUrl.Text))
        {
            YouTubeDetail yt = YouTubeHelper.GetVideoInformation(YouTubeUrl.Text);

            if (yt != null)
            {
                YouTubeTitle.Text = yt.Title;
                YouTubeDescription.Text = yt.Description;
                YouTubeThumbnail.ImageUrl = yt.ImageUrl;
            }
        }
    }
}

From looking at my code, you've probably noticed I'm actively using a "JavaScriptSerializer" to pass all my form values as JSON. I find this is the most straight-forward way to store multiple form values in a custom control. In this case, our values will be stored within a Kentico table column in the following format:

{
    "ID":"fLyoog562x4",
    "Title":"How The Dark Knight Rises Should Have Ended",
    "Description":"Check out HISHE\u0027s spin on the epic conclusion to The Dark Knight Trilogy: How The Dark Knight Rises Should Have Ended.",
    "Url":"http://www.youtube.com/watch?v=fLyoog562x4",
    "ImageUrl":"http://i1.ytimg.com/vi/fLyoog562x4/hqdefault.jpg"
}

Whenever I need to get those values back, all I need to do is call the JavaScriptSerializer.Deserialize method.

NOTE: If what I have shown doesn't make any sense, it'll be useful to take a look at an in-depth tutorial on how to create a Custom Form Control in Kentico: http://devnet.kentico.com/docs/devguide/index.html?developing_form_controls.htm

Step 2: Create YouTubeDetail Class

In order to serialize and deserialize values when using the JavaScriptSerializer, we need to create a class object with a number of properties to interpret the JSON structure.

public class YouTubeDetail
{
    public string ID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Url { get; set; }
    public string ImageUrl { get; set; }
}

Step 3: YouTube Methods

This is the part when we start using Google's YouTube API and in order for this class to work, you will need to download the necessary DLL's. I suggest you take a gander at a post I wrote a while back called "Dynamically Output A List of YouTube Videos In ASP.NET" to get an in-depth introduction into using the YouTube API.

To get data back from YouTube you will need as a minimum requirement the DLL's and register your application in order to pass an Application Name, Developer Key and Client ID values to your application.

public class YouTubeHelper
{
    private static string YouTubeDeveloperKey = WebConfigurationManager.AppSettings["YouTubeDeveloperKey"].ToString();
    private static string YouTubeAppName = WebConfigurationManager.AppSettings["YouTubeAppName"].ToString();
    private static string YouTubeClientID = WebConfigurationManager.AppSettings["YouTubeClientID"].ToString();
 
    //Get YouTube video
    public static Video YouTubeVideoEntry(string videoID)
    {
        YouTubeRequestSettings settings = new YouTubeRequestSettings(YouTubeAppName, YouTubeClientID, YouTubeDeveloperKey);
        YouTubeRequest request = new YouTubeRequest(settings);
 
        //Link to the feed we wish to read from
        string feedUrl = String.Format("http://gdata.youtube.com/feeds/api/videos/{0}", videoID);
 
        Feed<Video> videoFeed = request.Get<Video>(new Uri(feedUrl));
 
        return videoFeed.Entries.SingleOrDefault();
    }
 
    //Extract the YouTube ID from the web address.
    public static string GetVideoID(string videoUrl)
    {
        Uri tempUri = new Uri(videoUrl);
 
        string sQuery = tempUri.Query;
 
        return System.Web.HttpUtility.ParseQueryString(sQuery).Get("v");
    }
 
    //Get required YouTube video information
    public static YouTubeDetail GetVideoInformation(string url)
    {
        Video v = YouTubeVideoEntry(GetVideoID(url));
 
        //Pass required YouTube information to custom class called YouTubeDetail
        YouTubeDetail vDetail = new YouTubeDetail();
        vDetail.ID = v.VideoId;
        vDetail.Title = v.Title;
        vDetail.Description = v.Description;
        vDetail.ImageUrl = v.Thumbnails[2].Url;

        return vDetail;
    }

    //Output YouTube property within a document by passing the Document ID
    public static YouTubeDetail GetDocumentYouTubeValue(int docID)
    {
        TreeProvider tree = new TreeProvider();
        TreeNode tn = tree.SelectSingleDocument(docID);

        if (tn.GetValue("YouTube") != null && !String.IsNullOrEmpty(tn.GetValue("YouTube").ToString()))
        {
            JavaScriptSerializer jsSerialize = new JavaScriptSerializer();
            var jsResult = jsSerialize.Deserialize<YouTubeDetail>(tn.GetValue("YouTube").ToString());

            if (jsResult != null && !String.IsNullOrEmpty(jsResult.ToString()))
            {
                return jsResult;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }
}

Step 4: Add New Control Into Kentico

Please refer to the Kentico Development Guide (as referenced in Step 1). The main thing you need to ensure is that the control can only be used as a "Long text" type.

YouTube Kentico Control Settings

Step 5: Outputting The YouTube Values To A Page

Since we have stored all our YouTube fields in a JSON string, we can get those values out by carrying out a deserialization on our document type property.

if (CMSContext.CurrentDocument.GetStringValue("YouTubeVideo", String.Empty) != String.Empty)
{
    JavaScriptSerializer jsSerialize = new JavaScriptSerializer();
    YouTubeDetail yt = jsSerialize.Deserialize<YouTubeDetail>(CMSContext.CurrentDocument.GetStringValue("YouTubeVideo", String.Empty));

    YouTubeTitle.Text = yt.Title;
    YouTubeDescription.Text = yt.Description;
    YouTubeUrl.Text = yt.Url;
}

You may think I have slightly over-engineered the process to store YouTube video's. But if you have a website that is trying to push video content along with its META data, I believe this is the way to go.

Goodbye BlogEngine. Hello Kentico!

Kentico LogoFor many years, I've been a happy BlogEngine user. However, recently my website was starting to expand in a way that wasn't flexible enough for the BlogEngine platform. Don't get me wrong, BlogEngine is a great blogging platform and it is without a doubt one of the best out on the market. But the capabilities and features Kentico provides made moving over to another platform an easy decision.

The future of my site needed something that would give me free reign and control on making full customisations myself in a solid framework, and Kentico seemed to fit this requirement.

Having worked with the Kentico platform for quite a few years now, I was impressed by how easy or complex I could make a site. Most importantly, the page perfomance of my site has got quite a boost. For a site that doesn't look like is doing much at face value, it is behind the scenes.

What was the migration process like?

Migrating all my BlogEngine content into Kentico did take some time, but the Kentico Import tool really does take the hassle out of getting key blog post information into the CMS. Just don't be expect tags and categories to be imported in the process. Migrating these facets is a manual job.

In addition, I decided to overhaul the friendly URL's used for my blog posts in keeping with the URL structure provided by BlogEngine. Even to this day, I'm not sold on the way Kentico generates it's friendly URL's for blog posts. For example:

/Blog/February-2013/My-Blog-Post.aspx

When it should be the following format:

/Blog/2013/02/03/My-Blog-Post.aspx

Luckily, it was pretty easy to write a Custom Eventhandler to add additional custom URL paths whenever I update or insert a new post (will add a post in the near future on how to do this).

I still have some additional features to add to this site (and dare I say fixes!) so watch this space...