Kentico 12 MVC: Get List of Widgets Used On A Page

There are times when you need to know what widgets are being used on a page. In my case, I needed to know this information in order to render JavaScript code at the bottom of the page that each of my individual widgets depends on.

Why don't I just place all the JavaScript code my site and widgets use in one file? Loading one large JavaScript file isn't the best approach for page performance. Instead, I use LabJS to dynamically load scripts in specific execution order without blocking other resources. So if I created a Carousel widget in Kentico, I would only load the JavaScript plugin if added to the page.

I'll use my JavaScript scenario as a basis for demonstrating the way to list out widgets used in a page.

If we delve into the CMS_Document table, Kentico uses the "DocumentPageBuilderWidgets" field that stores a JSON structure consisting of a list of all the widgets and their property values. All we are interested in is the type property.

Let's get to the code.

Controller - SharedController

I created a SharedController class containing a GenerateWidgetJavascript() PartialViewResult. This will convert the JSON from the "DocumentPageBuilderWidgets" field into a JSON Object to then be queried (using SelectTokens) to select every iteration of the type field in the hierarchy.

/// <summary>
/// Get widget used on a page to render any required JavaScript.
/// </summary>
/// <returns></returns>
public PartialViewResult GenerateWidgetJavascript()
{
    List<string> widgetTypes = new List<string>();

    if (Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty) != string.Empty)
    {
        JObject pageWidgetJson = JObject.Parse(Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty));

        if (pageWidgetJson != null)
            widgetTypes = pageWidgetJson.SelectTokens("$.editableAreas[*].sections[*].zones[*].widgets[*].type").Select(jt => jt.ToString().Substring(jt.ToString().LastIndexOf('.') + 1)).Distinct().ToList();
    }

    return PartialView("Widgets/_PageWidgetJs", widgetTypes);
}

Additional manipulation is carried out on the type field using LINQ to return a distinct set of results, as there might be a case where the same widget is used multiple times on a page. Since I name all my widgets in the following format - <SiteName>.<WidgetGroup>.<WidgetName>, I am only interested in the <WidgetName>. For example, my widget would be called "SurinderSite.Layout.Carousel". The controller will simply output "Carousel".

To avoid confusion in my code snippet, it's worth noting I use a Page variable. This contains information about the current page and is populated in my base controller. It has a type of TreeNode. You can see my approach to getting the current page information in this post.

Partial View - _PageWidgets

The most suitable place to add my widget dependent JavaScript is in the /View/Shared/Widgets directory - part of the recommended Kentico project structure.

All we need to do in the view is iterate through the string collection of widget types and have a bunch of if-conditions to render the necessary JavaScript.

@model List<string>

@if (Model.Any())
{
    <script>
        @foreach (string widgetType in Model)
        {
            if (widgetType == "Carousel")
            {
                <text>
                    $LAB
                        .script("/resources/js/plugins/slick.min.js")
                        .script("/resources/js/carousel.min.js").wait(function () {
                            {
                                FECarousel.Init();
                            }
                        });
                </text>
            }
        }
    </script>
}

Layout View

The Layout view will be the best place to add the reference to our controller in the following way:

@{ Html.RenderAction("GenerateWidgetJavascript", "Shared"); }

Temporary ASP.NET Files Directory Compilation Error After Project Rename

After renaming my MVC project from "SurinderBhomra" to "Site.Web" (to come across less self-serving) I get the following error:

Compiler Error Message: CS0246: The type or namespace name 'xxx' could not be found (are you missing a using directive or an assembly reference?)

CS0246 Compiler Error - Temporary ASP.NET Files

But the misleading part of this compiler error is the source file reference to ASP.NET Temporary Files directory, which led me to believe that my build was been cached when it was actually to do with the fact I missed some areas where the old project name still remained.

I carried out a careful rename throughout my project by updating all the places that mattered, such as:

  • Namespaces
  • Using statements
  • "AssemblyTitle" and "AssemblyProduct" attributes in AssemblyInfo.cs
  • Assembly name and Default Namespace in Project properties (followed by a rebuild)

The key area I missed that caused the above compiler error is overlooking the the namespace section in the /Views/web.config file.

<system.web.webpages.razor>
  <host factorytype="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  <pages pagebasetype="System.Web.Mvc.WebViewPage">
    <namespaces>
      ...
      ...
      ...
      <add namespace="System.Web">
      ...
      ...
      ...
    </add></namespaces>
  </pages>
