Nibble

Extending the Umbraco Backend Using MVC

1 thing I wanted to do while creating Optimus for Umbraco was creating the page that is responsible for editing the bundle use the MVC framework instead of Webforms

The backoffice of Umbraco v4/v6 is still Webforms but it’s perfectly possible to plug in pages that use MVC.

The sourcecode for Optimus is available and you can use that to figure out how it’s done but I thought It would be even easier if I created a starter project for that Smile

Umbraco MVC Backoffice Pages

So I’ve just published a new project on github that does exactly that
https://github.com/TimGeyssens/UmbracoMVCBackofficePages

You might have come accros this post by Bart de Meyer http://blog.bartdemeyer.be/2013/01/using-mvc-backend-pages-in-umbraco-4-11-1/ that also talks about this subject but there is a difference in the approach I’m using since I’m not making use of a SurfaceController (since those are for frontend use) and I’m also trying to keep things seperated by working in the /App_plugins folder (~\App_Plugins\UmbracoMVCBackofficePages\ folder in this case)

Solution setup

image

The vs solution consist of 2 projects, 1 test site that just contains Umbraco and the the project where we’ll add our code and push to the test site using post build events.

The post build events (moving the assembly and the views/icons used)

xcopy "$(ProjectDir)bin\UmbracoMVCBackofficePages.*" "$(ProjectDir)..\TestSite\bin\" /Y
 
xcopy "$(ProjectDir)Icons\*.*" "$(ProjectDir)..\TestSite\App_Plugins\UmbracoMVCBackofficePages\Icons\" /Y   
 
xcopy "$(ProjectDir)Views\*.*" "$(ProjectDir)..\TestSite\App_Plugins\UmbracoMVCBackofficePages\Views\" /Y 

Step 1: Data

So first step it to create some data that we’ll be working with as this is an example I just creates a very simple Person class and another class that will return some sample data

Person class:

 public class Person
    {
        public Person(int id, string firstName, string lastName)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
 
        }
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
 
        public override string ToString()
        {
            return FirstName + " " + LastName;
        }
    }

 

Data class:

 public class Data
    {
        public static IEnumerable<Person> GetAll()
        {
            var data = new List<Person>();
            data.Add(new Person(1, "Jeff", "Trent"));
            data.Add(new Person(2, "Paula", "Trent"));
            data.Add(new Person(3, "Lieutenant", "Harper"));
            data.Add(new Person(4, "Colonel", "Edwards"));
            data.Add(new Person(5, "Patrolman", "Larry"));
            return data;
        }
 
        public static Person GetById(int id)
        {
 
            return GetAll().Where(p => p.Id == id).FirstOrDefault();
        }
    }


So imagine that this interacts with your datasource….

Step 2: The Tree

Next I’ll add a tree to the settings section that will list my data

 [Tree("settings", "exampleTree", "Example")]
    public class ExampleTree : BaseTree
    {
        public ExampleTree(string application)
            : base(application)
        {
        }
 
        protected override void CreateRootNode(ref XmlTreeNode rootNode)
        {
            rootNode.NodeType   = "example";
            rootNode.NodeID     = "init";
            rootNode.Menu =  new List<IAction> { ActionRefresh.Instance };
        }
 
        public override void Render(ref XmlTree tree)
        {
 
            foreach (var person in Data.GetAll())
            {
                var node = XmlTreeNode.Create(this);
                node.NodeID = person.Id.ToString();
                node.NodeType = "person";
                node.Text = person.ToString();
                node.Action = string.Format("javascript:openExamplePage({0});",
                    person.Id.ToString());
                node.Icon = "../../../App_Plugins/UmbracoMVCBackofficePages/Icons/example-icon.png";
                node.OpenIcon = "../../../App_Plugins/UmbracoMVCBackofficePages/Icons/example-icon.png";
                node.HasChildren = false;
                node.Menu = new List<IAction>();
                OnBeforeNodeRender(ref tree, ref node, EventArgs.Empty);
                if (node != null)
                {
                    tree.Add(node);
                    OnAfterNodeRender(ref tree, ref node, EventArgs.Empty);
                }
 
            }
 
        }
 
        public override void RenderJS(ref System.Text.StringBuilder Javascript)
        {
            Javascript.Append(
               @"function openExamplePage(id) {
                 UmbClientMgr.contentFrame('../App_Plugins/UmbracoMVCBackofficePages/Index?id='+id);
                }");
        }
    }

 

Important part here is the js function that will open the edit page and pass it the id (check the node action)

UmbClientMgr.contentFrame(’../App_Plugins/UmbracoMVCBackofficePages/Index?id=’+id);

Of course we’ll need to make sure ../App_Plugins/UmbracoMVCBackofficePages/Index get’s routed correctly (more in the next steps)

Step 3: Routing

