Using MVC backend pages in Umbraco 4.11.1

Umbraco backend

In a previous post we saw the use of your own MVC controllers in the Umbraco front end. The Umbraco community has made effort to document these possibilities on our.umbraco.org. The use of MVC in the back end of the Umbraco interface is not very well documented and it took me some analysis and debugging of the source code to find out how this could be implemented.

Default you have 7 sections in the backend:

  • Content
  • Media
  • Settings
  • Developer
  • Users
  • Members
  • Translation (hidden by default for the admin user)

These sections will do in a plain website where you don’t want to use custom code.

One of my personal projects is to create a new website for the sport club where I’m already 15 years affiliated with. The current website is already 7 or 8 years old and was written in PHP. Needless to say that the look and feel is outdated, the PHP code I’ve written 8 years ago wasn’t the most beautiful piece of code I’ve created.
I wanted to create a new website in ASP.NET MVC but I don’t want to put the effort in creating controllers and views for the administration part where a good CMS can fill up this gap.

Because I’m already familiar with Umbraco it looked a good choice to combine the strength of Umbraco with custom development. But therefor I had to get custom MVC controllers working in the backend.

New backend project

This blog post goes further on the project created in the previous post. You can download the source at Github.

In the existing solution we’ll add a new project by right clicking the solution and choose Add – New project.

image

Choose for a ASP.NET MVC 3 Web Application and give it a name (in this demo: UmbracoMVCDemo.BackEnd)

image

In the next window choose for Internet Application and the Razor view engine. We’ll need the core Umbraco libraries so open up the Package Manager Console and type ‘”Install-Package UmbracoCms.Core” and hit enter. Wait for the “Successfully added ‘UmbracoCms.Core 4.11.1’ to UmbracoMVCDemo.BackEnd.” message and we’re ready to go.

Adding a new section

First we’ll have to create a new section in the Umbraco backend. Open op your windows explorer and go to the root of the Umbraco installation. Open up the config folder and open up the applications.config file.

<?xml version="1.0" encoding="utf-8"?>
<applications>
  <add alias="content" name="Content" icon=".traycontent" sortOrder="0" />
  <add alias="media" name="Media" icon=".traymedia" sortOrder="1" />
  <add alias="settings" name="Settings" icon=".traysettings" sortOrder="2" />
  <add alias="developer" name="Developer" icon=".traydeveloper" sortOrder="3" />
  <add alias="users" name="Users" icon=".trayusers" sortOrder="4" />
  <add alias="member" name="Members" icon=".traymember" sortOrder="5" />
  <add alias="translation" name="Translation" icon=".traytranslation" sortOrder="6" />
</applications>

You’ll see the existing 7 sections configured in the config file. We’ll add a new one:

  <add alias="demo" name="Demo" icon="demo.gif" sortOrder="7" />

We give the new application or section the alias ‘demo’ and the name ‘Demo’. For the icon we’ll add a new picture called ‘demo.gif‘ and set the sort order to 7. (The default images are showed using sprites where the icon name is actually the css class to define the background image. You can choose to alter the existing sprite and add a css class or select new picture). Create a new picture with your favorite image editor, name it ‘demo.gif’ and place it in the folder: [Umbraco root]/umbraco/images/tray/ folder.

We’ll have to force the application pool to restart before Umbraco loads the new section. Open up the web.config file in the umbraco installation root and add a line break, save and refresh the page.

Still the new section is not available in the backend because none of the users have access to this new section. Go to the users section, open up your user and add a check in the sections area next to the demo section. Refresh your page and you’ll see the new section appear.

image

When you click the Demo section at the bottom of the page we’ll see a new section opening up with an empty tree and the section name in square brackets.

Adding a tree

Next step will be to create the tree for our Demo section. This tree you can populate with data you choose. For this demo we’ll use some dummy data.