</host></system.web.webpages.razor>

When you first create your project in Visual Studio, it automatically adds its original namespace to this file. This also goes for any other web.config you happen to have nested in other areas inside you MVC project.

Structuring Navigation and Other Page Properties In Kentico 12 MVC

As great as it is building sites using the MVC framework in Kentico, I do miss some of the page features we’re spoilt with that previous editions of Kentico has to offer.

Like anything, there are always workarounds to implement the features we have become accustomed to but ensuring the correct approach is key, especially when moving existing Kentico clients onto Kentico 12 MVC edition. Familiarity is ever so important for the longer tenure clients who already have experience using previous versions of Kentico.

In my Syndicut Medium post, I show how to use Kentico's existing fields from the CMS_Document table to work alongside the new MVC approach.

Take a read here: https://medium.com/syndicut/adding-navigation-properties-to-pages-in-kentico-12-mvc-8f054f804af2

ASP.NET Developers Who Use Eval()... Evaluate Yourself!

The title of this post might seem a tad extreme, but I just feel so strongly about it! Ever since I started learning ASP.NET those many years ago, I've never been a fan of using "Eval" in data-bound controls I primarily use, such as GridViews, Repeaters and DataList. When I see it still being used regularly in web applications I cringe a little and I feel I need to express some reasons to why it should stop being used.

I think working on an application handed down to me from an external development agency pushed me to write this post... Let's call it a form of therapy! I won't make this post a rant and will "try" to be constructive and concise. My views might come across a little one-sided, but I promise I will start with at least one good thing to say about our evil friend Eval.

Postive: Quick To Render Simple Data

If the end goal is to list out some string values as is from the database with some minor manipulation from a relatively small dataset, I almost have no problem with that, even though I still believe it can be used and abused by inexperienced developers.

Negative: Debugging

The main disadvantage of embedding code inside your design file (.aspx or .ascx) is that it's not very easy to view the output during debugging. This causes a further headache when your Eval contains some conditional statements to alter the output on a row-by-row basis.

Negative: Difficult To Carry Out Complex HTML Changes

I wouldn't recommend using Eval in scenario's where databound rows require some form of HTML change. I've seen some ugly implementations where complex conditional statements were used to list out data in a creative way. If the HTML ever had to be changed through design updates, it would be a lot more time consuming to carry when compared to moving around some form controls that are databound through a RowDataBound event.

Negative: Ugly To Look At

This point will come across very superficial. Nevertheless, what I find painful to look at is when Eval is still used to carry out more functionality by calling additional methods and potentially repeating the same functionality numerous times.

Performance/Efficiency

From my research, it's not clear if there specifically is a performance impact in using Eval alone, especially with the advances in the .NET framework over the years. A post from 2012 on StackExchange brought up a very good point:

Eval uses reflection to get the value of the relevant property/field, and using Reflection to get values from object members is very slow.

If the type of an object can be determined at runtime, you're better off explicitly declaring this. After all, it's good coding standards. In the real world, the performance impact is nominal depending on the number of records you are dealing with. Not recommended for building scalable applications. I generally notice a slow down (in milliseconds) when outputting 500 rows of data.

I have read that reflection is not as much of an issue in the most recent versions of the .NET framework when compared to say, .NET 1.1. But I am unable to find any concrete evidence of this. Regardless, I'd always prefer to use the faster approach, even if I am happening to shave off a few milliseconds in the process.

Conclusion

Just don't use Eval. Regardless of the size of the dataset I am dealing with, there would only be two approaches I'd ever use:

  1. RowDataBoundEvent: A controls RowDataBoundEvent event is triggered every time a row is databound with data. This approach enables us to modify the rows appearance and structure in a specific way depending on the type of rules we have in place.
  2. Start From Scratch: Construct the HTML markup by hand based on the datasource and render to the page.

If I were to be building a scalable application dealing with thousands of rows of data, I am generally inclined to go for option 2. As you're not relying on a .NET control, you won't be contributing to the page viewstate.

Even though I have been working on a lot more applications using MVC where I have more control on streamlining the page output, I still have to dabble with Web Forms. I feel with Web Forms, it's very easy to make a page that performs really bad, which makes it even more important to ensure you are taking all necessary steps to ensure efficiency.

Kentico MVC - Getting TreeNode At Controller Level

