Using ASP.NET MVC with Kentico CMS

   —   

We got a lot of questions on how you could use MVC within a Kentico CMS web site. Now it is finally the time for the answer...

Hi there,

Some time ago, I promised you to explain how you can use the MVC development model within Kentico CMS. I finally got enough time to prepare it so here it is.

Before you start, you need to realize that MVC works only in a Web application project, so if you want to start using it, you first need to convert the default web site project to a web application. Here is how to do that and what are the pros and cons of that action:

How to convert Kentico CMS web site to a Web Application

Once you have a web application project, you can start incorporating MVC into it.

How MVC works

If you are interested in this article, you probably already know how MVC works. Just for case you don't and wonder what can be the advantage of it, here is a summarization:

  • Behavior of the page, called Controller in MVC, is completely separated from the presentation.
  • The controller provides the data to a View, which is just a page displaying data, without any business logic in it.
  • The data from the Controller is presented to the View through a Model, which is nothing more than the data representation.

The abbreviation of the Model, View, Controller is MVC, which is from where the name comes. A request is processed like this:

  • Once a request reaches the application, it is processed by the Routing module, and if the URL matches a registered wildcard (route), it is given to a Controller for processing.
  • The Controller determines what to display or do through an Action name, and based on that it does the work and displays the page using View.
  • View just takes data from Controller, and renders it.

The reason why MVC is so heavily discussed is that it provides clean separation between application layers, which allows replacing of particular components by some other for integration or testing purposes.

For instance, you can run unit tests on it simply by providing an alternate set of controllers that feed particular View with testing data. That is exactly how the default Unit test that you get with a new MVC project created from VS templates works.
 
What I definitely want to say here is that MVC is not a solution for everything. It is very good for simple scenarios, where it is clear what you display and what your actions are. But it is also very bad for scenarios that are very complex or even dynamically driven. Not that it would not be possible, but the complexity of it becomes just too much for maintaining such solution. That is why we don't use it as our primary development model. And there is other factor, too, which is historical. MVC has simply not been on the market long enough and it still has flaws that are making the implementation too complicated in those advanced cases.

What you can expect from us is the support for you so your pages can be built on MVC without the need for any hacks. There is no exact plan how this will be done, we just want to make sure it can be done nicely.

MVC project specifics

Just as a standard Web forms project (either web application or web site), an MVC project has it's specifics which you can recognize:

  • It contains the Controllers and Views folders
  • The ASP.NET Routing module is registered within web.config, and routes (URLs) are registered upon the application start event
  • Pages heavily use the System.Web.Mvc namespace
  • Pages in Views inherit from System.Web.Mvc.ViewPage

What we need to do is incorporate this set of "rules" to our project, so it becomes "MVC ready".

I found a very good example of how to do it here:

http://www.packtpub.com/article/mixing-asp.net-webforms-and-asp.net-mvc 

And I will partially use it for this post. You don't need to read it, I will cover most of it, too.

Preparing your application

First you need to make sure your application can use MVC. So we need MVC classes and the module to be registered.

Go ahead and add references to the following libraries:

  • System.Web.Routing
  • System.Web.Abstractions
  • System.Web.Mvc 



Once you do that, you need to modify the web.config of your project. Register the following assemblies:

<system.web>
    ...
   <compilation ...>
    <assemblies>
      ...
      <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral,PublicKeyToken=31BF3856AD364E35"/>
      <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral,PublicKeyToken=31BF3856AD364E35"/>
    </assemblies>
    ...
  </compilation>
</system.web>


Note that versions may vary based on the .NET version. Then, you register the namespaces for them:

<system.web>
  ...
  <pages ...>
    ...
    <namespaces>
      <add namespace="System.Web.Mvc"/>
      <add namespace="System.Web.Mvc.Ajax"/>
      <add namespace="System.Web.Mvc.Html"/>
      <add namespace="System.Web.Routing"/>
    </namespaces>
    ...
  </pages>
  ...