In our backed project add a folder ‘Trees’ and add a new class named DemoTree. This class will have to inhered from the umbraco.cms.presentation.Trees.BaseTree class. This class has 3 abstract methods that we’ll need to implement.

  • RenderJS: the javascript that will run when a tree node is clicked
  • Render: the creation of tree nodes
  • CreateRootNode: to define the root node.

We’ll also have to create a constructor that will receive a string that will forward to the base class.

Last but not least we’ll have to decorate the class with the umbraco.businesslogic.TreeArrtibute where we define the application alias and title.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using umbraco.cms.presentation.Trees;

namespace UmbracoMVCDemo.BackEnd.Trees
{
	[Tree("demo","demo","Demo")]
    public class DemoTree : umbraco.cms.presentation.Trees.BaseTree
    {
        public DemoTree(string application) : base(application)
        {
        }

        public override void RenderJS(ref StringBuilder Javascript)
        {
            throw new NotImplementedException();
        }

        public override void Render(ref XmlTree tree)
        {
            throw new NotImplementedException();
        }

        protected override void CreateRootNode(ref XmlTreeNode rootNode)
        {
            throw new NotImplementedException();
        }
    }
}

Let’s start with defining the root node what is as simple as setting the NodeType and NodeId parameter.

protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
	rootNode.NodeType = "init" + TreeAlias;
	rootNode.NodeID = "init";
}

Next we’ll implement the Render method and attach 2 nodes to the tree.

public override void Render(ref XmlTree tree)
{
	XmlTreeNode xNode = XmlTreeNode.Create(this);
	xNode.NodeID = "1";
	xNode.Text = "Demo Node 1";
	xNode.Action = "";
	xNode.Icon = "folder.gif";
	xNode.OpenIcon = "folder_o.gif";
	OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty);
	if (xNode != null)
	{
		tree.Add(xNode);
		OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty);
	}

	XmlTreeNode xNode2 = XmlTreeNode.Create(this);
	xNode2.NodeID = "2";
	xNode2.Text = "Demo node 2";
	xNode2.Action = "";
	xNode2.Icon = "folder.gif";
	xNode2.OpenIcon = "folder_o.gif";
	OnBeforeNodeRender(ref tree, ref xNode2, EventArgs.Empty);
	if (xNode2 != null)
	{
		tree.Add(xNode2);
		OnAfterNodeRender(ref tree, ref xNode2, EventArgs.Empty);
	}
}

In the RenderJS method remove the ‘throw new NotImplementedException” line and build the project. We’ll have to copy the UmbracoMVCDemo.BackEnd.dll from the bin folder to the Umbraco installation bin folder.

Refresh the page and you’ll see two new node appear. Notice that the Demo root node isn’t surrounded by square brackets any more.

image

If you open up the trees.config file in the umbraco root – config folder you’ll see Umbraco automatically added the tree.

For now the nodes don’t do much if you click on it because we left the Action attribute empty. Before we can add an action we need the editor for the right hand side.

Adding an editor – controller and views

Like I said before we want to create the editor in a MVC pattern instead of the default Webforms like was possible before. Therefor will create a new controller by right clicking the controllers folder in the backend project.

image

Name the controller DemoAdminController and choose for the empty controller template. Because we’ll already have a DemoController in the frontend application I named this one DemoAdmin to avoid confusion.

The Controller will have to inhered from the Umbraco.Web.Mvc.SurfaceController before we can use it in the backend.

IMPORTANT: Although the documentation of version 5 stated the controller didn’t need to be suffixed by ‘Surface’ I noticed there are constraints set on the PluginControllerResolver in the Umbraco source code. We can remove this constraint in the source code but the easiest way is to rename our controller to DemoAdminSurfaceController. This will change our URL’s but they won’t be visible in the Umbraco backend anyway.

We have to decorate the class with the Umbraco.Web.Mvc.PluginController attribute. (yes, the plugincontroller attribute although we’re inheriting a SurfaceController). In this attribute we’ll have to enter the area name. Every plugin will be placed in his own MVC area to avoid duplicate views and controllers. We’ll set the area name to ‘demo’.

