Integrating N2CMS into Who Can Help Me? Part 4

Uncategorized No Comments »

This is part 4 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, second and third posts I

  • 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

Step 11 – Build up the HomePage content definition

Our HomePage definition has nothing on it – which means that although we can create a HomePage in the N2, there’s no properties to set, so no CMS content is actually being displayed on the Home Page.

I’m going to keep this simple and just add a BodyText property, using the N2 FreeTextArea control so the user gets a full WYSIWYG editor. I add the following property definition to HomePage.cs:

[PageDefinition("Home Page",
    Description = "A home page template.",
    SortOrder = 440,
    InstallerVisibility = InstallerHint.PreferredRootPage | InstallerHint.PreferredStartPage,
    IconUrl = "~/edit/img/ico/png/page_world.png")]
[WithEditableTitle("Title", 5, Focus = true, ContainerName = Tabs.Content)]
[RestrictParents(typeof(SiteRoot))]
public class HomePage : 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); }
    }
}

This now means that when we browse to our edit site and edit the Home page, we see the following edit view:

image

Changeset reference 56042

Step 12 – Update the HomePageViewModelMapper

Ultimately we want to display the CMS content on the HomePage view, but our Home page also displays a new feed that comes from the NewsTasks i.e. nothing to do with the CMS data. This is fine – it just means that we need to map both the CMS content and the News feed data to the view.

Lets start by updating our mapper interface:

public interface IHomePageViewModelMapper : IMapper<IList<NewsItem>, HomePage, HomePageViewModel>
{
}

We now map from a HomePage (from the CMS) and a list of NewsItems (from the NewsTasks) and return a HomePageViewModel. This is where the use of view models in MVC really becomes important – anything other than a really simple application will be displaying data from a variety of sources and so it’s not always possible to bind directly to your domain model. I find that it’s always a good idea to start with the use of view models from the offset as you will inevitably need them and it saves you refactoring time later on. As we already are inheriting from a base view model mapper, and utilising the power of Automapper, updating the HomePageViewModelMapper is easy. All we need to do is update the CreateMap method to set up the Automapper strategy for mapping from a HomePage to a HomePageViewModel. We’ll rely on the out the box behaviour of Automapper that properties with the same name are automatically mapped:

public class HomePageViewModelMapper : BasePageViewModelMapper<IList<NewsItem>, HomePage, HomePageViewModel>,
                                       IHomePageViewModelMapper
{
    private readonly INewsItemViewModelMapper newsItemViewModelMapper;

    public HomePageViewModelMapper(
        IPageViewModelBuilder pageViewModelBuilder,
        INewsItemViewModelMapper newsItemViewModelMapper)
        : base(pageViewModelBuilder)
    {
        this.newsItemViewModelMapper = newsItemViewModelMapper;
    }

    protected override void CreateMap()
    {
        Mapper.CreateMap<IList<NewsItem>, HomePageViewModel>().ConvertUsing(list => this.DoMapping(list));
        Mapper.CreateMap<HomePage, HomePageViewModel>();
    }

    private HomePageViewModel DoMapping(IList<NewsItem> input)
    {
        return new HomePageViewModel
        {
            NewsItems = input.MapAllUsing(this.newsItemViewModelMapper)
        };
    }
}

Step 13 – Update the HomeController

Now we’ve updated our mapper, our HomeController needs to change as it’s this which calls the mapper. I start by updating the specs for the HomeController, to make sure that both the HomePage and the list of news items are passed to the mapper:

[Subject(typeof(HomeController))]
public class when_the_home_controller_is_asked_for_the_default_view : specification_for_home_controller
{
    static HomePageViewModel the_view_model;
    static IList<NewsItem> the_news_items;

    static ActionResult result;

    Establish context = () =>
        {
            the_view_model = new HomePageViewModel();
            the_news_items = new List<NewsItem>();

            news_tasks.Stub(nt => nt.GetProjectBuzz()).Return(the_news_items);
            home_view_model_mapper.Stub(hvmm => hvmm.MapFrom(the_news_items, home_page)).Return(the_view_model);
        };