I wrote a post a couple of years ago regarding my observations on developing a Kentico site using MVC in version 9. Ever since Kentico 9, there was a shift in how MVC applications are to be developed, which has pretty much stood the test of time as we've seen in releases since then. The biggest change being the CMS itself is purely used to manage content and the presentation layer is a separate MVC application connected through a Web Farm interface.

The one thing I missed when working on sites in Kentico's MVC is the ability to get values from the current document as you could do in Kentico 8 MVC builds:

public ActionResult Article()
{
    TreeNode page = DocumentContext.CurrentDocument;

    // You might want to do something complex with the TreeNode here...

    return View(page);
}

In Kentico 11, the approach is to use wrapper classes using the Code Generator feature the Kentico platform offers from inside your Page Type. The Kentico documentation describes this approach quite aptly:

The page type code generators allow you to generate both properties and providers for your custom page types. The generated properties represent the page type fields. You can use the providers to work with published or latest versions of a specific page type.

You can then use these generated classes inside your controllers to retrieve page type data.

Custom Route Constraint

In order to go down a similar approach to get the current document just like in Kentico 8, we'll need to modify our MVC project and add a custom route constraint called CmsUrlConstraint. The custom route constraint will call DocumentHelper.GetDocument() method and return a TreeNode object based on the Node Alias path.

CmsUrlConstraint

Every Page Type your MVC website consists of will need to be listed in this route constraint, which will in turn direct the incoming request to a controller action and store the Kentico page information within a HttpContext if there is a match. To keeps things simple, the route constraint contains the following pages:

  • Home
  • Blog
  • Blog Month
  • Blog Post
public static class RouteConstraintExtension
{
    /// <summary>
    /// Set a new route.
    /// </summary>
    /// <param name="values"></param>
    /// <param name="controller"></param>
    /// <param name="action"></param>
    /// <returns></returns>
    public static RouteValueDictionary SetRoute(this RouteValueDictionary values, string controller, string action)
    {
        values["controller"] = controller;
        values["action"] = action;

        return values;
    }

    #region CMS Url Contraint

    public class CmsUrlConstraint : IRouteConstraint
    {
        /// <summary>
        /// Check for a CMS page for the current route.
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="route"></param>
        /// <param name="parameterName"></param>
        /// <param name="values"></param>
        /// <param name="routeDirection"></param>
        /// <returns></returns>
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string pageUrl = values[parameterName] == null ? "/Home" : $"/{values[parameterName].ToString()}";

            // Check if the page is being viewed in preview.
            bool previewEnabled = HttpContext.Current.Kentico().Preview().Enabled;

            // Ignore the site resource directory containing Image, CSS and JS assets to save call to Kentico API.
            if (pageUrl.StartsWith("/resources"))
                return false;

            // Get page from Kentico by alias path in its published or unpublished state.
            // PageLogic.GetByNodeAliasPath() method carries out the document lookup by Node Alias Path.
            TreeNode page = PageLogic.GetByNodeAliasPath(pageUrl, previewEnabled);

            if (page != null)
            {
                // Store current page in HttpContext.
                httpContext.Items["CmsPage"] = page;

                #region Map MVC Routes

                // Set the routing depending on the page type.
                if (page.ClassName == "CMS.Home")
                    values.SetRoute("Home", "Index");

                if (page.ClassName == "CMS.Blog" ||  page.ClassName == "CMS.BlogMonth")
                    values.SetRoute("Blog", "Index");

                if (page.ClassName == "CMS.BlogPost")
                    values.SetRoute("Blog", "Post");

                #endregion

                if (values["controller"].ToString() != "Page")
                    return true;
            }

            return false;
        }
    }

    #endregion
}

To ensure page data is returned from Kentico in an optimum way, I have a PageLogic.GetByNodeAliasPath() method that ensures cache dependencies are used if the page is not in preview mode.

Apply Route Constraint To RouteConfig

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        ...

        // Maps routes to Kentico HTTP handlers.
        // Must be first, since some Kentico URLs may be matched by the default ASP.NET MVC routes,
        // which can result in pages being displayed without images.
        routes.Kentico().MapRoutes();

        // Custom MVC constraint validation to check for a CMS template, otherwise fallback to default MVC routing.
        routes.MapRoute(
            name: "CmsRoute",
            url: "{*url}",
            defaults: new { controller = "HttpErrors", action = "NotFound" },
            constraints: new { url = new CmsUrlConstraint() }
        );

        ...
    }
}

Usage In Controller

Now that we have created our route constraint and applied it to our RouteConfig, we can now enjoy the fruits of our labour by getting back the document TreeNode from HttpContext. The code sample below demonstrates getting some values for our Home controller.