Last but not least we’ll have to create two constructors. One without parameters, one that will take a Umbraco.Web.UmbracoContext instance and forward this to the base class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Umbraco.Web;
using Umbraco.Web.Mvc;

namespace UmbracoMVCDemo.BackEnd.Controllers
{
    [PluginController("Demo")]
    public class DemoAdminSurfaceController : SurfaceController
    {

        public DemoAdminSurfaceController()
        {
        }

        public DemoAdminSurfaceController(UmbracoContext umbracoContext)
            : base(umbracoContext)
        {
        }

        public ActionResult Index()
        {
            return View();
        }
    }
}

Last but not least we’ll have to create a view to back up the Index action on our controller that will for now will be one line:

“<h1>Backend Demo Controller – view</h1>”

Build the backend project and copy the dll to the Umbraco installation bin folder. Default will the view be searched in the following folder:

[Umbraco ROOT]/App_Plugins/Demo/Views/DemoAdminSurface/. That is App_Plugins/[AreaName]/Views/[ControllerName]/. Create the folders and add the index view.

I we go to the Umbraco backend we”’ll notice the 2 demo nodes still don’t do anything if we click them. We’ll first have to assign an action to these nodes.

Assign an action to the nodes

To assign an action to the nodes we’ll have to do two things. We’ll have to implement the RenderJS method in out Tree class and fill out the Action parameter from our nodes in the Render method.

In the RenderJS method will render the necessary javascript that will open our editor in the right hand pane by using the ‘UmbClientMgr’ javascript object.

public override void RenderJS(ref StringBuilder Javascript)
{
	Javascript.Append(
	@"function openDemoController() {
		UmbClientMgr.contentFrame('/Demo/DemoAdminSurface');
	}");
}

In the Render method will add a call to the javascript action we just created (for now only on the first node).

public override void Render(ref XmlTree tree)
{
	XmlTreeNode xNode = XmlTreeNode.Create(this);
	xNode.NodeID = "1";
	xNode.Text = "Demo Node 1";
	xNode.Action = "javascript:openDemoController();";
	xNode.Icon = "folder.gif";
	xNode.OpenIcon = "folder_o.gif";
	OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty);
	if (xNode != null)
	{
		tree.Add(xNode);
		OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty);
	}

	XmlTreeNode xNode2 = XmlTreeNode.Create(this);
	xNode2.NodeID = "2";
	xNode2.Text = "Demo node 2";
	xNode2.Action = "";
	xNode2.Icon = "folder.gif";
	xNode2.OpenIcon = "folder_o.gif";
	OnBeforeNodeRender(ref tree, ref xNode2, EventArgs.Empty);
	if (xNode2 != null)
	{
		tree.Add(xNode2);
		OnAfterNodeRender(ref tree, ref xNode2, EventArgs.Empty);
	}
}

Build the project and copy the dll to the Umbraco installation bin folder and refresh the backend in the browser. If we now click the first test node we’ll see the test view appear in the right hand side frame.

Automating the copy process

Just like in the previous blog post you can use the build events to automate the copy process of DLL’s and views. Place the 2 xcopy commands in the post build event.

xcopy $(TargetPath) $(SolutionDir)UmbracoMVCDemo\bin\ /C /Y
xcopy $(ProjectDir)Views $(SolutionDir)UmbracoMVCDemo\App_Plugins\Demo\Views\ /E /Y

Source code

You can find the solution above on Github.

To do’s

There are still some things I need to resolve:

  • Block access to the backend controller for non authenticated users
  • Adapt the actions that you get in the Umbraco back end by right clicking
  • ….