    Because of = () => result = subject.Index();

    It should_return_the_default_view = () =>
        result.ShouldBeAView().And().ShouldUseDefaultView();

    It should_map_the_home_page_content_and_the_list_of_news_items_the_view_model =
        () => home_view_model_mapper.AssertWasCalled(hvmm => hvmm.MapFrom(the_news_items, home_page));

    It should_pass_the_view_model_to_the_view =
        () => result.Model<HomePageViewModel>().ShouldBeTheSameAs(the_view_model);
}

And then we can update the HomeController itself, which should make the code compile again and make the tests pass. The HomePage content from the CMS is accessed via the CurrentItem property which is surfaced from the base N2Controller and kindly automatically populated for us by N2:

[Cached(CacheName.AdHoc)]
private PageViewModel IndexInner()
{
    var buzz = this.newsTasks.GetProjectBuzz();

    return this.homePageViewModelMapper.MapFrom(buzz, CurrentItem);
}

Changeset reference 56163

Step 14 – Update the HomePageViewModel

All we need to do is add a new property that matches the BodyText property on our HomePage definition for Automapper to be able to map the properties:

public class HomePageViewModel : PageViewModel
{
    public HomePageViewModel()
    {
        this.NewsItems = new List<NewsItemViewModel>();
    }

    public IList<NewsItemViewModel> NewsItems { get; set; }

    public string BodyText { get; set; }
}

Changeset reference 56164

Step 15 – Update the view

Now everything should be wired up, we need to display the CMS content on the home page view. I remove the hard coded text in the Spark view file and replace it with with the BodyText property of the view model. Note the Spark syntax start with !{} – the exclamation mark means that Spark will not HTML encode this value, which is what we need as the content from the CMS will be a HTML string:

<viewdata model="WhoCanHelpMe.Web.Controllers.Home.ViewModels.HomePageViewModel"/>

<content name="title">
What's all this then?
</content>

!{Model.BodyText}

<h3>The latest WCHM buzz...</h3>
<NewsHeadlines />

Changeset reference 56165

Step 16 – Disable caching

WCHM makes use of caching in an attempt to show how to set up different expiration times on related pieces of data. Unfortunately, as our CMS system allows users to update content, our views are going to need to be a bit more dynamic – for now I’m just going to disable the caching service so that any changes I make will be displayed. I’ll revisit this next time to try to come up with a better solution that works for the CMS:

The cache can be disabled in the container.components.config file:

<configuration>
  <components>
    <component id="CachingService"
               service="WhoCanHelpMe.Framework.Caching.ICachingService, WhoCanHelpMe.Framework"
               type="WhoCanHelpMe.Infrastructure.Caching.HttpCachingService, WhoCanHelpMe.Infrastructure"
               lifestyle="singleton">
      <parameters>
        <enabled>false</enabled>
        <cacheDurations>
          <dictionary>
            <item key="VeryShort">60</item>
            <item key="Short">120</item>
            <item key="Medium">300</item>
            <item key="Long">10800</item>
            <item key="Permanent">86400</item>
          </dictionary>
        </cacheDurations>
        <caches>
          <dictionary>
            <item key="AdHoc">Medium</item>
            <item key="MvcTempData">Short</item>
          </dictionary>
        </caches>
      </parameters>
    </component>
  </components>
</configuration>

Changeset reference 56166

Step 17 – Update the N2 Item table schema (D’oh!)

So my authentication workaround had a slight problem – the openId username that I’m using for my N2 username is considerably longer than the 50 character limit that N2 imposes on its Item table’s SavedBy column. I updated this to NVCHAR(255) to match the column in the WCHM profile table.

Changeset reference 56167

Try it all out!

I can now browse to the N2 edit site and switch into edit mode of the Home Page:

image

If I add some content and click Save and Publish, we can now see the N2 content on the Who Can Help Me? home page! -