Since I’m not making use of an Umbraco SurfaceController I need to take care of the routing

So my route config looks like

 public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
               name: "ExampleMVCBackofficePages",
               url: "App_Plugins/UmbracoMVCBackofficePages/{action}/{id}",
               defaults: new { controller = "Example", action = "Index", id = UrlParameter.Optional }
           );
        }
    }

And to make sure that is done when the application is started I have an Umbraco ApplicationEventHandler (since I’m using 6.1 +) in place

public class StartUpHandlers : ApplicationEventHandler
    {
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }

 

Step 4: VIew model

Pretty simple model making use of data annotations

public class PersonViewModel
    {
        public int Id { get; set; }
 
        [Display(Name = "First name:")]
        [Required]
        public string FirstName { get; set; }
 
        [Display(Name = "Last name:")]
        [Required]
        public string LastName { get; set; }
 
    }

 

Step 5: Controller

An  MVC controller inheriting from UmbracoAuthorizedController that takes care of the authentication (so you can’t access App_Plugins/UmbracoMVCBackofficePages/Index if you aren’t logged into the backoffice)

public class ExampleController : UmbracoAuthorizedController
    {
        public ActionResult Index(int id)
        {
            var p = Data.GetById(id);
 
            PersonViewModel model = new PersonViewModel();
            model.Id = p.Id;
            model.FirstName = p.FirstName;
            model.LastName = p.LastName;
 
            return View("~/App_Plugins/UmbracoMVCBackofficePages/Views/Index.cshtml", model);
        }
 
        [HttpPost]
        public ActionResult Edit(PersonViewModel person)
        {
            if (ModelState.IsValid) { }
                //do something
 
            TempData["success"] = true;
 
            return View("~/App_Plugins/UmbracoMVCBackofficePages/Views/Index.cshtml",person);
        }
    }

But notice that I do specify the view path since it won’t look in that directory by default

Step 6: View

Strongly typed view that uses the same js/css/markup as the backoffice webforms pages so it also has the same look Smile (and also shows the speech bubble when the form was submitted successfully)

@model UmbracoMVCBackofficePages.Models.PersonViewModel
 
<!doctype html>
<html>
<head>
    <title>Example editor</title>
   
    <script src="~/umbraco_client/ui/jquery.js" type="text/javascript"></script>
    <script src="~/umbraco_client/Application/NamespaceManager.js" type="text/javascript"></script>
    <script src="~/umbraco_client/Application/UmbracoApplicationActions.js" type="text/javascript"></script>
    <script src="~/umbraco_client/Application/UmbracoUtils.js" type="text/javascript"></script>
    <script src="~/umbraco_client/Application/UmbracoClientManager.js" type="text/javascript"></script>
    <script src="~/umbraco_client/ui/default.js" type="text/javascript"></script>
 
 
    <link href="~/umbraco_client/ui/default.css" rel="stylesheet" />
    <link href="~/umbraco_client/menuicon/style.css" rel="stylesheet" />
    <link href="~/umbraco_client/panel/style.css" rel="stylesheet" />
    <link href="~/umbraco_client/propertypane/style.css" rel="stylesheet" />
    <link href="~/umbraco_client/scrollingmenu/style.css" rel="stylesheet" />
    
  
 
    <style>
 
        #save {
            height: 26px;
            margin: 0;
            padding: 0;
        }
 
        #save img {
            padding: 0;
            margin: 0;
        }   
    </style>
    @if (TempData["success"] != null)
    {
        <script>
            UmbClientMgr.mainWindow().UmbSpeechBubble.ShowMessage(’save’, ‘Saved’, ’successfully saved’);
        </script>
    }
</head>
    <body>
        
        @using (Html.BeginForm("Edit", "Example"))
        {
        <div id="body_UmbracoPanel" class="panel" style="width:100%;">
            <div class="boxhead">
                <h2 id="body_UmbracoPanelLabel">Example Editor</h2>
            </div>
            <div class="boxbody">
                <div id="body_UmbracoPanel_menubackground" class="menubar_panel">
                    <span id="body_UmbracoPanel_menu">
                        <table id="body_UmbracoPanel_menu_tableContainer">
                            <tbody>
                                <tr id="body_UmbracoPanel_menu_tableContainerRow">
                                    <td id="body_UmbracoPanel_menu_tableContainerButtons">
                                        <button type="submit" id="save">
                                            <img src="~/umbraco/images/editor/save.gif" alt="Save Bundle" class="editorIcon"/>
                                        </button>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </span>
                </div>
                <div id="body_UmbracoPanel_content" class="content">
                    <div class="innerContent" id="body_UmbracoPanel_innerContent">
                        <h2 class="propertypaneTitel">Details</h2>
 
                        @Html.HiddenFor( m => m.Id)
 
                        <div class="propertypane">
                            <div>
                                <div class="propertyItem">
                                    <div class="propertyItemheader">@Html.LabelFor(m => m.FirstName)</div>
                                    <div class="propertyItemContent">
                                        @Html.EditorFor(m => m.FirstName)
                                        @Html.ValidationMessageFor(m => m.FirstName)
                                    </div>
                                </div>
 
                                <div class="propertyItem">
                                    <div class="propertyItemheader">@Html.LabelFor(m => m.LastName)</div>
                                    <div class="propertyItemContent">
                                        @Html.EditorFor(m => m.LastName)
                                         @Html.ValidationMessageFor(m => m.LastName)
                                    </div>
                                </div>
 
                                <div class="propertyPaneFooter">-</div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="boxfooter">
                <div class="statusBar">
                    <h2></h2>
                </div>
            </div>
        </div>
        }
    </body>
