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