Blog

Tagged by '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.

  • 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...

  • I noticed something very strange whilst working on one of my recent Kentico projects, where I required a query string value to be case-sensitive. You might be asking why? Well the plan was to pass case-sensitive Base64 random value in a bit.ly ID format. For example: www.mysite.com/Home/iAfcTy.

    So I added a Wildcard URL to one of my pages to keep the URL looking nice and tidy. In this case: “/Home/{ID}”.

    Kentico Document Url Path

    Something with the most simplest of intensions ended up being a bit of a nightmare and to demonstrate what I experienced, see the following test-cases using Kentico’s Wildcard parameter.

    Test 1

    Passing “Hello” to the query string parameter resulted in the following:

    Kentico Wildcard Case 1

    This is the correct outcome.

    Test 2 – Things get interesting!

    Passing “HELLO” to the same query string parameter resulted in the following:

    Kentico Wildcard Case 2

    As you can see, the query string has been cached and resulted in the same value being used. It seems Kentico completely disregards the case sensitivity and it’s only by adding or removing characters that Kentico detects the value passed has changed.

    ***

    My understanding is that by default Kentico accepts the URL’s as entered by the website user. I thought by going to CMS Site Manager and changing the URL settings to “Use exactly the URL of the document” would accept case-sensitive lettering .

    Kentico Redirect Valid Urls

    As it turns out through my testing, this setting under “URL's and SEO” section doesn’t fix the issue and this may only work for document page names and not the query strings values themselves.

    For one moment, I thought I managed to find a bug in the Kentico platform and was hoping that I'd get a tree planted bearing my name through Kentico’s brilliant tree for a bug campaign. Alas, this was not the case. After discussing in great detail the problem with emails sent back and forth I couldn't seem to get the support personnel to replicate the issue.

    But if I'm experiencing this issue across different networks, workstations and installations, there must be an underlying problem within the Kentico platform.

    If one of my fellow Kentico experts can can try what I have stated in my post and report their findings in the comments section, it would be much appreciated.

    Who knows, there might be a really simple thing I’ve overlooked.

    Workaround

    Using the standard way of passing a query string value works perfectly and it only seems Kentico Wildcard URL’s  experiences this issue. So instead of using the Wildcard method, you will have to pass values in the following format:

    www.mysite.com/Home/?ID=Hello
    
  • I've been playing around with creating multilingual sites in Kentico (version 5.5R2). One of (the many) Kentico strengths is being able to configure an existing site installation to cater for multi-lingual support in a straight-forward manner.

    I came across a perplexing problem when trying to view both of my multilingual sites within the same browser. In my development environment I created two domain aliases and sites in IIS:

    1. Danish – http://172.16.1.28:8010
    2. British – http://172.16.1.28:8011

    As you can see, I differentiate between two of my site cultures by the port number.

    Kentico Domain Aliases

    I could only view one version of the site on both URL’s. This was all due to a cookie that gets created on first site visit and stores the “CMSPreferredCulture” based on the domain name excluding the port number. For those who aren’t aware, “CMSPreferredCulture” simply contains a localisation code for the site. You might be thinking: What’s the big deal?

    Kentico CMSPreferredCulture Cookie

    Well an issue only occurs when you are developing and testing the multi-lingual setup within a local environment and don’t have access to a range of unique domains. I thought that I could use the different port numbers in my environment to distinguish between the different site cultures.

    I was wrong. Kentico only uses the domain name and excludes any port  numbers. If you need to view different culture versions of your site at the same time, you will need to view them in different browsers.

    A small thing like this can cause some bewilderment for a Kentico novice. It sure bewildered me. You would expect to have the ability to view different versions of a site based on the domain aliases setup in Kentico.

    If anyone can suggest a work-around. Please leave a comment.

  • If you are storing images or files using a “Direct Uploader” field type within a document and you need to retrieve them in your code, you have two options to get this file back:

    1. Read up on the Kentico API (DocumentHelper.GetAttachment() methods).
    2. Use Kentico’s “GetFile.aspx” page to reference the file itself.

    As much as I would like to do things properly and familiarise myself with the Kentico API in greater detail, project time constraints can be a hindrance. In this case, I used the “GetFile.aspx” page in the following manner:

    <img src="/CMSPages/GetFile.aspx?guid=<Attachment GUID>" title="My Kentico Image" />
    

    The GUID for “Attachment GUID” will be found in the document where you use the “Direct Uploader” field.

    I don’t know if this is what you would necessarily call a hack. But it works!

  • I’ve been using Message Boards for some page templates within my Kentico site. I needed to count the number of messages for each document to be displayed on the homepage. I couldn’t find a way to do this using Kentico’s API.

    The only way I could retrieve the message count for a document is to query the database directly. The SQL query is as follows.

    SELECT COUNT(*)
    FROM Board_Board
    JOIN Board_Message ON Board_board.BoardID = Board_Message.MessageBoardID
    WHERE 
    Board_Message.MessageApproved = 1 AND Board_Board.BoardDocumentID = @DocumentID
    
     
    

    If anyone knows how to achieve the exact same thing through the API, please leave a comment.

  • I needed to be able to pass a file that was stored in a MemoryStream into my Kentico Media Library. In my case, the file was a dynamically generated PDF.

    I couldn’t find anything on the web on how I would achieve this. So I decided to have a go creating my own method based on the Media Library API and some very basic examples, as you can see below:

    public static string AddFile(MemoryStream file, string fileName, string description, string mediaLibrayName, string folder, string fileExt)
    {
        string cleanFileName = MakeValidFileName(fileName).Replace(" ", "-");
    
        string folderDirectory = HttpContext.Current.Server.MapPath(String.Format("/MyWebsite/media/{0}/{1}/", mediaLibrayName, folder));
    
        string mediaFilePath = String.Format("{0}{1}.{2}", folderDirectory, cleanFileName, fileExt);
    
        if (!File.Exists(mediaFilePath))
        {
            #region Create File in Media Library Directory
    
            //Check if directory exists
            if (!Directory.Exists(folderDirectory))
                Directory.CreateDirectory(folderDirectory);
    
            file.Position = 0;
    
            FileStream outStream = new FileStream(mediaFilePath, FileMode.Create, FileAccess.Write);
            file.WriteTo(outStream);
            outStream.Flush();
            outStream.Close();
    
            #endregion
    
            #region Add file info to Kentico Media library
    
            //Media Library Info - takes Media Library Name and Website Name
            MediaLibraryInfo libraryInfo = MediaLibraryInfoProvider.GetMediaLibraryInfo(mediaLibrayName, CMSContext.CurrentSiteName);
    
            // Get Relative Path to File
            string path = CMS.MediaLibrary.MediaLibraryHelper.EnsurePath(mediaFilePath);
    
            //Create media file info item
            MediaFileInfo fileInfo = new MediaFileInfo(path, libraryInfo.LibraryID, folder);
    
            fileInfo.FileTitle = fileName;
            fileInfo.FileDescription = description;
    
            // Save media file info
            MediaFileInfoProvider.ImportMediaFileInfo(fileInfo);
    
            #endregion
    
            return String.Format("/MyWebsite/media/{0}/{1}/{2}.{3}?&ext=.{3}", mediaLibrayName, folder, cleanFileName, fileExt);
        }
        else
        {
            return String.Empty;
        }
    }
    

    The method I created is generic enough for you use in your own Kentico site. It provides all the necessary parameters needed  to add an image to a media library of your choice. For example:

    AddFile(fileMemStream, “Marketing Issue", “Monthly Marketing Info”, "Private", "Premium Folder", "pdf");
    

    As much as a like using the Kentico CMS platform, I find their API documentation some what lacking in examples on how to use certain methods, especially for a new Kentico developer like myself. I am hoping this is something that will change in the near future.

  • I needed to implement a message board for users to comment on individual articles stored within my Kentico site. To achieve this, I decided to use a message board. Initially, what I found when I implemented the message board web part to my article template was that submitted comments for individual articles were getting displayed on all other articles.

    In my page I am using two Kentico controls: MessageBoardViewer to output the list of comments and MessageBoard for the comments form.

    <%@ Register Src="/CMSWebParts/MessageBoards/MessageBoard.ascx" TagName="MessageBoard" TagPrefix="cms" %>
    <%@ Register Src="/CMSWebParts/MessageBoards/MessageBoardViewer.ascx" TagName="MessageBoardViewer" TagPrefix="cms" %>
    
    <cms:MessageBoardViewer ID="MessageBoardViewer1" runat="server" Enabled="true" HideControlForZeroRows="false" DisplayOnlyApproved="true" DisplayToRoles="Registered;Paid" ShowForDocumentTypes="NewsSite.News" ZeroRowsText="No Messages in viewer" TransformationName="Community.Transformations.MessageBoard" AlternatingItemTransformationName="Community.Transformations.MessageBoard"></cms:MessageBoardViewer>
    <cms:MessageBoard ID="MessageBoard1" BoardModerated="true" runat="server" BoardUseCaptcha="false" BoardAccess="AllUsers" DisplayToRoles="Paid" BoardOpened="true" BoardRequireEmails="false"  BoardEnableSubscriptions="true" ></cms:MessageBoard>
    

    I came across a fix on the (very informative) Kentico forums whereby a user carried out a where condition on the MessageBoardViewer control to retrieve article comments through the “BoardDisplayName” field:

    MessageBoardViewer1.WhereCondition = 
        String.Concat("BoardDisplayName = '", 
                        CMSContext.CurrentDocument.GetValue("Title"), " (", 
                        CMSContext.CurrentDocument.DocumentNamePath, 
                      ")'"); 
    

    Some of you may not know, the Board Display Name field is also used in the Message board section within CMS Desk.

    Kentico Message Board Admin

    Retrieving comments based on the Board Display Name is in my opinion not the best way. As you can see from the title of my document (above) contains single quotes. This would cause an SQL syntax error (which I did experience).

    To get around this, it is best to query the MessageBoardViewer control using the “BoardDocumentID” field. So the code will be as follows:

    MessageBoardViewer1.WhereCondition = 
        String.Concat("BoardDocumentID = ", 
                        CMSContext.CurrentDocument.DocumentID); 
    

    If anyone knows of a better way of achieving the same thing. Please leave a comment. I am relatively new to Kentico and probably missed a trick!