</system.web>


And the third step is to register the Routing module. That varies based on the IIS you are using. You either use this for IIS 6.5 and earlier:

<system.web>
  ...
  <httpModules>
    ...
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,System.Web.Routing, Version=3.5.0.0,Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    ...
  </httpModules>
  ...
</system.web>


Or this for IIS 7 and higher (integrated mode):

<system.webServer>
  ...
  <modules>
    ...
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,System.Web.Routing, Version=3.5.0.0,Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  </modules>
  ...
</system.webServer>


You can use both if you are not sure under IIS7, it will pick up the right one.

Once your web application is ready, we can start preparing Controllers and Views. As you will see, it is almost the same as with standard MVC.

Registering the routes (URLs)

The routes should be registered as soon as possible to make sure URLs can be picked up by the Routing module even on the very first request to the application. It is typically done right after the application starts. The best location for it in Kentico CMS is ~/Old_App_Code/Global/CMS/CMSApplication.cs, the AfterApplicationStart method. Modify it like this:

/// <summary>
/// Fires after the application start event
/// </summary>
public static void AfterApplicationStart(object sender, EventArgs e)
{
  RegisterRoutes(RouteTable.Routes);
}


public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");

  routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "NewsMVC", action = "Detail", id = "" } // Parameter defaults
  );
}


You can see the URL is defined by something called route. It is very similar to our wildcard URLs (just a lucky coincidence or lack of originality on both sides:-) ). It basically transforms parts of the URL to some variables for MVC processing engine. As I said before, Controller is a class that provides data, Action is a method that does some action or provides particular data within the controller. And there can be additional parameters that identify the data. We will want to display News based on the alias of particular news, with URL in form ~/NewsMVC/Detail/Your-First-News (the last part is an alias).

NewsMVC is a controller, Detail is an action and Your-First-News is an id of the news. The system is basically told:

"Take a controller called NewsMVC, execute the Detail action on it, with a parameter Your-First-News."

You need to do one more thing to make sure that Kentico CMS URL rewriter won't process this kind of URL, otherwise, it could identify it as a non-existing page and fire 404 or simply just consume the CPU without any gain. 

You need to exclude this URL (URLs). You can do that in Site manager -> Settings -> URLs and SEO -> Excluded URLs. Just enter /NewsMVC in there which means "do not process anything that starts with /NewsMVC". It may also work without this setting, but it has too much overhead that is not necessary.



Let's prepare the Controller...

Preparing the controller

As I said before, controllers are located in the Controllers folder in the root of the web project. Create a folder named like that, and create a new class named NewsMVCController as a ~/Controllers/NewsMVCController.cs file that inherits from the System.Web.Mvc.Controller class:

using System.Web.Mvc;

using CMS.CMSHelper;

namespace KenticoCMS55WAPMigrated.Controllers
{
  /// <summary>
  /// Controller for the news
  /// </summary>
  public class NewsMVCController : Controller
  {
    /// <summary>
    /// Process the detail action
    /// </summary>
    public ActionResult Detail()
    {
      // Prepare the data for view
      CMS.TreeEngine.TreeNode document = TreeHelper.GetDocument(CMSContext.CurrentSiteName, "/News/" + RouteData.Values["id"], CMSContext.PreferredCultureCode, true, "CMS.News", true);
      if (document != null)
      {
        ViewData["NewsTitle"] = document.GetValue("NewsTitle");
        ViewData["NewsText"] = document.GetValue("NewsText");

      }

      return View();
    } 
  }
}


Note that the class name is always <controller name>Controller, that is how the system finds it. As you can see, there is a method named Detail that corresponds to our action.

You may need to change the namespace from KenticoCMS55WAPMigrated to the name of your project. I am not sure how smart the MVC engine is but hopefully it is smart enough.

