Integrating N2CMS into Who Can Help Me? Part 5
Tags: .NET, N2 CMS, Who Can Help Me 3 Comments »This is part 5 in my series of posts about integrating the .Net open source content management system N2CMS into the Sharp Architecture demo application Who Can Help Me?.
So far, I’ve
- Added the N2 assemblies
- Added the edit site to the web project
- Created the initial content definitions
- Created the database schema
- Added root nodes to the CMS database
- Updated the Web.config
- Set up the N2 Content Routes and intialised the CMS engine
- Added a base CMS controller
- Updated the HomeController to be a CMS controller
- Tackled authentication with Open ID and the N2 membership providers
- Built up the HomePage content definition
- Updated the HomeController, the view model, mappers and the view
We’ve now got content from inside N2 displaying on the Home page of the site, which is excellent and I could stop there. However, there’s just a couple more things I want to cover off before I wrap this series up.
N.B. It should be noted that a lot of this code is going to be out of data very soon! WCHM is still running against MVC 1.0 and my branch is running off an old version of N2CMS. Both the Sharp Architecture project and N2CMS are currently being updated for MVC 2.0 support, and I guess I will also have to do the same as soon as I find the time, but to keep things simple I’m going to stick with the codebase as it is for now.
I want to focus on adding more CMS definitions this time and creating a dynamic navigation that is populated from the CMS tree.
Step 18 – Reworking the About page
Our site only has one content managed page – the Home page, which is nice, but what if we wanted to add a new page to our site through N2 – that’s the point of content management after all! Well, at the minute, nothing much would happen as all our site navigation is hard coded in the Menu.spark view file. I want to be able to add new pages as I like and have them appear in this navigation dynamically. The WCHM site already has an About page, which is kind of a generic text page, so I’m going to re-work this into a N2 TextPage defintion.
I actually added a TextPage definition way back in Changeset 54950 which we can use for a generic page in the site. For recap, here’s the definition:
[PageDefinition("Text Page",
Description = "A text page.",
SortOrder = 700,
IconUrl = "~/edit/img/ico/png/page.png")]
[WithEditableTitle("Page Title", 5, Focus = true, ContainerName = Tabs.Content)]
[WithEditableName(ContainerName = Tabs.Content)]
[RestrictParents(typeof(HomePage), typeof(TextPage))]
public class TextPage : AbstractPage
{
/// <summary>
/// Gets or sets BodyText.
/// </summary>
/// <value>
/// The body text.
/// </value>
[EditableFreeTextAreaAttribute("Body Text", 100, ContainerName = Tabs.Content,
HelpText = "Set the body text for the page")]
public string BodyText
{
get { return (string)GetDetail("BodyText"); }
set { SetDetail("BodyText", value); }
}
}
We’re restricting this definition to only be available under the home page, or another text page and giving the user the ability to update one property – the Body Text, which is a string property and will be edited using a WYSIWYG HTML editor.
As this is already part of the solution, when I right click on the HomePage in the N2 site tree from within the admin interface and select New, I have the option to create a new TextPage:
I can populate some data into the new about page:
And save the page, which means it will now appear in the content tree:
As yet, we have no controller that handles the TextPage definitions, and no view to render them, however, as we have an existing non-CMS controller called AboutController, selecting the About page in the CMS tree requests a URL of /about, which calls the existing AboutController so we see the existing content.
If we add a new TextController to handle the TextPage definitions and the associated view, in a similar fashion to the HomeController and Index view for the home page the /about route will be handled by N2 instead to display our new CMS content.
[Controls(typeof(TextPage))]
public class TextController : N2Controller<TextPage>
{
private readonly ITextPageViewModelMapper textPageViewModelMapper;
public TextController(ITextPageViewModelMapper textPageViewModelMapper)
{
this.textPageViewModelMapper = textPageViewModelMapper;
}
public override ActionResult Index()
{
return View(textPageViewModelMapper.MapFrom(CurrentItem));
}
}
Here’s the associated mapper class – note that as it’s inheriting from the base page mapper class, which uses AutoMapper, we don’t actually need to add any mapping logic:
public class TextPageViewModelMapper : BasePageViewModelMapper<TextPage,TextPageViewModel>,
ITextPageViewModelMapper
{
public TextPageViewModelMapper(IPageViewModelBuilder pageViewModelBuilder) : base(pageViewModelBuilder)
{
}
}
This is because the TextPageViewModel has properties with the same name as the TextPage definition, so Automapper can just wire them up:
public class TextPageViewModel : PageViewModel
{
public string BodyText { get; set; }
public string Title { get; set; }
}
And finally, here’s the Spark view:
<viewdata model="Text.Model.TextPageViewModel"/>
<content name="title">
${Model.Title}
</content>
!{Model.BodyText}
(pretty terse don’t you think!?)
So, know when we navigate to the /about URL, we see our CMS content:
Step 19 – Updating the Navigation
Ok. so now we can add as many pages in the CMS of type TextPage as we like. However, our navigation menu is still hard-coded to write out the original links. We need to make this dynamic so that as we add new pages, our navigation menu stays in synch.
We already have a NavigationController that is being called via the RenderAction method to render out the menu, so we need to update this and the associated view model to get the links from the CMS.
I start by updating the specification for the NavigationController by adding the following specs:
It should_ask_the_cms_tasks_for_the_navigation_items = () =>
cms_tasks.AssertWasCalled(x => x.GetNavigationItems());
It should_set_the_list_of_cms_links_correctly = () =>
result.Model<MenuViewModel>().CmsLinks.Count.ShouldEqual(3);
This leads me to creating a new ICmsTasks interface and updating the MenuViewModel to add a list of CmsLinks, which I created as a new LinkViewModel as this could be something that I might re-use elsewhere.
public interface ICmsTasks
{
List<ContentItem> GetNavigationItems();
}
public class MenuViewModel
{
public MenuViewModel()
{
CmsLinks = new List<LinkViewModel>();
}
public bool IsLoggedIn { get; set; }
public List<LinkViewModel> CmsLinks { get; set; }
}
My new specifications fail until I update the NavigationController to call into the new ICmsTasks:
public class NavigationController : BaseController
{
private readonly IIdentityTasks identityTasks;
private readonly ICmsTasks cmsTasks;
private readonly ILinkViewModelMapper linkViewModelMapper;
public NavigationController(IIdentityTasks identityTasks, ICmsTasks cmsTasks, ILinkViewModelMapper linkViewModelMapper)
{
this.identityTasks = identityTasks;
this.cmsTasks = cmsTasks;
this.linkViewModelMapper = linkViewModelMapper;
}
public ActionResult Menu()
{
return View(
string.Empty,
string.Empty,
new MenuViewModel
{
IsLoggedIn = this.identityTasks.IsSignedIn(),
CmsLinks = cmsTasks.GetNavigationItems().MapAllUsing(linkViewModelMapper)
});
}
}
So that’s all great, but now we need to create and implement our real CmsTasks layer. There’s various ways that we could implement the GetNavigationItems() functionality – we could add an extra property to the base page class – ShowInNavigation, and retrieve all of these items, or we could come up with any other complicated requirement. However, for the purposes of this demo, I’m just going to return all the top-level items, that is all the pages under the Home page. This logic would need updating if you want to show nested items or have more of a context specific navigation menu. There’s loads of help in the N2 samples and forums about all of this.
What’s great about integrating N2 and WCHM together is that both projects use CastleWindsor as the IOC container. When we initialise the N2 engine, we pass in our existing WindsorContainer, which means that at run time we can get any of the N2 dependencies out of the container that we need. Because of this, our CmsTasks can treat N2 as just a repository of data and it can depend on any of the N2 interfaces for accessing content. These dependencies will be inject automatically at runtime, meaning our solution is loosely coupled and we can test our tasks easily. Here’s my specs for the CmsTasks:
public abstract class specification_for_cms_tasks : Specification<ICmsTasks, CmsTasks>
{
protected static IUrlParser n2Repository;
Establish context = () =>
{
n2Repository = DependencyOf<IUrlParser>();
};
}
public class when_the_cms_tasks_is_asked_for_the_navigation_items : specification_for_cms_tasks
{
static IList<ContentItem> result;
Establish context = () =>
{
n2Repository.Stub(x => x.StartPage).Return(An<HomePage>());
n2Repository.StartPage.Children = new List<ContentItem>{new TextPage(), new TextPage()};
};
Because of = () => result = subject.GetNavigationItems();
It should_ask_the_cms_repository_for_all_the_top_level_content_items = () =>
result.Count.ShouldEqual(2);
}
In this case, I’m depending on the IUrlParser from N2, which gives me direct access to the StartPage content item property, which is my site Home page. From here, I can easily get the list of child content items.
Another good N2 interfaces is the IItemFinder, which gives you access to the fluent linq-style N2 sytnax for finding content items using filters etc. Take a while to look at the N2 codebase and you’ll see that you don’t need to tightly couple your app to the N2.Find… API, which forces you to create fake contexts etc for testing purposes.
Our actual CmsTasks implementation is then as follows:
public class CmsTasks : ICmsTasks
{
private readonly IUrlParser n2UrlParser;
public CmsTasks(IUrlParser n2UrlParser)
{
this.n2UrlParser = n2UrlParser;
}
public IList<ContentItem> GetNavigationItems()
{
return n2UrlParser.StartPage.Children;
}
}
All that’s left now is to update the Menu.spark view. I’ve removed the hard coded reference to the About page and added a loop of <LI> tags over the list of CmsLinks:
<ul>
<li class="leaf first">!{Html.ActionLink<WhoCanHelpMe.Web.Controllers.Home.HomeController>(x => x.Index(), "Home")}</li>
<li>!{Html.ActionLink<WhoCanHelpMe.Web.Controllers.Profile.ProfileController>(x => x.Update(), "You!")}</li>
<li if="Model.IsLoggedIn" class="leaf last">!{Html.ActionLink<WhoCanHelpMe.Web.Controllers.User.UserController>(x => x.SignOut(), "Sign Out")}</li>
<!-- Loop over the cms links-->
<li each="var cmsLink in Model.CmsLinks">
<a href="${cmsLink.Url}">${cmsLink.Title}</a>
</li>
</ul>
Note that I’m not using the MVC HtmlHelper extension methods to build up the hyperlinks. This is simply because they’re not going to work in a CMS context. Remember that although we’re point to a TextController in this case, our pages are actually called something else (About) and it’s that which will appear in the URL. So, this breaks away from the standard controller/action/index routing convention that MVC uses. The N2ContentRoute handles this all for us, but it means that we lose some of the out the box MVC support – e.g. the HtmlHelperMethods. I think the N2 project has implemented a bunch of it’s own extension methods for this, but I don’t really like adding this to my views as it kind of feels wrong – accessing entities from data stores in the view isn’t something I want to do. I prefer my approach of accessing the N2 data further down the stack and converting to dumb view models before passing to the view.
Ok, so now we’re ready to add more pages. Every page that we add under the site root (the Home page) is going to render a new navigation menu link. I’ve added a few new pages here:
Summary
Hopefully I’ve shown here how I’d go about adding CMS driven navigation to my custom application. Remember – the point behind all of this is that what we’re building is not just a pure-CMS site. If this site was just pure content, entirely driven from a CMS then we could make things a whole lot simpler. However, experience tells me that most applications I built are more of the “custom web app” variety and content management is a supporting function. This is where N2 really comes into its own as it’s really easy to integrate into any form of .Net application.
Next Time
So I’ve just got one more thing to cover off before I wrap this up, and that’s Caching. This is important in a CMS backed application as there’s a lot of dynamic, changeable data and you need to find the right balance of performance and presenting the most up to date information. I’ll go through some of the approaches I’ve taken in the past.
Cheers

Recent Comments