image

Next…

So we’ve now got a working content management system and we can set the body text for the HomePage and see it displayed in the view alongside our news feed content. Next time I’m going to try and deal with the caching issue and also do the same for the About page which will force me to tackle the navigation, which will also need to come from the CMS…

Integrating N2CMS into Who Can Help Me? Part 3

Uncategorized No Comments »

This is part 3 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 and second post I

  • 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

Step 8 – Add a base CMS controller

To link our content items in the CMS database to the appropriate controllers, we need our CMS related controllers to inherit from the N2 controller class ContentController<T> where T is the type of content definition that the controller is handling. This means that we have access to a strongly typed instance of the current content item that the controller is handling within our controller actions.

We already have a BaseController class in WCHM, as we wanted to set up some defaults for the master page name to use etc in the Spark views. Ideally, I’d like to be able to reuse this code, but as our CMS controllers need to be generic, I had to create a new base class, N2Controller<T> which inherits the N2 ContentController<T> and duplicate the method overrides for setting up the default master page etc.

namespace WhoCanHelpMe.Web.Controllers
{
    using System.Web.Mvc;
    using N2;
    using N2.Web.Mvc;

    /// <summary>
    /// Base N2 controller
    /// </summary>
    /// <typeparam name="T">
    /// The content item type
    /// </typeparam>
    public class N2Controller<T> : ContentController<T> where T : ContentItem
    {
        /// <summary>
        /// Stores the site master name
        /// </summary>
        private const string MasterName = "Site";

        /// <summary>
        /// Default controller action
        /// </summary>
        /// <returns>
        /// The default view with the current CMS item as the model
        /// </returns>
        public override ActionResult Index()
        {
            return View(CurrentItem);
        }

        /// <summary>
        /// Returns a view result with default view and master name
        /// </summary>
        /// <returns>
        /// View Result
        /// </returns>
        protected new ViewResultBase View()
        {
            return View(string.Empty, MasterName, null);
        }

        /// <summary>
        /// Returns a view result with specified view name and the default master name
        /// </summary>
        /// <param name="viewName">
        /// The view name.
        /// </param>
        /// <returns>
        /// View Result
        /// </returns>
        protected new ViewResultBase View(string viewName)
        {
            return View(viewName, MasterName, null);
        }

        /// <summary>
        /// Returns a view result with the specified view name, default master name
        /// and the specified model
        /// </summary>
        /// <param name="viewName">
        /// The view name.
        /// </param>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// View Result
        /// </returns>
        protected new ViewResult View(string viewName, object model)
        {
            return View(viewName, MasterName, model);
        }

        /// <summary>
        /// Returns a view result with default view and master name
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// View Result
        /// </returns>
        protected new ViewResult View(object model)
        {
            return View(string.Empty, MasterName, model);
        }
    }
}

Changeset reference 55248

I also added my specs for the behaviour of the base CMS controller:

namespace MSpecTests.WhoCanHelpMe.Web.Controllers
{
    using System.Web.Mvc;
    using System.Web.Routing;
    using global::WhoCanHelpMe.Domain.Cms.Pages;
    using global::WhoCanHelpMe.Web.Controllers;
    using Machine.Specifications;
    using Machine.Specifications.AutoMocking.Rhino;
    using Machine.Specifications.Mvc;

    public abstract class context_for_n2_controller : Specification<N2Controller<HomePage>>
    {
        Establish context = () =>
            {
                var routeData = new RouteData();
                routeData.Values.Add("n2_item", new HomePage { Name = "Home Page" });

                subject.ControllerContext = new ControllerContext() { RouteData = routeData };
            };
    }

    [Subject(typeof(N2Controller<>))]
    public class when_the_n2_controller_is_asked_for_the_default_action : context_for_n2_controller
    {
        static ActionResult result;

        Because of = () => result = subject.Index();

        It should_return_the_default_view = () =>
            result.ShouldBeAView().And().ShouldUseDefaultView();

