This is part 2 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?.

In the first post I

  • Added the N2 assemblies
  • Added the edit site to the web project
  • Created the initial content definitions
  • Created the database schema

Step 5 – Adding the root nodes to the database

We need to insert some root data in the N2 tables for the site root and the home page. I already created the content definitions for these back in step 3, so I can create a SQL insert script to add these two rows to the table.

INSERT INTO [dbo].[n2Item] ([ID], [Type], [Created], [Published], [Updated], [Expires], [Name], [ZoneName], [Title], [SortOrder], [Visible], [SavedBy], [State], [AncestralTrail], [VersionIndex], [VersionOfID], [ParentID]) VALUES (1, N'SiteRoot', '20100118 15:48:59.000', '20100118 15:48:59.000', '20100118 15:48:59.000', NULL, N'root', NULL, N'Who Can Help Me?', 0, 1, N'admin', 1, N'/', 0, NULL, NULL)
INSERT INTO [dbo].[n2Item] ([ID], [Type], [Created], [Published], [Updated], [Expires], [Name], [ZoneName], [Title], [SortOrder], [Visible], [SavedBy], [State], [AncestralTrail], [VersionIndex], [VersionOfID], [ParentID]) VALUES (2, N'HomePage', '20100118 15:48:59.000', '20100118 15:48:59.000', '20100127 10:03:56.000', NULL, N'home', NULL, N'Home', 0, 1, N'admin', 16, N'/1/', 1, NULL, 1)

As we’re using the Visual Studio Team System Database Edition, I can add this script to the Post-Deployment scripts that will get run whenever I deploy the database, so I can easily re-base the CMS data. If you don’t have access to this, then it’s a manual SQL insert I’m afraid.
Changeset reference 54957

Step 6 – Update Web.Config

We need to add the N2 specific configuration to web.config. I copied the N2 configuration form the output of the N2 MVC templates example site into my web.config.

<n2>
  <host rootID="1" startPageID="2">
    <web extension="" rewrite="None" />
  </host>
  <engine>
    <assemblies>
      <add assembly="N2"/>
      <add assembly="N2.Edit"/>
      <add assembly="N2.Security"/>
    </assemblies>
  </engine>
  <database connectionStringName="N2CMS" flavour="SqlServer2005" caching="false" cacheProviderClass="NHibernate.Caches.SysCache2.SysCacheProvider, NHibernate.Caches.SysCache2" />
</n2>

I also needed to modify the runtime assembly binding redirect for NHibernate as the build of N2 I’m referencing uses a different version (the joys of using OSS projects!). We already had a redirect in there, but it was referencing a specific version of the assembly. I just loosened this a bit by redirecting all versions to the latest and greatest.

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="NHibernate"
                        publicKeyToken="aa95f207798dfdb4"
                        culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-2.1.1.4000"
                       newVersion="2.1.2.4000" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Changeset reference 55241

Step 7 – Initialise N2 and Register Routes

In WCHM we had a RouteInitialiser which was responsible for adding the MVC routes into the route table at application startup. N2 works by addding a custom N2ContentRoute into the route table which handles all the N2 urls and routes them through to the correct controller based on the content definition.

The beauty of N2 is that you can decide how much you want to feature in your application – not everything has to go through N2, so in an MVC application you can mix standard MVC routes with the N2ContentRoute. As the MVC routing works top-to-bottom, the first matching route wins, so it’s down to you to decide where N2 should sit in the routing table. A full CMS site would need N2 sat at the top handling everything, but if the majority of your app has no CMS input, then you might choose to put N2 at the bottom to handle anything that hasn’t already been picked up.

What N2 is doing is taking the URL path to a piece of content and routing it to the correct controller based on the content definition type. So, where you would normally have

http://www.mysite.com/about

routing to either the About action on the HomeController, or the Index action on an AboutController, N2 would find the “About” piece of content, see that it is of type, say TextPage, and route it through to the TextPageController. Therefore, all pieces of content that are TextPages get handled by this controller, regardless of where they sit in the content hierarchy. Clever stuff.

I start by deleting the RouteInitialiser as it’s no longer needed. We need to initialise the N2 engine and add the N2 content route to the route table, which is easier to do together as the content route needs the engine. I create an N2RouteRegistrar class to do both of these actions:

namespace WhoCanHelpMe.Web.Controllers.Registrars
{
    using System.ComponentModel.Composition;
    using System.Web.Mvc;
    using System.Web.Routing;
    using Castle.Windsor;
    using Domain.Contracts.Container;
    using N2.Castle;
    using N2.Web.Mvc;

    /// <summary>
    /// Registers the ASP.NET MVC Routes with the CMS system so that
    /// CMS pages can be resolved as URLs.
    /// </summary>
    [Export(typeof(IComponentRegistrar))]
    public class N2RouteRegistrar : IComponentRegistrar
    {
        /// <summary>
        /// Initialises the N2 engine and registers all the routes
        /// </summary>
        /// <param name="container">
        /// The container.
        /// </param>
        public void Register(IWindsorContainer container)
        {
            RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            RouteTable.Routes.IgnoreRoute(" { *favicon }", new { favicon = @"(.*/)?favicon.ico(/.*)?" });
            RouteTable.Routes.RouteExistingFiles = false;

            // Initialise N2 for MVC
            container.Kernel.RemoveComponent("CachingService"); // TODO – fix this
            var engine = MvcEngine.Create(new WindsorServiceContainer(container));

            if (engine != null)
            {
                // ************************************************************************
                // This route detects N2 content item paths and executes their controller
                //************************************************************************
                RouteTable.Routes.Add(new ContentRoute(engine));
            }

            // Add the default route
            RouteTable.Routes.MapRoute(
                "Default",
                "{controller}/{action}/{id}",
                new { controller = "Home", action = "Index", id = string.Empty });
        }
    }
}

I encountered a slight problem at this point – we want to pass N2 our existing Windsor container so that it has access to all the dependencies as it will be responsible for creating our controllers that handle N2 requests. However, when I pass the container in the N2 WindsorServiceContainer (line 32), the XML container.components.config file seems to get read again. N2 throws an exception at this point as it’s trying to register the components from the XML config again. I haven’t yet figured out a solution to this, on previous projects we’ve had no XML configuration for Castle Windsor so haven’t encountered this. I found a hacky workaround for now that works, but I want to come back and sort this out at a later date. For now, adding a call to remove the CachingService component from the container (line 31) sorts this out as it’s the only thing in the XML config, and it gets read back in again when N2 does its stuff with our container. Not great, but so far, so good.

Changeset reference 55242

Next

I’m going to stop there as next we need to start changing our existing controllers to wire them up to handle requests for N2 content definitions. Again, we don’t have a working site yet, but we’re getting there…