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.

My First Butter CMS JavaScript Implementation

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);

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

What Prismic.io Is Lacking

WARNING! I may sound like an absolute hypocrite when the contents of this post is compared to my earlier post on first impressions of the Prismic.io platform. So here we go...

I am starting to encounter increasingly longwinded and somewhat frustrating stumbling blocks during the development of a Prismic.io powered website due to lack of basic development related features. Fundamental features that should already be there from the start.

I understand that Prismic.io is a new platform and is still in its infancy, but not having something simple as a time attribute to a date field is unforgivable (which I will explain later).

The idea behind Prismic.io is to empower the developer and gives them the tools to manage the content anyway they want. Sounds great! But how can developers like myself be empowered when the tools that are provided are not up to scratch.

So I have picked a few things lacking in the Prismic.io platform. I'll probably add some more on completion of the project I'm working on.

1) Sorting By Date/Time

Now you'd think if you have a date field, a time field would be not too far away. Wrong! A document only contains a date format field that shows a calendar on selection. This works for general use. But what if you have numerous articles written in a day that are displayed on a page in descending order and you wish to move an article higher up the page? There is no time field to allow for this.

By default when using date ordering, two things happen:

  1. All documents are ordered by the date value defined in the document.
  2. If multiple documents added within a day, they are then ordered by the time it was added in Prismic.

For me, this was a pain.

2) Non-match predicate

Sometimes, you want the ability to exclude documents from a query. In my case, return a list of authors except for one or two. Since Prismic.io predicate language is lacking a "not" operator I had to return a full list of authors and carry out the filtering at application level.

In the grand scheme of things, this isn't a massive flaw. I can see this becoming an issue when you need to exclude items from a larger dataset. It would be faster to do this at Prismic level than application level.

3) Where's the "OR" Operator?

No really, I would like to know!

4) No Required or Validation Fields

It is not possible to make fields compulsory or implement any form of validation. Therefore, up to the developer to make sure suitable checks are put in place where null or incorrect values are present.

To me, this seems a little bit backwards and you're solely relying on the editors to ensure the all data is correct and complete.

5) WYSIWYG Editor Improvements

As I stated in my previous post, that one of main deciding factors to why I used Prismic over Contentful was its easy to use WYSIWYG editor. I still stand by this point. It seems to offer a mish-mash of features that feel very intelligent and basic at the same time.

The WYSIWYG functionality is based on a StructuredText field type, flexible enough to allow an amalgamation of different content, such as embedded object (from social websites), paragraphs, images, etc.

On the surface, StructuredText is really nice to work with but then all of a sudden you encounter a key missing feature: blockquote! The only way I could get around this is by getting editors to insert custom mark up around any text for transformation into a blockquote at application level, like so:

[BlockQuote][Hello. I would like to blockquote this text please.]

This was just the start. There were other instances where further customisation had to be made to meet the editors requirements.

I have to quote Paul Dijou here (link at bottom of post) for describing the additional changes he too had to make in a very theatrical manner:

A writer wanted to have blockquotes: a whole paragraph should be displayed in a custom design and have an author. I had to kill him really fast and bury his body deep. Another one wanted semantic distinction between paragraphs, something like: this one should be red and this one blue just because. Thrown him into a bucket full of piranhas.

6) Technical Support

A platform or technology can only ever be as successful as the infrastructure present to support it. Without it, cracks will form. Currently, there is only one place you can ask a question: https://qa.prismic.io. It's definitely no StackOverflow. You really have to hope and pray for someone to answer your question promptly.

7) Convoluted Production Workflow

There will come a time when additional changes to a live site will be required. Whether it be modifications to a field or addition for a new document. All these changes will have to happen on the live Prismic repository. There is no development > stage > live workflow.

It would be nice to have the ability to duplicate repositories and push new releases.

Thankfully, someone has already raised this. I don't see this addition happening anytime soon.

Summary

My intention is not to give a very negative impression of the Prismic.io platform. It will most likely meet your content management needs. However, it does have its faults and unless modifications are made to some of the points raised from others in their Q&A forum and my post, I will have to question whether I use it again on a project by project basis. It's a CMS platform that just falls short of the mark.

I recommend reading the following blog post written by Paul Dijou, describing his own experiences working with Prismic: http://platformpauldijou.fr/blog/2014/07/17/prismicio-when-happiness-met-disappointment.

NOTE: If I have stated something that I have got completely wrong due to a lack of understanding. Let me know and I'll take everything back! :-)

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.

Prismic.io - Content Management for The Masses

Generally, all Content Management Systems are tightly integrated into the websites they control to serve one key function: publish custom content. Almost as one singular entity. From the moment you choose a CMS, you shall be forever locked down by its required platform and technology.

So in terms of the CMS world, nothing revolutionary has happened to change our perception otherwise...until now...

I have been doing some research into some content management systems that sits externally from a platform (such as a website), giving you freedom to manage the content however you like and it something that's gaining a lot of traction. I am starting to see why.  In fact, I'm in the middle of building a site using one of these "externally" managed CMS platforms.

I would say the the main market players are Contentful and Prismic. They both are very similar in the features they provide and do a great job in delivering content to a platform of your choice through simply querying their native API's to return a nice JSON feed. So from a development perspective, they're both just as easy to integrate as each another and the deciding factors on the one you choose will primarily be:

  • Price
  • Ease of use
  • Editor features

Based on these factors alone, I found Prismic to be the ideal candidate to fulfill my clients needs and adding content was a pleasure. It probably has the nicest interface I've seen in a long time. Very quick, easy and has something Contentful didn't have: a nice WYSIWYG editor. The markdown editor alone in Contentful was a deal breaker and I feared it would add an additional learning curve for non-technical clients.

The only strange thing I noticed about Prismic was that you cannot add any form of validation or set a field to be required. Hopefully, this is something they will add to future releases. When you have other great features like an easy image upload to Amazon Cloud for resizing and cropping, having no validation isn't all that important. :-)

I am already more than halfway through my first Prismic managed website and the implementation couldn't be easier with the help of their forum and starter projects in the technology of your choice.

One of the fears I did have whilst implementing Prismic was how well will my pages load on high demand, especially when the content itself is external from the website. Would there be issues or delays in sending content to my platform? I guess this question is still yet to be answered. So far, the page speed has been better than expected (based on initial testing).

Prismic in a nutshell (stolen from their website):

prismic.io is a developer friendly, API-based approach to CMS. It features a Writing Room for content writers to author, manage and store content, and a Content Query API for developers to integrate managed content. Your content doesn't live "in a website / in websites", your project doesn't live "in a CMS"; rather, your content lives in one place and is shared across your websites, and your project lives absolutely anywhere you want.

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