Skip To Content

Using MVC Web Parts Inside a Portal Engine Kentico Website

A team of people has spent countless hours designing, building, and launching your Kentico Portal Engine website.  The CEO loves it, the marketing team is thrilled with the new customer outreach tools, the content team feels empowered and the IT team is relieved to no longer be responsible for content updates.  Everyone is ecstatic over the new site.  Fast forward a year and now that the marketing team has had a chance to analyze the data which they have been collecting for the last 12 months, they have identified ways to make the site even better.  They really want to start to provide a more personalized user experience to increase customer conversions. 

Fantastic!  This would be a perfect opportunity to upgrade to Kentico 12 in order to take advantage of the improved content personalization features.  While you’re at it, moving to MVC would be a huge step forward as well.  Unfortunately, convincing the CEO and CFO of the rebuild that would be necessary to move from portal engine to MVC so soon after the initial launch is likely going to be a tough sell given the time, effort and expense that went into that launch.  The good news is that you do not have to switch to Kentico MVC in order to start taking advantage of .Net MVC features.  You can start building MVC “widgets” that can be reused once you transition fully to the Kentico MVC development model. Additionally, this approach will work whether your existing site is built using a website or a web app.

The basic approach is simple – build a web forms webpart which will invoke an MVC controller action.  Obviously, there is a little more to it than that, but not much.  Below are the various functional components involved in such an implementation.

Controller, Model, View and Utility Class to Invoke an MVC Controller Action

DummyController

You will need a controller inside your CMS app to handle rendering an MVC partial view.

using System.Web.Mvc;

[RoutePrefix("Dummy")]

public class DummyController : Controller

{

    [Route("PartialRender")]

    public ActionResult PartialRender()

    {

        return PartialView("/Views/PartialRender.cshtml");

    }

}

Note: If your project is set up as a website, this will need to live in the App_Code directory of the CMS website.

PartialRender View

You will need a partial view in the CMS app which invokes a controller action using the RenderAction HtmlHelper method.

@model RenderActionViewModel

@{Html.RenderAction(Model.ActionName, Model.ControllerName, Model.RouteValues);}

RenderActionViewModel

You will also need a view model available in your CMS project to collect all the information needed by the  partial view.

public class RenderActionViewModel

{

    public string ControllerName { get; set; }

    public string ActionName { get; set; }

    public object RouteValues { get; set; }

}

Note: If your project is set up as a website, this class needs to live in the App_Code directory of the CMS website.

MvcUtility Class

Lastly, you will need a way to invoke a controller action from outside the MVC routing engine.  The class below handles this.  This class needs to reside somewhere in your CMS project. 

using System;

using System.Text;

using System.Web;

using System.Web.Mvc;

using System.Web.Routing;

public class MvcUtility

{

    public MvcUtility() { }

    private static void RenderPartial(string partialViewName, object model)

    {

        HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current);

        RouteData routeData = new RouteData();

        routeData.Values.Add("controller", "Dummy");

        ControllerContext controllerContext = new ControllerContext(new

RequestContext(httpContextBase, routeData), new DummyController());

        IView view = FindPartialView(controllerContext, partialViewName);

        ViewContext viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary {

Model = model }, new TempDataDictionary(), httpContextBase.Response.Output);

        view.Render(viewContext, httpContextBase.Response.Output);

    }

    //Find the view, if not throw an exception

    private static IView FindPartialView(ControllerContext controllerContext, string partialViewName)

    {

        ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext,

partialViewName);

        if (result.View != null)

        {

            return result.View;

        }

        StringBuilder locationsText = new StringBuilder();

        foreach (string location in result.SearchedLocations)

        {

            locationsText.AppendLine();

            locationsText.Append(location);

        }

        throw new InvalidOperationException(String.Format("Partial view {0} not found. Locations

Searched: {1}", partialViewName, locationsText));

    }

    //Here the method that will be called from aspx page or ascx control

    public static void RenderAction(string controllerName, string actionName, object routeValues)

    {

        RenderPartial("/Views/PartialRender.cshtml", new RenderActionViewModel() { ControllerName =

controllerName, ActionName = actionName, RouteValues = routeValues });

    }

}