        It should_use_the_default_master_page = () =>
            result.ShouldBeAView().And().MasterName.ShouldEqual("Site");

        It should_populate_the_view_model_with_the_current_item_from_the_cms = () =>
            result.Model<HomePage>().Name.ShouldEqual("Home Page");
    }
}

What I’ve done here is add a base Index() action to the CMS controller that will just pass the current content item as the model into the view. In reality, I’ll probably not use this as in WCHM we’re pretty strict on using viewmodels to bind to our views, rather than domain entities (which is what the CMS definitions really are) however, this allows a quick and dirty way to pass the CMS content into the view as your’re building up your application. With anything but the most simple application, your view will be made up of more than just the content from the CMS, so you’ll need to build up a more specific model for the view anyway.

Note how N2 works – when it realises that it can handle an incoming request for a URL with a content item it creates that item for us and puts it into the RouteData before calling the associated controller. This means that we easily have access to the current item via the ContentController<T>.CurrentItem property. We can easily simulate this for the purposes of our specs by adding a content item into the RouteData associated with our ControllerContext – (in the Establish context).

Changeset reference 55314

Step 9 – Update the HomeController

The goal of this exercise was to surface content from N2CMS onto our WCHM Home page and About page. We’ve now got a base CMS controller that we can use, so next I refactor the existing HomeController to inherit N2Controller.

[Controls(typeof(HomePage))]
public class HomeController : N2Controller<HomePage>
{
   ...existing code the same for now...
}

The things to note are that we now inherit N2Controller which means we can easily access the current item (as a HomePage) from within our controller, and the Controls[] attribute, which tells N2 which content types this controller is going to handle. Any requests matching a content item of type HomePage will now come this way.

We also need to update our specs as because we now inherit N2Controller, which in turn inherits ContentController<T> (in the N2 namespace), our controller is going to try to access the current content item out of RouteData. Even though we’re not accessing it yet, unless we put it into RouteData, our specs will fail. I updated the Establish context for the HomeControllerSpecs in the same fashion as the specs for our base N2Controller, by adding a content item into the RouteData associated with our ControllerContext.

Establish context = () =>
{
    var routeData = new RouteData();
    home_page = new HomePage();
    routeData.Values.Add("n2_item", home_page);

    home_view_model_mapper = DependencyOf<IHomePageViewModelMapper>();
    news_tasks = DependencyOf<INewsTasks>();

    ServiceLocatorHelper.AddCachingService();

    subject.ControllerContext = new ControllerContext() { RouteData = routeData };
};

Changeset reference 55315

At this point, our site should build, compile, pass tests and load up in the normal WCHM fashion, so lets take a pause and go off tangent ever so slightly…

Step 10 -  Authentication

This is a bit of a tricky one in WCHM as we’re already using Open ID for users to authenticate themselves in order to create profiles. N2 uses the Asp.Net membership providers to manage users and access to the CMS system (although it has it’s own custom provider that stores the users as content items in it’s own database). We don’t want to allow anyone to be able to get into the CMS, but we need to be able to distinguish between those that can and cannot with one unified mechanism.

I went round in circles a bit with this one, and with some help from Jon, I ended up back right where I started, which was to do nothing. The solution I came up with is a little hacky, but it works, and requires no changes to N2, or the WCHM authentication, so all is good for now.

In order to set up a CMS user, I first created a profile in the usual way in WCHM, using my preferred Open ID provider. I then opened the Profiles database table in the WCHM database and found the Open ID username that has been returned and stored against my profile. If, like me, you use a Google account, it will look something like this: (this is not my real Open ID!)

https://www.google.com/accounts/o8/id?id=asdasdasdasd6auasoas_asdadssad_das

I then used this username to create a new admin user in the N2 database in the normal way I would do in N2. Firstly, I need to add the relevant membership config for N2 into the web.config:

