Integrating N2CMS into Who Can Help Me? Part 3

Tags: , 10 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

Tags: , , 5 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?

Tags: , , 10 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

Tags: , , , , 5 Comments »

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/

Introducing Machine.Specifications.AutoMocking

Tags: , , , , , 7 Comments »

If you’re a reader of this blog, you’ll know that I’m a big fan of Behaviour Driven Development and have been for about the last two years. My interest in this style of development began when I attended JP Boodhoo’s Nothin But .Net training course “bootcamp” in Vancouver back in the spring of 2008, where I was first introduced to proper test-first development and the benefits it gave. Ever since then, I’ve been working with JP’s home-rolled BDD library, which started out as bdddoc and bddunit and has evolved over time into it’s current incarnation as the developwithpassion.bdd library. Two years ago, there were not many (if any) mature BDD frameworks out there (especially for .Net), so I stuck with JP’s framework throughout whilst delivering projects for EMC Consulting.

I have recommended the developwithpassion libraries to colleagues and used them in examples in blog subjects – most recently my ASP.Net MVC Controllers and BDD series, however, I’ve always struggled a bit with it’s lack of “productisation”. This is no fault of JP – he very kindly and generously makes the source code for this framework available on github, but it’s still very much his tool and evolves only as and when he needs it to in supporting his training and consulting activities.

It also has a few barriers to entry – lack of ReSharper support for test running may be fine if you’re using the command line all the time, however, for some this would be a must have before adopting properly (although there is a “hack” to get support for TestDriven.Net). Also, it’s difficult to set up – and a lack of clear instructions makes this even harder to get going, especially for people who are out of their comfort zone when it comes to Rake. Finally, it depends on old versions of Rhino.Mocks and MBUnit which makes integrating into new projects sometimes awkward if you want to use the latest and greatest. So, whilst I owe JP a lot, it was always at the back of my mind that lowering the barrier to entry and providing a few more IDE integration features would not go amiss if this framework was to continue to be my BDD tool of choice in the future.

I’d increasingly been hearing and reading good things about Machine.Specifications (MSpec) from colleagues and the .Net community and when I finally got round to checking it out, I was really pleased to find that the things I found lacking in developwithpassion were implemented magnificently in MSpec. Also, the syntax was practically identical to the syntax I’d already been using. The ReSharper support is great – both in the way it parses the specs in the Unit Test Sessions window and allows you to draft out your specs and have them show up as not implemented.

However, I’d become increasingly dependent on the richness of the developwithpassion library – especially in it’s support for creating mock objects, automatically registering dependencies and using these to build up the system under test (the subject in MSpec terminology) – a kind of Auto Mocking framework baked right into the library.

With MSpec being more of a product – with support from a wider community, an easy installation, great IDE integration etc. this seemed the logical way to go and I began to realise that adding this extra richness of functionality to MSpec would make a brilliant tool.

So, I decided to create Machine.Specifications.AutoMocking to add in the missing pieces that I’d been using from developwithpassion – a base specification class to use in MSpec specifications providing the following functionality:

  1. An easy mechanism for creating and registering mock instances of the dependencies that the system under test requires (using RhinoMocks mocking framework).
  2. A easy mechanism for creating mock objects for use in specifications (initially using RhinoMocks mocking framework).
  3. Automatically creating and exposing an instance of system under test, with all registered dependencies, based on the specified interface (optional) and subject type.
  4. ReSharper File Templates for specifications for subjects with and without a contract.

Example

The following example shows how a mock instance of INewsService can easily be created and registered for the NewsController. The base Specification class exposes the subject property, which is an instance of NewsController that has automatically been created using all the registered dependencies.

using Machine.Specifications;
using Machine.Specifications.AutoMocking.Rhino;
using Rhino.Mocks;

namespace Machine.Specifications.AutoMocking.Example.Rhino
{
    /// <summary>
    /// Example specification for a class without a contract that uses constructor based DI
    /// </summary>
    public abstract class context_for_news_controller : Specification<NewsController>
    {
        protected static INewsService newsService;

        Establish context = () =>
        {
            newsService = DependencyOf<INewsService>(); // DependencyOf creates and registers a mock instance of the dependency
        };
    }

    [Subject(typeof(NewsController))]
    public class when_the_news_controller_is_told_to_display_the_default_view : context_for_news_controller
    {
        static string result;

        Establish context = () => newsService.Stub(x => x.GetLatestHeadline()).Return("The latest headline");

        Because of = () => result = subject.Index(); // the subject has been created for us automatically, with all registered dependencies

        It should_ask_the_news_service_for_the_latest_headline =
            () => newsService.AssertWasCalled(x => x.GetLatestHeadline());

        It should_display_the_latest_headline = () => result.ShouldEqual("The latest headline");
    }
}

What next?

This is still very much work in progress, but so far I’ve added all the features I needed to re-factor my recent BDD-MVC controllers example to use Machine.Specifications and Machine.Specifications.AutoMocking. I’ve added the project code to GitHub so please feel free to get it and take a look – and start to use it with your MSpec specifications!

I plan to provide support for other mock frameworks (e.g. Moq).

I held off finishing off my MVC Controller series by posting the source code as promised, as I wanted to see if I could switch to using MSpec before I went ahead with this. Now that I know I can, expect this full example to come soon – using all the goodness I’ve talked about in Machine.Specfications.AutoMocking

Thanks

It goes without saying – but thanks to both Aaron Jensen and the Machine community and JP Boodhoo for building two great tools. All the real hard work had already been done by these guys.

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