public class HomeController : Controller
{
    public ActionResult Index()
    {
        TreeNode currentPage = HttpContext.Items["CmsPage"] as TreeNode;

        if (currentPage != null)
        {
            HomeViewModel homeModel = new HomeViewModel
            {
                Title = currentPage.GetStringValue("Title", string.Empty),
                Introduction = currentPage.GetStringValue("Introduction", string.Empty)
            };

            return View(homeModel);
        }

        return HttpNotFound();
    }
}

Conclusion

There is no right or wrong in terms of the approach you as a Kentico developer use when getting data out from your page types. Depending on the scale of the Kentico site I am working on, I interchange between the approach I detail in this post and Kentico's documented approach.

ASP.NET Core - Render Partial View To String Outside Controller Context

When building MVC websites, I cannot get through a build without using a method to convert a partial view to a string. I have blogged about this in the past and find this approach so useful especially when carrying out heavy AJAX processes. Makes the whole process of maintaining and outputting markup dynamically a walk in the park.

I've been dealing with many more ASP.NET Core builds and migrating over the RenderPartialViewToString() extension I developed previously was not possible. Instead, I started using the approach detailed in the following StackOverflow post: Return View as String in .NET Core. Even though the approach was perfectly acceptable and did the job nicely, I noticed I had to make one key adjustment - allow for views outside controller context.

The method proposed in the StackOverflow post uses ViewEngine.FindView(), from what I gather only returns a view within the current controller context. I added a check that will use ViewEngine.GetView() if a path of the view ends with a ".cshtml" which is normally the approach used when you refer to a view from a different controller by using a relative path.

public static class ControllerExtensions
{
    /// <summary>
    /// Render a partial view to string.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="controller"></param>
    /// <param name="viewNamePath"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    public static async Task<string> RenderViewToStringAsync<TModel>(this Controller controller, string viewNamePath, TModel model)
    {
        if (string.IsNullOrEmpty(viewNamePath))
            viewNamePath = controller.ControllerContext.ActionDescriptor.ActionName;

        controller.ViewData.Model = model;

        using (StringWriter writer = new StringWriter())
        {
            try
            {
                IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;

                ViewEngineResult viewResult = null;

                if (viewNamePath.EndsWith(".cshtml"))
                    viewResult = viewEngine.GetView(viewNamePath, viewNamePath, false);
                else
                    viewResult = viewEngine.FindView(controller.ControllerContext, viewNamePath, false);

                if (!viewResult.Success)
                    return $"A view with the name '{viewNamePath}' could not be found";

                ViewContext viewContext = new ViewContext(
                    controller.ControllerContext,
                    viewResult.View,
                    controller.ViewData,
                    controller.TempData,
                    writer,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                return writer.GetStringBuilder().ToString();
            }
            catch (Exception exc)
            {
                return $"Failed - {exc.Message}";
            }
        }
    }
}

Quick Example

As you can see from my quick example below, the Home controller is using the RenderViewToStringAsync() when calling:

  • A view from another controller, where a relative path to the view is used.
  • A view from within the realms of the current controller and the name of the view alone can be used.
public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        NewsListItem newsItem = GetSingleNewsItem(); // Get a single news item.

        string viewFromAnotherController = await this.RenderViewToStringAsync("/Views/News/_NewsList.cshtml", newsItem);
        string viewFromCurrentController = await this.RenderViewToStringAsync("_NewsListHome", newsItem);

        return View();
    }
}

 

ASP.NET Core MVC Numbered Pagination

This is a relatively simple pagination that will only be shown if there are enough items of data to paginate through. The user will have the ability to paginate by either clicking on the "Previous" and "Next" links as well as clicking on the individual page numbers from within the pagination.

I created a PaginationHelper.CreatePagination() method that carries out all the paging calculations and outputs the pagination as an unordered list. The method requires the following parameters:

  • currentPage - the current page number being viewed.
  • totalNumberOfRecords - the total count of records from your dataset in order to determine how many pages should be displayed.
  • pageRequest - the current request from by passing in "HttpContext.Request" to get the page URL.
  • noOfPageLinks - the number of page numbers that should be shown. For example "1, 2, 3, 4".
  • pageSize - the number of items will be shown per page.
using Microsoft.AspNetCore.Http;
using System;
using System.Text;