<!-- These membership and role providers store their data as content items. You can also use the forms authentication credentials only (remove these sections) or the default ASP.NET providers (check your ASP.NET documentation) -->
<membership defaultProvider="ContentMembershipProvider">
  <providers>
    <clear/>
    <add name="ContentMembershipProvider" type="N2.Security.ContentMembershipProvider, N2.Security" />
  </providers>
</membership>
<roleManager enabled="true" defaultProvider="ContentRoleProvider">
  <providers>
    <clear/>
    <add name="ContentRoleProvider" type="N2.Security.ContentRoleProvider, N2.Security" />
  </providers>
</roleManager>
<profile defaultProvider="ContentProfileProvider">
  <providers>
    <clear />
    <add name="ContentProfileProvider" type="N2.Security.ContentProfileProvider, N2.Security" />
  </providers>
</profile>

Changeset reference 55352

Then, I temporarily replace the WCHM authentication configuration with the out-the-box default N2 authentication section:

    <authentication mode="Forms">
      <forms loginUrl="edit/login.aspx" protection="All" timeout="30000" path="/">
<credentials passwordFormat="Clear">
  <!-- WARNING: Change this default password. Please do it now. -->
  <user name="admin" password="changeme"/>
</credentials>
      </forms>
    </authentication>

I can now load up my site, browse to the /edit url and be presented with the standard N2 login screen.

image

Logging in with the default admin user specified in config gets me into the N2 admin interface:

image

Now we’re getting somewhere! I can then use the N2 users screen to add a new user – I use the Open ID username as the N2 username, and fill the rest of the details in, but in fact, they’re not going to be used as we’re going to rely on Open ID to authenticate us. Once the user is added, we can see it in the N2 users table:

image

Now that we have our first real admin user, we can replace the authentication section in web.config with the original WCHM Open ID config:

<authentication mode="Forms">
    <forms defaultUrl="~/" loginUrl="~/user/login" timeout="2880" name="__wchm_auth"/>
</authentication>

We can now log in and out of N2 using the Open ID authentication. If I try to browse to the /edit url in order to get to the admin site, I will get presented with the WCHM Open ID authentication screen. Once authenticated using Open ID, I can then access the edit site fine as the authenticated username matches an admin user of the same username in N2.

Next…

So, we’re practically there – we have a working site and a working N2 admin site which we can access using our Open ID authenticated username. The Home page still loads normally and is in fact being routed via the N2 content route. The final steps are to build up the content definitions, and pass the content from N2 to the view…

Integrating N2CMS into Who Can Help Me? Part 2

Uncategorized 2 Comments »

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…

Integrating N2CMS into Who Can Help Me?

Uncategorized 5 Comments »

This is going to be the first of three or four posts around integrating the .Net open source content management system N2CMS into the Sharp Architecture demo application Who Can Help Me?. Who Can Help Me? was born out of our work on Fancy Dress Outfitters, which had N2CMS at its core, however we decided to leave the CMS piece out of WCHM as the application didn’t really need it and we didn’t want to overly complicate things. WCHM already has quite a lot going on – even though it’s a relatively simple application, the idea was to present an architecture reference for an application that would scale and potentially become a lot more complicated.

However, over the last few weeks, a number of people have asked about the approach we took with N2 and ASP.Net MVC on Fancy Dress Outfitters and how we would go about adding N2CMS to WCHM. So, after a brief discussion with the guys, we decided it would be beneficial to create a new branch of WCHM that is backed by N2CMS.

I want these posts to walk through the steps taken and changes made to the solution in order to integrate the CMS. I’m going to write the posts as I’m making the changes, so ideally I’ll be able to link to specific changesets that I check into the branch.

Step 1 – Adding the N2 assemblies

I already had a download of the N2CMS trunk code from here as of a week or two ago as I’m using it again on my current project. I built the solution using the Deploy_Everything-vs2008.bat script, after replacing the System.Data.SQLite.dll for a 64 bit version.