</html>

Result

So that are the different bits and the end result should look like

image

Again source code for this project is available at https://github.com/TimGeyssens/UmbracoMVCBackofficePages

(I’ll also do a follow up post to show you how to get create/delete working)

34 Comments so far

  1. Adrian on July 18th, 2013

    Hi
    This is a really great post, but you use Html.BeginForm which gives me a ‘System.Web.Mvc.HtmlHelper’ does not contain a definition for ‘BeginForm’ and no extension method ‘BeginForm’ error when deploying to Umbraco 6.1.x

    Any tips would be most welcome

  2. Tim Geyssens on July 18th, 2013

    @Adrian, have you tried downloading and running my example site, does that run?

    Try adding @using System.Web.Mvc.Html

    Or make sure you have the same web.config in your folder containing the view as you have in /views

  3. Adrian on July 19th, 2013

    Have discovered the key to this is in the detail, like the web.config in your Views Directory. I would highlight that in this document ideally.

  4. Martin on August 15th, 2013

    Hi Tim,

    Trying to modify this into my own setup.
    However, clicking on a node leaves the page blank.

    I’ve set the routeconfig, and it does get added to the routescollection. But in some way, the page turns up completely blank. If I remove the routeconfig, I’ll get a 404 error.

    Would you know anything to point me in the right direction?

    Martin

  5. Tim Geyssens on August 15th, 2013

    @Martin how does the view for the page look is there some markup on there?

  6. Martin Lingstuyl on August 15th, 2013

    Basically i just used your demopage and changed the model and the formfields. Do you think it’s got something to do with that? How would i know if there’s an error anywhere? Nothing shows up in umbracoLog. And firebug says the blank page returns 200 ok.

    Some extra info: when adding breakpoints the routeconfig will get hit, but the controller won’t. I can’t seem to add break points to the cshtml page, because of symbols not loaded.

    I did try changing some of the names of the controller in the forms and the config, doesn’t seem to work either.

  7. Martin Lingstuyl on August 15th, 2013

    Hmmm, I just created a new controller, as default as possible, and this seems to work now

    How does the route config ‘controller’ default string connect to the right class? As it now seems to not have recognised my original controller class.

  8. Martin on August 16th, 2013

    Hi Tim,

    Is there a simple way to use the umbraco tabs and for example the richtext editor in all this?

    Martin

  9. Martin Lingstuyl on August 16th, 2013

    AH, i discovered that the classname of a controller should actually end with the word ‘controller’. https://www.simple-talk.com/dotnet/asp.net/asp.net-mvc-controllers-and-conventions/
    Thats why mine didnt work. I remember thinking this possibility throughout debugging and dismissing it for seeming too ridiculous a notion :-) maybe i need to update my mvc knowledge. Still think it’s ridiculous though…

    Anyway, its working now. Thanks for the great example!

    Martin

  10. Tim Geyssens on August 17th, 2013

    @Martin no example of tabs but just take a look at how the webforms stuff is done you should be able to reuse the markup/js

  11. Martin on August 23rd, 2013

    Hi Tim / Any others who are interested.

    I looked into it and created some MVC Html Helpers to create tabs with richtext editors. Used it for a project of mine.

    Below a link to a blog and download. It took me a while to grind through all the scripts and html requirements, so it’s only logical anyone else shouldn’t have to go through that :)
    http://interactivewebdesign.nl/sharing/blog/2013/august/umbraco-custom-section-mvc-tabview-and-richtext/

  12. Tim Geyssens on August 26th, 2013

    @Martin awesome :)

  13. Pavel Gurecki on November 4th, 2013

    Great post!

    Any ideas how to use umbraco property editors in custom section mvc view?

  14. Tim Geyssens on November 4th, 2013

    @Pavel, prop editors are web controls so you can’t :) maybe in v7 this will be possible

  15. Karthik on January 22nd, 2014

    Really a great post, helped me to create my own node to list the unapproved members in Members section. Thanks a lot! :)

  16. Fabrice on February 11th, 2014

    Hello,
    I have successfully made that works. Thank you very much.
    By adding one line on the tree.config and application.config and changing the attributes on the ExampleTree class, you can even show the custom tree in a new section !

    Could you please tell me if this method can work with Umbraco 7 ? if not do you have an idea/url of what need to be changed to work ?
    Thank you very much !!

  17. Moran monovich (@moranmono) on February 22nd, 2014

    Thanks a lot great post and project

  18. When considering corporate gifts consider chocolates including bonbons, bars and truffles.
    Chocolove Company has it set of standards, which it
    follows word-by-word for proper manufacturing of high standard
    chocolates. Have a look inside these sensational gift baskets
    with free shipping and add an enjoyably and nice surprise to the celebration of your dearest one.

    Check out my site; bonbons Fabriqués dans Les vosges

  19. faggs on March 24th, 2014

    Wow, awesome blog layout! How long have you been running a blog for?
    you make running a blog glance easy. The entire glance of your
    web site is fantastic, as neatly as the content!

    Here is my website - faggs

  20. wijnen on April 8th, 2014

    Dit is een onderwerp dat is dicht bij Veel dank !
    Waar zijn uw contactgegevens hoewel?

  21. marchande d'amour bi on April 27th, 2014

    Оn peut te dire que ce n’est pas incohérent !

  22. Dom Hemingway Télécharger on April 28th, 2014

    You’re so cool! I don’t suppose I’ve truly read through a single thing like this before.
    So good to find someone with a few unique thoughts on this topic.
    Seriously.. thanks for starting this up. This website is something that is required on the web, someone with some originality!

    Visit my website: Dom Hemingway Télécharger

  23. It’s appropriate time to make some plans for the future and it’s time to be happy.
    I have read this post and if I could I desire to suggest you some interesting things or suggestions.

    Maybe you could write next articles referring to this article.
    I want to read more thiongs about it!

    Here is my webpage - Air Max Tailwind+4 Mujeres Gris/Púrpura Alta Calidad

  24. faux ray ban pas cher on May 16th, 2014

    parceque d abord ce titre n existe pas dans la religion musulmane :faux ray ban soldes il n y a jamais eu et il n y a pas de grand imam .

  25. full moon on May 24th, 2014

    I used to be recommended this blog by way of my cousin. I am not certain whether or not this post is written by
    way of him as no one else realize such unique about my problem.
    You are wonderful! Thanks!

  26. love unconditionally on May 28th, 2014

    Hi to every one, the contents existing at this website are in fact awesome for
    people experience, well, keep up the nice work fellows.

  27. Jamey on July 8th, 2014

    I do nott know if it’s just me or if perhaps everybody else encountering isues with your blog.

    It seems like some of the text on your content arre running off the screen. Can someone else please provide feedback and
    let me know if this is happening to them too?
    This mayy be a problem with my internst browser because I’ve had this happen before.
    Kudos

  28. besace longchamps pas cher on July 13th, 2014

    financières internationales petit sac longchamps pliable l’extrémiste jordanien

  29. comments on July 16th, 2014

    Chartres and Notre Dame had spoiled me on cathedrals,
    making me somewhat blas. ristiques de l’montres
    Omega Speedmaster Replica sont excellents. volue g.

    Here is my web blog - comments

  30. NZ on July 21st, 2014

    Hi Tim,

    Thanks for this post. This is exactly what I am looking for!

    However I am trying it with Umbraco 7 and the javascript doesn’t seem to be rendered.

    When clicking on a person’s node I am getting:

    Error evaluating js callback from legacy tree node: ReferenceError: openExamplePage is not defined

    Is there any reason for the RenderJS not to be called?

    Thanks!

  31. NZ on July 22nd, 2014

    Hi Tim,

    Javascript issue sort, please ignore the above.

    Do you have by any chance an example for unmbraco 7 as well? So when expanding the example tree, an ‘Add’ button will show in the pane the opens? And the view styled as the Umbraco 7 style?

    Thanks!

  32. iwa lubin on July 23rd, 2014

    Wonderful blog! I found it while browsing on Yahoo News.
    Do you have any tips on how to get listed in Yahoo News?
    I’ve been trying for a while but I never
    seem to get there! Appreciate it

  33. sac a main Balenciaga pas cher on July 23rd, 2014

    Ici, tout est abstrait sac a main Chlo茅 pas cher et pourtant, tout est limpide,

  34. moviestarplanet triche on July 29th, 2014

    A few more seconds passed and all three slowly disappeared as if
    they were on a rheostat. They live in high, densely tangled canopies of
    trees in the Atlantic coastal foreets of Brazil. I’m a big fan of dramedy, and Jenji’s
    known for dealing with seriousness and adding elements of comedy to it.

    my weblog; moviestarplanet triche

Leave a Reply