namespace MyProject.Helpers
{
    public static class PaginationHelper
    {
        /// <summary>
        /// Renders pagination used in listing pages.
        /// </summary>
        /// <param name="currentPage"></param>
        /// <param name="totalNumberOfRecords"></param>
        /// <param name="pageRequest">Current page request used to get the URL path of the page.</param>
        /// <param name="noOfPagesLinks">Number of pagination numbers to show.</param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static string CreatePagination(int currentPage, int totalNumberOfRecords, HttpRequest pageRequest, int noOfPagesLinks = 5, int pageSize = 10)
        {
            StringBuilder paginationHtml = new StringBuilder();

            // Only render the pagination markup if the total number of records is more than our page size.
            if (totalNumberOfRecords > pageSize)
            {
                #region Pagination Calculations

                int amountOfPages = (int)(Math.Ceiling(totalNumberOfRecords / Convert.ToDecimal(pageSize)));

                int startPage = currentPage;

                if (startPage == 1 || startPage == 2 || amountOfPages < noOfPagesLinks)
                    startPage = 1;
                else
                    startPage -= 2;

                int maxPage = startPage + noOfPagesLinks;

                if (amountOfPages < maxPage)
                    maxPage = Convert.ToInt32(amountOfPages) + 1;

                if (maxPage - startPage != noOfPagesLinks && maxPage > noOfPagesLinks)
                    startPage = maxPage - noOfPagesLinks;

                int previousPage = currentPage - 1;
                if (previousPage < 1)
                    previousPage = 1;

                int nextPage = currentPage + 1;

                #endregion

                #region Get Current Path

                // Get current path.
                string path = pageRequest.Path.ToString();

                int pos = path.LastIndexOf("/") + 1;

                // Get last route value.
                string lastRouteValue = path.Substring(pos, path.Length - pos).ToLower();

                // Removes page number from end of path if path contains a page number.
                if (lastRouteValue.StartsWith("page"))
                    path = path.Substring(0, path.LastIndexOf('/'));

                #endregion

                paginationHtml.Append("<ul>");

                if (currentPage > 1)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{previousPage}\"><span>Previous page</span></a></li>");

                for (int i = startPage; i < maxPage; i++)
                {
                    // If the current page equals one of the pagination numbers, set active state.
                    if (i == currentPage)
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\" class=\"is-active\"><span>{i}</span></a></li>");
                    else
                        paginationHtml.Append($"<li><a href=\"{path}/Page{i}\"><span>{i}</span></a></li>");
                }

                if (startPage + noOfPagesLinks < amountOfPages && maxPage > noOfPagesLinks || currentPage < amountOfPages)
                    paginationHtml.Append($"<li><a href=\"{path}/Page{nextPage}\"><span>Next page</span></a></li>");

                paginationHtml.Append("</ul>");

                return paginationHtml.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
    }
}

The PaginationHelper.CreatePagination() method can then be used inside a controller where you would like to list your data as well as render the pagination. A simple example of this would be as follows:

/// <summary>
/// List all news articles.
/// </summary>
/// <param name="page"></param> 
/// <param name="pageSize"></param>
/// <returns></returns>
[Route("/Articles")]
[Route("/Articles/Page{page}")]
public ActionResult Index(int page = 1, int pageSize = 10)
{
    // Number of articles to skip.
    int skip = 0;
    if (page != 1)
        skip = (page - 1) * pageSize;

    // Get list of articles from my datasource.
    List<NewsArticle> articles = MyData.GetArticles().Skip(skip).Take(pageSize).ToList();

    //Render Pagination.
    ViewBag.PaginationHtml = PaginationHelper.CreatePagination(page, articles.Count, HttpContext.Request, pageSize: pageSize);

    return View(articles);
}

The pagination will be output to a ViewBag that can be called from within your view. I could have gone down a different route and developed Partial View along with the appropriate model. But for my use the method approach offers most flexibility, as I could have the option to either use this from within a controller or view.

New Year, New Site!

This site has been longing for an overhaul, both visually and especially behind the scenes. As you most likely have noticed, nothing has changed visually at this point in time - still using the home-cooked "Surinder theme". This should suffice in the meantime as it currently meets my basic requirements:

  • Bootstrapped to look good on various devices
  • Simple
  • Function over form - prioritises content first over "snazzy" design

However, behind the scenes is a different story altogether and this is where I believe matters most. Afterall, half of web users expect a site to load in 2 seconds or less and they tend to abandon a site that isn’t loaded within 3 seconds. Damning statistics!

