Sunday, 12 December 2010

N2CMS MVC and how to create a configurable MasterPage option

One of the most common questions on the N2CMS forums seems to be how to make it possible to be able to select a master page. In the past when I've worked on cms driven sites the templates have been relatively straightforward - the homepage has had a unique design whilst the others have had a set template. This was quite easy to do as I just created my own class which derived from the StartPage class and then created it's own HTML without using the main MasterPage. Simple, but effective.

However, recently I've been asked to convert an existing Sharepoint site to the MVC version of N2. This has been quite interesting to do and one of the areas I've had to work on is how to give the user the option of which master page template to use. I can't do this statically and because there are a number of templates on the current Sharepoint site it's possible that users may need to change page templates "on the fly".

I'm not going to go to deep in to the workings of this - it's probably easier to fire up N2 MVC and step through the code. I'm going to give you a step-by-step tutorial to get it working!

I'd like to thank the guys on this discussion on the N2CMS forum (n2cms.codeplex.com/discussions?size=2147483647) for providing the inspiration for this. I can't find the exact post however.

Step 1:
Make sure you download the latest v2.1rc MVC version of the site and make sure you copy the DLLs in the library folder into the bin folder of the N2CMS folder. If you don't do this with then you might get an error when you first try to start the site.

Step 2:


In the services folder, open the ThemedMasterViewEngine.cs file. In that there's a method which tries to determine which Master page to use based on theme or uses the  there's a class which checks to see what master page to use.



public class ThemedMasterViewEngine : WebFormViewEngine
{
        // You should change the "TwoColumnLayout.master" to be your default template
        string masterPageFile = "~/Views/Shared/TwoColumnLayout.master";

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (!controllerContext.IsChildAction)
{
if (string.IsNullOrEmpty(masterName))
masterName = masterPageFile;

var root = Find.Closest<StartPage>(controllerContext.RouteData.CurrentPage()) ?? Find.ClosestStartPage;
if (root != null)
{
string theme = root.Theme;
if (!string.IsNullOrEmpty(theme))
{
string potentialMaster = string.Format("~/Views/Shared/{0}.master", theme);
if (base.FileExists(controllerContext, potentialMaster))
{
masterName = potentialMaster;
}
}
}
}

return base.FindView(controllerContext, viewName, masterName, useCache);
}
}

Step 3:

In the models/pages folder there's a ContentPageBase.cs class. We're going to change this to add an enum for the name of the page templates and add a property for any page which derives from the ContentPageBase (which is all of them) which allows you to select a template and puts it under the Advanced tab.

Open /Models/Pages/ContentPageBaseup and add this code:

        public enum PageTemplates
        {
            MyHomePage,
            TwoColumnLayout
        }

        [EditableEnum("Page layout", 60, typeof(PageTemplates), ContainerName = Tabs.Advanced)]
        public virtual PageTemplates PageLayout
        {
            get { return (PageTemplates)(GetDetail("PageLayout") ?? PageTemplates.TwoColumnLayout); }
            set
            {
                SetDetail("PageLayout", value, PageTemplates.TwoColumnLayout);
            }
        }

Step 4:

In the controllers folder, open up the TemplatesControllerBase.cs file. We're going to add some code in here which tries to get the name of the PageLayout from the page and sets the master page. Find the method below (be careful - there's a few overloads of it) and replace the method with this code:

protected override ViewResult View(string viewName, string masterName, object model)
{
CheckForPageRender(model);

            model = model ?? CurrentItem;
            if (model != null)
            {
                var item = model as N2.Templates.Mvc.Models.Pages.ContentPageBase;
                if (!string.IsNullOrEmpty(item.PageLayout.ToString()))
                {
                    masterName = item.PageLayout.ToString();
                }
            }

return base.View(viewName, masterName, model ?? CurrentItem);
}

Step 5:

I think this might be an oversight in the n2 release, but I've found I have to add this line into the web.config in the n2 editing folder:

<httpRuntime requestValidationMode="2.0" />

Otherwise I get an error when I try to change the page layout.

Step 6:

If you start up your site, and select "Edit" on any page you will find that under the "Advanced" tab you have a drop down list of layouts. If you change this you will be able to change the master page used on the site!

16 comments:

  1. This is great. Do you have any guidance on how to define custom routes that use content pages? For example, I want to pass route data to a page like /mypage/new/mode. The mypage is the content page and /new/mode are custom route data. Thanks again for the blog.

    ReplyDelete
  2. Hi "Sales Department"!

    I am not 100% sure what you are looking for - can you expand on what you are trying to achieve? If you can I might be able to help. You can of course pass data via the querystring which may be a bit easier that writing custom routes. Is there any particular reason why you are looking to do this?

    ReplyDelete
  3. Our existing site has custom routes using asp.net webforms. We were hoping to keep the same urls without changing to query strings. We have one page that has multiple modes and and id field we pass into it. We were hoping to just publish /mypage in n2cms then use custom routes to pass querystring or route data to it. I haven't figured out how to do it yet. Any guidance or help would be great.

    ReplyDelete
  4. Thanks that's helped me a lot. Keep up good work.

    ReplyDelete
  5. Annette (a newbie )22 March 2011 at 06:15

    Thanks that's helped me a lot too. But I run into a problem. If I add a News Container to the Site I can't change the layouts. It's always take my default template even if I choose another template. ;-( What should I do to make the News Container work with different layouts? Thanks again for this good work!

    ReplyDelete
  6. Hi Anette,

    Hmm... I haven't used the news container on any of my sites yet so I can't really advise on this one. I'm surprised it works for the others apart from the news container.

    If I can get a little time over the next couple of days I'll see if there's anything I can suggest!

    ReplyDelete
  7. I'm unsure why, but I cannot appear to get this to work.
    If I try to implement Step 5, then I always get an error. If I don't implement Step 5, then the pages will all load, but there's no "Advanced" tab that appears on the page to change to a different layout page.
    There is (in my version of web.config) an instance of the Step 5 code, but it was commented out in the initial code from the installation. So ..... I'm a little confused.

    ReplyDelete
  8. Following up on the last post.
    I've started from scratch and completely re-done this entire process, but getting a number of errors now in the attempted debug/build process in VS2010.
    This just does not appear to be working for me and I'm pretty confident that I've followed everything correctly.
    Really not sure what's happening here. Please help!

    ReplyDelete
  9. Hi Steve

    Can you tell me what errors you're getting?

    ReplyDelete
  10. Hi There...
    been a while since your response. I'm sorry for the slow followup.
    I actually posted the last two posts on here from 4/13. Same issue appears from the first 4/13 post - there is no Advance Tab appearing on the page. I can't see any location to change the layout on a specific page.

    ReplyDelete
  11. Nudge....
    Still having this error. Any further help?

    ReplyDelete
  12. OK -- Updates: Using VS 2010

    ERROR: ContentPageBase.cs / STEP 3
    The modifier 'virtual' is not valid for this item
    (FROM THIS LINE)
    public virtual PageTemplates PageLayout


    ERROR: Expected class, delegate, enum, interface, or struct
    FROM SAME LINE ABOVE

    ReplyDelete
  13. Hi SteveF

    Sorry I've not been around recently and haven't had time to look at any N2 stuff recently as I've been working on other projects. I'll see if I can squeeze sometime in over the next week to look at your problem.

    Cheers!

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Plz help in n2cms mvc responsive homepage design

    ReplyDelete