This creates the output folder and deploys the example, templates and edit sites with all the necessary references. I grabbed the necessary dlls and checked them into the ReferencedAssemblies folder under the N2CMS folder.

Changeset reference 54936

Step 2 – Add edit site to web project

I then grabbed the deployed edit site (which is the CMS admin interface) from under one of the template sites in the output folder and added it to a new Edit directory under the WhoCanHelpMe.Web MVC web application project. The edit site is a Web Forms project, however they sit nicely side by side together as when browsing to the .aspx pages of the Web Forms project, the MVC routing knows that it should ignore real existing file requests and so they just get processed as normal.

Another option is to split out the edit site and the public facing website into two separate web projects. This is a good idea as it allows you to deploy the two sites differently. For example, you could set up two separate websites in IIS:

http://www.yoursite.com

http://admin.yoursite.com

and create a virtual directory to the edit site in only the second site:

http://admin.yoursite.com/edit

which is routed to the edit site location. By IP restricting the admin.yoursite.com site in IIS, you are preventing unwanted users from even being able to browse to your edit site url, before the even get chance to log in.

However, this seemed like overkill for the WCHM demo, so I took the easy route and just dropped the edit site into the existing web project.

Changeset reference 54943

Step 3 – Creating initial content definitions

N2 requires a root node and a start node as an initial site skeleton. Each node must be of a particular content type, so we need to start by creating these type definitions in code so that we can set the root and start nodes in both the database and the N2 config section in web.config.

I created two definitions – a SiteRoot:

namespace WhoCanHelpMe.Domain.Cms.Pages
{
    using N2;
    using N2.Details;
    using N2.Installation;
    using N2.Integrity;

    /// <summary>
    /// The site root definition
    /// </summary>
    [PageDefinition("Site Root",
        Description = "A site root used to organize home pages.",
        SortOrder = 0,
        InstallerVisibility = InstallerHint.PreferredRootPage,
        IconUrl = "~/edit/img/ico/png/page_gear.png")]
    [WithEditableTitle("Title", 5, Focus = true, ContainerName = Tabs.Content)]
    [RestrictParents(AllowedTypes.None)]
    public class SiteRoot : AbstractPage
    {
        /// <summary>
        /// Gets site root Url.
        /// </summary>
        /// <value>
        /// The site root url.
        /// </value>
        public override string Url
        {
            get { return "~/"; }
        }
    }
}

And a HomePage:

namespace WhoCanHelpMe.Domain.Cms.Pages
{
    #region Using Directives

    using N2;
    using N2.Details;
    using N2.Installation;
    using N2.Integrity;
    using N2.Web.UI;

    #endregion

    /// <summary>
    /// The home page definition.
    /// </summary>
    [PageDefinition("Home Page",
        Description = "A home page template.",
        SortOrder = 440,
        InstallerVisibility = InstallerHint.PreferredRootPage | InstallerHint.PreferredStartPage,
        IconUrl = "~/edit/img/ico/png/page_world.png")]
    [WithEditableTitle("Title", 5, Focus = true, ContainerName = Tabs.Content)]
    [RestrictParents(typeof(SiteRoot))]
    public class HomePage : AbstractPage
    {
    }
}

Both these definitions inherit from a base AbstractPage:

namespace WhoCanHelpMe.Domain.Cms.Pages
{
    #region Using Directives

    using System.Collections.Generic;
    using System.Security.Principal;
    using N2;
    using N2.Collections;
    using N2.Web.UI;

    #endregion

    /// <summary>
    /// A base class for page items.
    /// </summary>
    [TabContainer(Tabs.Content, "Content", 0)]
    public abstract class AbstractPage : ContentItem, IItemContainer
    {
        #region IItemContainer Members

        /// <summary>
        /// Gets the item associated with the item container.
        /// </summary>
        public ContentItem CurrentItem
        {
            get { return this; }
        }

        #endregion