Note: If your project is set up as a website, this class needs to live in the App_Code directory of the CMS website.

Global.asax

The following will need to be added to your global.asax file:

public static void RegisterRoutes(RouteCollection routes)

{

    routes.MapMvcAttributeRoutes();

}

void Application_Start(object sender, EventArgs e)

{

    RegisterGlobalFilters(GlobalFilters.Filters);

    RegisterRoutes(RouteTable.Routes);

}

Note: Attribute routing is important here since the MVC routing engine is not being used within portal engine and without using attribute routing, there is no way for the MvcUtility to resolve and transfer control to the widget controllers.

MVC Model, View and Controller

Once you have all the infrastructure above in place, you can create MVC views, models, and controllers which contain page and/or widget functionality.

Model (ContentBlockDetailsViewModel.cs)

using System;

using System.Collections.Generic;

public class ContentBlockDetailsViewModel

{

    public string ContentBlockHtml { get; set; }

}

Note: If your project is set up as a website, this class need to live in the App_Code directory of the CMS website.

View (_contentBlockDetails.cshtml)


@model ContentBlockDetailsViewModel

@{

    Layout = null;

}

<div>@Html.Raw(@Model.ContentBlockHtml)</div>

Controller (ContentBlockController.cs)

using CMS.SiteProvider;

using System.Linq;

using System.Web.Mvc;

[RoutePrefix("ContentBlock")]

public class ContentBlockController : BaseController

{

    [Route("ContentBlockDetails/{model}")]

    public ActionResult ContentBlockDetails(ContentBlockDetailsViewModel model)

    {

return PartialView("/Views/Shared/Widgets/ContentBlock/_contentBlockDetails.cshtml",

model);

    }

}

Note: If your project is set up as a website, this class needs to live in the App_Code directory of the CMS website.  

Note: Attribute routing is important here since the MVC routing engine is not being used within portal engine and without using attribute routing, there is no way for the MvcUtility to resolve and transfer control to the ContentBlockController.

Custom Web Forms Web Part

Next, you will need a web forms web part to wrap the MVC components.  This web part will retrieve any web part/widget properties and pass those through to the MVC controller.

ContentBlock.ascx


<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ContentBlock.ascx.cs" Inherits="CMSWebParts_MyProject_ContentBlock" %>

<asp:PlaceHolder runat="server">

    <% MvcUtility.RenderAction("ContentBlock", "ContentBlockDetails", new { model = new

ContentBlockDetailsViewModel { ContentBlockHtml = ContentBlockHtml }}); %>

</asp:PlaceHolder>

ContentBlock.ascx.cs


using System;

using CMS.Helpers;

using CMS.PortalEngine.Web.UI;

public partial class CMSWebParts_MyProject_ContentBlock : CMSAbstractWebPart

{

    public string ContentBlockHtml { get; set; }

    protected void Page_Load(object sender, EventArgs e)

    {

//load widget/web part properties

        ContentBlockHtml = ValidationHelper.GetString(GetValue("ContentBlockHtml "), string.Empty);

    }

}

Kentico Portal Engine Web Part/Widget

All that is left to do is to register the web part (and widget) in the CMS and start using it on your pages! 


A Final Note 

Because the portal engine and MVC development models are fundamentally different, there will be some work in migrating your portal engine MVC code over to MVC.  Again, this far outweighs the alternative of having to rewrite everything.  All of the code you wrote in widget models, views and controllers can be reused.  You will, however, need to write MVC widget controllers and any associated widget property classes. 


While it may seem like there is a lot involved in integrating MVC web parts/widgets into a portal engine website, once the initial infrastructure is in place the additional effort for each new web part is minimal.  Furthermore, the benefits far outweigh both the initial investment to build the infrastructure and the small overhead on each new web part.  Using this approach, you can continue to add new features to portal engine sites without having to throw them away when you make the move to the MVC development model.  Additionally, you can gradually migrate existing features to MVC without having to wait until the entire site is in MVC development mode. This allows you to continue to maintain and enhance your current site while simultaneously building for the future.