20 thoughts on “Using MVC backend pages in Umbraco 4.11.1

  1. Damian

    I kept on having issues with the view complaining it didn’t understand what viewdata, model, viewbag etc were. I ended up copying the web.config from the Views folder to the App_PlugIns folder. This gives all the correct namespaces to the Views for the above objects.

    Reply
    1. Bart De Meyer Post author

      Hey Damian,
      That’s indeed the resolution for that problem. I didn’t run into that problem because the first run I copied the whole views directory to the Umbraco App_Plugins folder before I used the post build script. I will add it to the post.

      Reply
    1. Bart De Meyer Post author

      I haven’t had the time yet to go through the source of the 6.x but as far a I could see in other blog posts the way MVC is implemented is roughly the same.

      You still have the surface controller where you can inherit from. I think it still should work this way in V6.x

      Regards,

      Bart

      Reply
  2. Tim

    Hey Bart,

    Did you find a solution for ensuring authenticated Umbraco users can view pages only?

    Thanks for the post!

    Reply
    1. Bart De Meyer Post author

      Hey Tim,

      Due to professional projects I hadn’t had the time yet to search for a solution.

      I think you can use the standard MVC authentication like explained in this StackOverflow post (http://stackoverflow.com/questions/1816625/asp-net-mvc-forms-authentication-and-unauthenticated-controller-actions).

      The trick will be to call the Users API from the Surface controller.

      If I have some time on an evening next week, I’ll take a look at it. (No promise, my schedule is filled up quite heavy the next few weeks)

      Reply
  3. Pingback: Securing Backend Pages in MVC for Umbraco 4.11.1 | Bart De Meyer - Blog

  4. Damian

    I have just done this again in another project and used MVC Area successfully to implement the PlugIn.

    This time however im just seeing an umbraco progress bar on the right hand side and it doesnt look like i have a surface/canvas area that my view is being placed on. Im thinking the progress bar is only there because you dont normally see it!

    Any ideas why im not getting a canvas area?

    My html controls are getting placed on the form but the background colour of the page suggests its not a page area.

    A bit odd!

    Damian

    Reply
  5. Damian

    Also, something else i’ve noticed is if you tab between sections (e.g. members ) and then back to this section, the names of the items in the tree get duplicated.

    Reply
    1. Martin Lingstuyl

      That name duplication has been an issue since I created custom sections for umbraco 4.5. The problem, as I remember it, was not-unique nodeId’s due to the recursive structure in which every node ( on what level doesn’t matter ) needs a unique nodeId. Very annoying if you’re loading from database tables with overlapping id’s.

      The solution was adding some text to the nodeId. For example: uniqueid1, uniqueid2, superuniqueid1, superuniqueid2. This is possible if you’re using multiple levels of nodes.

      Only problem with this approach is getting the create and delete menu actions to work. The solution to that is within the task classes using the alias attribute instead of the parentID. In some strange benevolent way the alias will be the nodeId you filled in on the base tree class if it’s a string instead of a regular integer.

      Reply
  6. Shannon Deminick

    Nice work figuring that out 🙂
    In 6.1 we have base classes for back office stuff like: UmbracoAuthorizedController, but you’ll have to define your own routes for that, only SurfaceControllers are auto-routed.
    You’ll just have to be aware that with your current setup you are going to need to authorize the requests to your surface controller. We also have an filter attribute you could use: [UmbracoAuthorize]

    Reply
    1. Bart De Meyer Post author

      Emilio,

      I don’t see any reason why it wont work with MVC4.

      Have fun

      Reply
    1. Bart De Meyer Post author

      Hey Martin,
      Looks a good post on your blog. Thx for creating the post!

      (and thx for the backlink)

      Reply
  7. Jeret Sauer

    I have to say that the documentation out there for Umbraco is horrible, which is a shame since I really like this CMS. With that said, this is the best, most comprehensive tutorial on adding custom sections that I have found, so a big thanks. I have implemented this solution in Umbraco 4.11.x and I’m trying it now with v6.2.0 as well as have used the authentication attributes. Again I appreciate this.

    Reply

Leave a Reply to Martin Lingstuyl Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.