The last time I overhauled the site was back in 2014 where I took a more substantial step form to current standards. What has changed since then? I have upgraded to Kentico 10, but this time using ASP.NET Web Forms over MVC.

Using ASP.NET Web Form approach over MVC was very difficult decision for me. Felt like I was taking a backwards step in making my site better. I'm the kind of developer who gets a kick out of nice clean code output. MVC fulfils this requirement. Unfortunately, new development approach for building MVC sites from Kentico 9 onwards will not work under a free license.

The need to use Kentico as a platform was too great, even after toying with the idea of moving to a different platform altogether. I love having the flexibility to customise my website to my hearts content. So I had to the option to either refit my site in Kentico 10 or Kentico Cloud. In the end, I chose Kentico 10. I will be writing in another post why I didn't opt for the latter. I'm still a major advocate of Kentico Cloud and started using it on other projects.

The developers at Kentico weren't lying when they said that Kentico 10 is "better, stronger, faster". It really is! I no longer get the spinning loader for obscene duration of time whilst opening popups in the administration interface or lengthy startup times when the application has to restart.

Upgrading from Kentico 8.0 to 10 alone was a great start. I have taken some additional steps to keep my site clean as possible:

  1. Disable view state on all pages, components and user controls.
  2. Caching static files, such as CSS, JS and images. You can see how I do this at web.config level from this post.
  3. Maximising Kentico's cache dependencies to cache all data.
  4. Took the extra step to export all site contents into a fresh installation of Kentico 10, resulting in a slightly smaller web project and database size.
  5. Restructured pages in the content tree to be more efficient when storing large number of pages under one section. 

I basically carried out the recommendations on optimising website performance and then some! My cache statatics have never been so high!

My Kentico 10 Cache Statistics

One slight improvement (been a long time coming) is better open graph support when sharing pages on Facebook and Twitter. Now my links look pretty within a tweet.

My Development Overview of Kentico 9 MVC

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

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

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

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

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

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

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

Kentico 9 MVC Missing Page Properties

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

Kentico 9 Core Content Page Inheritance

2) No Document Aliases

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

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

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

3) Separation of the CMS and Presentation Layer

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

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

Kentico 9 MVC Architecture

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

4) Licensing and Environment Setup

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

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

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

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

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

5) Page Templates

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

6) Retrieving Content In MVC Controllers

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

7) Friendly URL's Should Include A Node ID

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

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

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

Conclusion

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

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

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

Is ReactJS A Worthwhile Addition To An ASP.NET MVC Application?

ReactJSI've been meddling around with ReactJS over the last week or so, seeing if this is something viable to use for future client projects. I am always constantly on the lookout to whether there are better alternatives on how my fellow developers and I develop our sites.

Throughout the sample applications I've been building, I constantly asked myself one question: Why Would I Use ReactJS In My Day To Day Development? I am ASP.NET developer who build websites either using Web Forms or MVC Razor. So I am finding it difficult to comprehend whether using ReactJS is viable in these frameworks, especially MVC.

ReactJS is primarily a view framework where you have the ability to write component-based web applications directly into your JavaScript that then gets output to the DOM virtually - making for a very fast and responsive webpage. It's a different approach to developing websites that I quite like. But for the moment, I just don't see how it can benefit me when the full MVC framework does a pretty good job with all the bells and whistles.

For example, I segregate all my key HTML markup into partial views in order to increase re-use throughout my web application, which works really well when making AJAX calls where the markup needs to be displayed on the page asynchronously as well as server-side. I can just see by implementing ReactJS, I will be duplicating this process at JavaScript and CSHTML level if a markup change ever needed to be made. If partial views does the job effectively, I'm not too sure the need for ReactJS in my future ASP.NET MVC creations.

Don't get me wrong - I really like ReactJS. It makes writing JavaScript an even more enjoyable experience purely due to the JSX syntax. Long gone are the days where you have to concatenate strings to form HTML. More importantly, it's readable and truly scalable.

Unfortunately, it doesn't look like ReactJS is a viable option for me at this moment in time. I can see how it would be a very useful framework for building web applications where there is a requirement for the view to be created strictly client-side along with heavy use of AJAX calls from an API layer to serve data to your application. But in situations where you have two frameworks that provide the ability to create views, in this case ReactJS and ASP.NET MVC, it doesn't make sense.

I could be talking absolute nonsense and missing the whole point. If this is the case (most likely!), please leave a comment.