This controller basically serves two values from the news found by the ID parameter that was passed from the routing module. The ViewData collection is an abstraction of the model to pass data to a particular View. We have the data prepared, so let's make the last step, which is creating a View.

Preparing the View

View in MVC is a page that inherits from the System.Web.Mvc.ViewPage class, mostly to provide simple access to data and MVC context without the need to fully qualify it.

Create a folder and a ~/Views/NewsMVC/Detail.aspx web page with the following ASPX code:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Detail.aspx.cs" Inherits="KenticoCMS55WAPMigrated.Views.NewsMVC.Detail" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>News - <%= ViewData["NewsTitle"] %></title>
</head>
<body>
  <div>
    <h1>News - <%= ViewData["NewsTitle"]%></h1>

    <p><%= ViewData["NewsText"]%></p>
  </div>
</body>
</html>


And the following code behind:

using System;
using System.Web.Mvc;

namespace KenticoCMS55WAPMigrated.Views.NewsMVC
{
  public partial class Detail : ViewPage
  {
    protected void Page_Load(object sender, EventArgs e)
    {
    }
  }
}


The page is very simple to just demonstrate how to display the data from the Model, it is the same as in standard MVC.

Viewing the results

Now if you try to show the ~/NewsMVC/Detail/Your-First-News and ~/NewsMVC/Detail/Your-Second-News URLs, you get the page view for them processed by the MVC.





And that is all. You just saw how you can use MVC within a Kentico CMS project. 

There is always an alternative

I should probably finish at this point but I wanted to give you some guidelines how to do the same with the current engine without MVC. Just briefly in steps:

  • Prepare a portal page called NewsMVC of any type with the News repeater
  • Set the path property of that repeater to "/News/{%id%}". Note that we use a querystring macro to access the wildcard value, which is always transformed into a virtual querystring parameter internally.
  • Set the URL path of that page to "/NewsMVC/Detail/{id}".

Kentico CMS URL rewriter will pull up the wildcard URL and feed your macro value from it. You can do the same in ASPX mode, just take the "id" parameter from querystring and feed it to the Path property of a repeater.

The final conclusion is: Do not use MVC unless you really need to for some valid reason, because you can do the same without it, too