        /// <summary>
        /// Overridden for performance reasons
        /// to save N2 having to do role based
        /// authorization look ups for each page
        /// </summary>
        /// <param name="user">
        /// The current user.
        /// </param>
        /// <returns>
        /// Always true
        /// </returns>
        public override bool IsAuthorized(IPrincipal user)
        {
            return true;
        }

        /// <summary>
        /// Gets all children of the specified type
        /// of the current item
        /// </summary>
        /// <typeparam name="T">
        /// The type filter
        /// </typeparam>
        /// <returns>
        /// The list of children
        /// </returns>
        public virtual IList<T> GetChildren<T>() where T : ContentItem
        {
            return new ItemList<T>(
                Children,
                new TypeFilter(typeof(T)));
        }
    }
}

Changeset reference 54950

Step 4 – Create database schema

I then added the N2CMS database schema to the solution. As we’re already using the Visual Studio Team System Database Edition, I was able to create a database project easily by pointing the wizard at an existing installation of N2 that I already had. The database can then easily be created by right clicking the database project and selecting deploy.

Without the database project, you’d need to create the database schema manually by running the scripts that N2 provide as part of the download.

Once your database is created, remember to grant the correct permissions to be able to access it (This had me banging my head against the wall for a while!).

Changeset reference 54952

Next

That’s enough for now, we don’t have a working site just yet and there’s still a lot to do, but I want to break this up into manageable chunks and also am writing this as I go along. Next we need to add in our root nodes to the database, register our N2 routes and start to build out the controllers and content definitions….

Introducing Machine.Specifications.Mvc

Uncategorized 1 Comment »

Hot on the heels of my Machine.Specifications.AutoMocking framework, I am please to announce another extension to the awesome Machine.Specifications BDD testing framework.

Machine.Specifications.Mvc is a set of extensions for testing ASP.Net MVC specific types.

It aims to ease the testing of ActionResult objects returned from MVC Controllers by providing an MSpec BDD syntax over these types.

Here’s a list of its current features:

ActionResult extensions

ShouldBeAView()

e.g.

result.ShouldBeAView();

ShouldBeARedirectToRoute()

e.g.

result.ShouldBeARedirectToRoute();

ShouldBeARedirect()

e.g.

result.ShouldBeARedirect();

Each expose the And() chaining command, returning the strongly typed ActionResult.

e.g.

result.ShouldBeAView().And().ViewName.ShouldEqual(“NotFound”);

result.ShouldBeARedirect().And().Url.ShouldEqual(“http://someurl.com“);

result.ShouldBeARedirectToRoute().And().RouteName.ShouldEqual(“RouteName”);

ViewResult specific extensions

ShouldUseDefaultView()

e.g.

result.ShouldBeAView().And().ShouldUseDefaultView();

ShouldHaveModelOfType<T>()

e.g.

result.ShouldBeAView().And().ShouldHaveModelOfType<Person>();

ShouldHaveModelOfType<T>() also exposes the And() chaining command, returning the model strongly typed as T

e.g.

result.ShouldBeAView().And().ShouldHaveModelOfType<Person>().And().Id.ShouldEqual(1);

RedirectToRoute specific extensions

ControllerName()

e.g.

result.ShouldBeARedirectToRoute().And().ControllerName().ShouldEqual(“Person”);

ActionName()

e.g.

result.ShouldBeARedirectToRoute().And().ActionName().ShouldEqual(“List”);

Further ActionResult extensions

Model<T>()

Provides a shortcut straight to the ViewData.Model property of an ActionResult cast as a ViewResult

e.g.

result.Model<Person>().Name.ShouldEqual(“James Broome”);

ShouldRedirectToAction<TController>(Expression<Action<TController>> action)

Provides a strongly typed shortcut to check an ActionResult cast as a RedirectToRoute against Actions on Controllers

e.g.

result.ShouldRedirectToAction<HomeController>(x => x.Index());

Where can I get it?

Source code is available on github at http://github.com/jamesbroome/Machine.Specifications.Mvc/

Design by j david macor.com.Original WP Theme & Icons by N.Design Studio