Blog

Tagged by 'cms'

  • Published on
    -
    2 min read

    ASP.NET Core - Get Page Title By URL

    To make it easy for a client to add in related links to pages like a Blog Post or Article, I like implementing some form of automation so there is one less thing to content manage. For a Kentico Cloud project, I took this very approach. I created a UrlHelper class that will carry out the following:

    • Take in an absolute URL.
    • Read the markup of the page.
    • Selects the title tag using Regex.
    • Remove the site name prefix from title text.
    using Microsoft.Extensions.Caching.Memory;
    using MyProject.Models.Site;
    using System;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text.RegularExpressions;
    
    namespace MyProject.Helpers
    {
        public class UrlHelper
        {
            private static IMemoryCache _cache;
    
            public UrlHelper(IMemoryCache memCache)
            {
                _cache = memCache;
            }
    
            /// <summary>
            /// Returns the a title and URL of the link directly from a page.
            /// </summary>
            /// <param name="url"></param>
            /// <returns></returns>
            public PageLink GetPageTitleFromUrl(string url)
            {
                if (!string.IsNullOrEmpty(url))
                {
                    if (_cache.TryGetValue(url, out PageLink page))
                    {
                        return page;
                    }
                    else
                    {
                        using (WebClient client = new WebClient())
                        {
                            try
                            {
                                Stream stream = client.OpenRead(url);
                                StreamReader streamReader = new StreamReader(stream, System.Text.Encoding.GetEncoding("UTF-8"));
    
                                // Get contents of the page.
                                string pageHtml = streamReader.ReadToEnd();
    
                                if (!string.IsNullOrEmpty(pageHtml))
                                {
                                    // Get the title.
                                    string title = Regex.Match(pageHtml, @"\<title\b[^>]*\>\s*(?<Title>[\s\S]*?)\</title\>", RegexOptions.IgnoreCase).Groups["Title"].Value;
    
                                    if (!string.IsNullOrEmpty(title))
                                    {
                                        if (title.Contains("|"))
                                            title = title.Split("|").First();
                                        else if (title.Contains(":"))
                                            title = title.Split(":").First();
    
                                        PageLink pageLink = new PageLink
                                        {
                                            PageName = title,
                                            PageUrl = url
                                        };
    
                                        _cache.Set(url, pageLink, DateTimeOffset.Now.AddHours(12));
    
                                        page = pageLink;
                                    }
                                }
    
                                // Cleanup.
                                stream.Flush();
                                stream.Close();
                                client.Dispose();
                            }
                            catch (WebException e)
                            {
                                throw e;
                            }
                        }
                    }
    
                    return page;
                }
                else
                {
                    return null;
                }
            }
        }
    }
    

    The method returns a PageLink object:

    namespace MyProject.Models.Site
    {
        public class PageLink
        {
            public string PageName { get; set; }
            public string PageUrl { get; set; }
        }
    }
    

    From an efficiency standpoint, I cache the process for 12 hours as going through the process of reading the markup of a page can be quite expensive if there is a lot of HTML.

  • For one of my side projects, I was asked to use Butter CMS to allow for basic blog integration using JavaScript. I have never heard or used Butter CMS before and was intrigued to know more about the platform.

    Butter CMS is another headless CMS variant that allows a developer to utilise API endpoints to push content to an application via an arrange of approaches. So nothing new here. Just like any headless CMS, the proof is in the pudding when it comes to the following factors:

    • Quality of features
    • Ease of integration
    • Price points
    • Quality of documentation

    I haven't had a chance to properly look into what Butter CMS fully has to offer, but from what I have seen from working on the requirements for this side project I was pleasently surprised. Found it really easy to get setup with minimal amount of fuss! For this project I used Butter CMS's Blog Engine package, which does exactly what it says on the tin. All the fields you need for writing blog posts are already provided.

    JavaScript Code

    My JavaScipt implementation is pretty basic and provides the following functionality:

    • Outputs a list of posts consisting of title, date and summary text
    • Pagination
    • Output a single blog post

    All key functionality is derived from the "ButterCMS" JavaScript file:

    /*****************************************************/
    /*                    Butter CMS                                 */
    /*****************************************************/
    var ButterCMS =
    {
        ButterCmsObj: null,
    
        "Init": function () {
            // Initiate Butter CMS.
            this.ButterCmsObj = new ButterCmsBlogData();
            this.ButterCmsObj.Init();
        },
        "GetBlogPosts": function () {
            BEButterCMS.ButterCmsObj.GetBlogPosts(1);
        },
        "GetSinglePost": function (slug) {
            BEButterCMS.ButterCmsObj.GetSinglePost(slug);
        }
    };
    
    /*****************************************************/
    /*                Butter CMS Data                         */
    /*****************************************************/
    function ButterCmsBlogData() {
        var apiKey = "<Enter API Key>",
            baseUrl = "/",
            butterInstance = null,
            $blogListingContainer = $("#posts"),
            $blogPostContainer = $("#post-individual"),
            pageSize = 10;
    
        // Initialise of the ButterCMSData object get the data.
        this.Init = function () {
            getCMSInstance();
        };
    
        // Returns a list of blog posts.
        this.GetBlogPosts = function (pageNo) {
            // The blog listing container needs to be cleared before any new markup is pushed.
            // For example when the next page of data is requested.
            $blogListingContainer.empty();
    
            // Request blog posts.
            butterInstance.post.list({ page: pageNo, page_size: pageSize }).then(function (resp) {
                var body = resp.data,
                    blogPostData = {
                        posts: body.data,
                        next_page: body.meta.next_page,
                        previous_page: body.meta.previous_page
                    };
    
                for (var i = 0; i < blogPostData.posts.length; i++) {
                    $blogListingContainer.append(blogPostListItem(blogPostData.posts[i]));
                }
    
                //----------BEGIN: Pagination--------------//
    
                $blogListingContainer.append("<div>");
    
                if (blogPostData.previous_page) {
                    $blogListingContainer.append("<a class=\"page-nav\" href=\"#\" data-pageno=" + blogPostData.previous_page + " href=\"\">Previous Page</a>");
                }
    
                if (blogPostData.next_page) {
                    $blogListingContainer.append("<a class=\"page-nav\" href=\"#\" data-pageno=" + blogPostData.next_page + " href=\"\">Next Page</a>");
                }
    
                $blogListingContainer.append("</div>");
    
                paginationOnClick();
    
                //----------END: Pagination--------------//
            });
        };
    
        // Retrieves a single blog post based on the current URL of the page if a slug has not been provided.
        this.GetSinglePost = function (slug) {
            var currentPath = location.pathname,
                blogSlug = slug === null ? currentPath.match(/([^\/]*)\/*$/)[1] : slug;
    
            butterInstance.post.retrieve(blogSlug).then(function (resp) {
                var post = resp.data.data;
    
                $blogPostContainer.append(blogPost(post));
            });
        };
    
        // Renders the HTML markup and fields for a single post.
        function blogPost(post) {
            var html = "";
    
            html = "<article>";
    
            html += "<h1>" + post.title + "</h1>";
            html += "<div>" + blogPostDateFormat(post.created) + "</div>";
            html += "<div>" + post.body + "</div>";
            
            html += "</article>";
    
            return html;
        }
    
        // Renders the HTML markup and fields when listing out blog posts.
        function blogPostListItem(post) {
            var html = "";
    
            html = "<h2><a href=" + baseUrl + post.url + ">" + post.title + "</a></h2>";
            html += "<div>" + blogPostDateFormat(post.created) + "</div>";
            html += "<p>" + post.summary + "</p>";
    
            if (post.featured_image) {
                html += "<img src=" + post.featured_image + " />";
            }
    
            return html;
        }
    
        // Set click event for previous/next pagination buttons and reload the current data.
        function paginationOnClick() {
            $(".page-nav").on("click", function (e) {
                e.preventDefault();
                var pageNo = $(this).data("pageno"),
                    butterCmsObj = new ButterCmsBlogData();
    
                butterCmsObj.Init();
                butterCmsObj.GetBlogPosts(pageNo);
            });
        }
    
        // Format the blog post date to dd/MM/yyyy HH:mm
        function blogPostDateFormat(date) {
            var dateObj = new Date(date);
    
            return [dateObj.getDate().padLeft(), (dateObj.getMonth() + 1).padLeft(), dateObj.getFullYear()].join('/') + ' ' + [dateObj.getHours().padLeft(), dateObj.getMinutes().padLeft()].join(':');
        }
    
        // Get instance of Butter CMS on initialise to make one call.
        function getCMSInstance() {
            butterInstance = new Butter(apiKey);
        }
    }
    
    // Set a prototype for padding numerical values.
    Number.prototype.padLeft = function (base, chr) {
        var len = (String(base || 10).length - String(this).length) + 1;
    
        return len > 0 ? new Array(len).join(chr || '0') + this : this;
    };
    

    To get a list of blog posts:

    // Initiate Butter CMS.
    BEButterCMS.Init();
    
    // Get all blog posts.
    BEButterCMS.GetBlogPosts();
    

    To get a single blog post, you will need to pass in the slug of the blog post via your own approach:

    // Initiate Butter CMS.
    BEButterCMS.Init();
    
    // Get single blog post.
    BEButterCMS.GetSinglePost(postSlug);
    
  • 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

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

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