You may also wait for 6.0 in which it will be simpler, not only because it will support Web application project out of the box (you won't need to handle the process of conversion), but we would also like to prepare some native support for it. Given it's nature, there is very small chance that it could ever support things that the Portal engine supports without becoming too complicated, so Portal engine will stay around as the main concept for a long time.

See you next time...

Share this article on   LinkedIn

Martin Hejtmanek

Hi, I am the CTO of Kentico and I will be constantly providing you the information about current development process and other interesting technical things you might want to know about Kentico.

Comments

DanO commented on

I'm not currently using Kentico, but was asking more from the perspective of someone in search of a CMS that is either built on or fully supports ASP.NET MVC development. I'll reassess when v6 comes out.

Martin Hejtmanek commented on

Hi Dan,

There is no real model in Kentico CMS 5.5 R2 that you could use since MVC is not officially supported yet. It will be in 6.0.

How would you approach to providing the possibility to register custom web parts / widgets, and allow people to use them in design mode with MVC, keeping the option to support Web forms?

DanO commented on

I haven't had a chance to try this yet myself, but I see your example doesn't do model binding in the views. Is this not supported?

Also - any update on the possibility of supporting Razor? It's now the default view engine in MVC projects, and is really nice to work with.

I would agree with Kieth that ASP.NET is certainly well suited to large complex applications. In my experience it performs much better than WebForms. No viewstate, and complete control over markup output...

Martin Hejtmanek commented on

Hi Jeff,

In 6.0 there will be the data context for Kentico objects and also support for text (html) transfomations. So I think helpers for rendering the page content in MVC are likely to be available. It will probably take some time to provide the complete components for the whole CMS functionality, but providing an interface for rendering the content and nicer business layer seems like good enough for now.

In fact, we are also thinking about introducing purely macro based HTML templates that would be very similar to Razor engine, but without any compilation and thus completely safe for multisite environments where every site is owned by a different client. (to avoid possibility of permission elevation by writing code). So we may end up with 4 development models, each suitable for specific situation.

Jeff Huntsman commented on

I don't think Kentico needs to change architecture to support MVC. We have several MVC applications that we integrate into our website using the Kentico API just fine. What would be beneficial to us would be a way to output webparts, masterpages, and page templates from MVC. We do it manually now by emitting equivalent html and adding the data from the API, but maybe some future Kentico MVC Helper classes would be in order.

Martin Hejtmanek commented on

Sure, we definitely plan to provide some decent LINQ provider, too

Keith patton commented on

Hi,
I don't care about the admin ui to be honest, web forms is fine. It might proofs hard hoever to bring things like dashboards support to front end with mvc model, reusing elements of cms desk.

My main desire is to provide a better way to ensure robustness of the custom code we write. Currently there is limited support for even compiling the website, and also there appears no easy way to unit test the work being done as a result.

I developed the MKB library so that we would have a way at least not to have a lot of code in our custom apps using datasets to provide a more maintainable code base. This gives our clients a higher quality solution with fewer bugs.

However we would still love to have a read and write Linq provider for kentico, as this is such a common and developer friendly way of consuming data oriented architectures.

Martin Hejtmanek commented on

Hi Keith,

Yes, it is large and complex site, but nobody expects the visitors to customize it from the UI, provide custom handlers, incorporate third party controls (those, which selection you cannot limit) to it, or manage its development model and code from the admin UI. So yes, on a web developer scale this site is quite complex, but on a CMS vendor scale it is nothing compared to a CMS. Of course you web sites can be done like that, that is why I am posting this article and why we will support it in future.

Loosely typed DataSets aren't problem at all. Anything strongly typed in internally based on either that or data readers anyway, you just dont' see it from the outside. You just take the DataSet, and provide enumeration with strongly typed objects over it, and you get the model the way you need it. You can see that on Marker Kentico Businness Library, that's exactly what they did. And you will see that same thing with our new business layer in 6.0. Kentico CMS currently just lacks nice strongly typed API with collections of objects, but you can make your own or use Marker's if you really need it. And MVC doesn't care how you get your data, it cares only how you put them into it.

You are right that we would have to rewrite a lot of code to completely transform to MVC, that is partially for historical reasons, because in versions 4.0 and lower we didn't have unified object architecture on which we could build the model (business layer).

But then we would just spend months in transforming the CMS that would look the same, have the same functionality but would be built on a different engine. What would that bring to the clients? Nothing at all ... So we rather devote the time to new features, and better code still built on WebForms, because in the end the only thing you need to solve as a client is whether you can build your own web site with MVC or WebForms. Once you have the choice, it doesn't matter if your admin UI is MVC or not ...

That is exactly where we are going.

Let me know your thoughts and tell me what would be the reasons why the admin UI should be strictly MVC based.

P.S.: Once we have the new bussiness layer, I am thinking about preparing alternative admin UI prototype based on MVC for mobile devices. It is proves strong enough, we may do similar thing for the standard UI (actually just provide different views for it), unless it is too time consuming to do that.

Keith patton commented on

Thanks for this, sure seems like a bit of work to get this happening. Umbraco is moving to a complete mvc model in there next version and I think it should actually scale better than the hard to test asps models we have at the moment. Stackoverflow.com is a pretty large and complex site running mvc, what makes you think mvc is not suited for more complex scenarios? Isn't the real reason kentico isn't mvc simply because your architecture is built upon a years old loosely typed dataset approach with providers that you simply couldn't move away from without a huge rewrite? Don't get me wrong, I've used kentico for years and do recommend it but I will be looking with interest as mvc cms's mature on .net as I think a lof developers will be looking there first for a number of reasons. It's a far more established architecture pattern than datasets.