Coding and Dismantling Stuff

Don't thank me, it's what I do.

About the author

Russell is a .Net developer based in Lancashire in the UK.  His day job is as a C# developer for the UK's largest online white-goods retailer, DRL Limited.

His weekend job entails alternately demolishing and constructing various bits of his home, much to the distress of his fiance Kelly, 3-year-old daughter Amelie, and menagerie of pets.

TextBox

  1. Fix dodgy keywords Google is scraping from my blog
  2. Complete migration of NHaml from Google Code to GitHub
  3. ReTelnet Mock Telnet Server à la Jetty
  4. Learn to use Git
  5. Complete beta release FHEMDotNet
  6. Publish FHEMDotNet on Google Code
  7. Learn NancyFX library
  8. Pull RussPAll/NHaml into NHaml/NHaml
  9. Open Source Blackberry Twitter app
  10. Other stuff

Adventures With NancyFx - NHaml ViewEngine Integration Part I

It's been a long time coming, but at last NHaml4 (the downright awesome .Net port of the equally awesome Haml view engine) is getting mature enough to start putting to use. Before I officially release NHaml4 onto the world, I want to make sure that the new engine's flexible enough for general use, so I'm looking at integration with both MVC.Net and (my favorite of the open source web platforms) NancyFx as "must haves" before the release.

NancyFx was actually the platform that initially sparked my interest in NHaml, I never seem to stop hearing praise for its codebase, so let's see how easy this integration's going to be.

NancyFx - First Impressions

I've Git cloned the source onto my machine, let's spin up Visual Studio and see what we've got. The first thing I notice is that there are 46 projects, that would normally feel like a lot, but they're well organized. The distributable assemblies are divided into projects as follows:

  • 1 core assembly - Nancy.dll
  • 2 authentication assemblies - Basic and Forms
  • 2 validation assemblies - DataAnnotations and FluentValidation
  • 4 hosting assemblies - Aspnet, Owin, Self and Wcf
  • 6 view engines - DotLiquid, NDjango, Nustache, Razor,Razor.BuildProviders and Spark
  • 1 testing assembly

The remaining assemblies include 16 test projects (these look one-to-one, which hints towards full TDD) and 15 sample projects. The sample projects are gold for my purposes - I can copy one of the existing ViewEngine sample projects, and use that along with some tests to drive out the NHaml integration.

Of course the code conventions are not what I'm used to (they're not a million miles away), I've not worked with xUnit before, and it's certainly been a while since I've seen so many XML comments (I prescribe quite closely to Uncle Bob's position on comments, though I see a value in open source APIs). But I'm sure I can work my around well enough.

Step 1 - Did I Say 46 projects? I Meant 49...

Let's make a start on the new code. Looking at the existing solution setup, the first thing I need to do is create three new projects:

  1. Nancy.Demo.NHamlViewEngine - An empty MVC Web Application
  2. Nancy.ViewEngines.NHaml.Tests - A xUnit based test library
  3. Nancy.ViewEngines.NHaml - A standard C# class library

I'm going to code through these classes in this order, so let's start with the demo web application.

Step 2 - Write The Integration Test

To give some direction to what I'm trying to do, I usually start with an acceptance test - some externally testable, clearly defined target. In this case, the demo projects are ideal to start with. So let's first create an NHaml demo project, using the "ASP.Net Empty Web Application" template in Visual Studio. This will give us a project with just a plain web.config file and nothing else, I'll replace the contents of this web.config with the following:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpHandlers>
      <add verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="*"/>
    </httpHandlers>
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
      <add name="Nancy" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="*"/>
    </handlers>
  </system.webServer>
</configuration>

Spin this up in a web browser, and not surprisingly you get an error, "Could not load type 'Nancy.Hosting.Aspnet.NancyHttpRequestHandler'". To fix this, let's add the following three project references:

  • Nancy
  • Nancy.Hosting.Aspnet
  • Nancy.ViewEngines.NHaml (I've already created a plain class library for this)

Rebuild your project, refresh your browser, and you should now be looking at a directory listing. Unimpressive stuff - let's get Nancy in there by adding the following MainModule.cs file to your web project in the root:

namespace Nancy.Demo.NHamlViewEngine
{
    public class MainModule : NancyModule
    {
        public MainModule()
        {
            Get["/"] = parameters =>
                           {
                               return View["Index"];
                           };            
        }
    }
}

Again, rebuild your project, refresh your browser, and this time you should be looking at a pretty awesome 500 error page - internal server error. Click the Details link, and you'll see the engine's unable to find our Index view. So let's add that index view:

  1. Create a folder in your web project called "Views"
  2. Add a new view, I'll be using my new view engine (which doesn't existing in Nancy yet remember), so I'll call my view "index.haml"
  3. I'll put a bare bones "Hello world" html document in my view

We've created our Nancy web app, we've referenced the Nancy request handler to invoke the Nancy engine, we've created a plain module referencing an Index view and created a matching view, but we've now hit a brick wall - we're missing a view engine. If we rebuild our app and refresh our page, we'll still get that same error message. So let's get started on our new view engine.

Step 3 - TDD The ViewEngine

A quick sniff around some of the other view enginea suggests that the crux of a Nancy view engine is a class implementing the IViewEngine interface. So in our test project, we'll drive this out. Create a new .cs file called "NHamlViewEngineFixture" and enter the following code (note this isn't the purest TDD - I've skipped the test to make me create the empty NHamlViewEngine class):

namespace Nancy.ViewEngines.NHaml.Tests
{
    using Nancy.ViewEngines.NHaml;
    using Nancy.Tests;
    using Xunit;

    public class NHamlViewEngineFixture
    {
        private readonly NHamlViewEngine engine;

        public NHamlViewEngineFixture()
        {
            engine = new NHamlViewEngine();
        }

        [Fact]
        public void Engine_Should_Implement_IViewEngine()
        {
            engine.ShouldBeOfType<IViewEngine>();
        }
} }

Of course this test isn't compiling yet, because we don't have a NHamlViewEngineFixture class. I'll use ReSharper to create this class and move it to my Nancy.ViewEngines.NHaml project. Now the test runs, and it fails. So we now need to make our NHamlViewEngine class implement the IViewEngine interface, so that we have something like:

namespace Nancy.ViewEngines.NHaml.Tests
{
    using System;
    using System.Collections.Generic;

    public class NHamlViewEngine : IViewEngine
    {
        public IEnumerable<string> Extensions
        {
            get { throw new NotImplementedException(); }
        }

        public void Initialize(ViewEngineStartupContext viewEngineStartupContext)
        {
            throw new NotImplementedException();
        }

        public Response RenderView(ViewLocationResult viewLocationResult, dynamic model, IRenderContext renderContext)
        {
            throw new NotImplementedException();
        }
    }
}

This should allow our new test to pass. Now that we have a bare bones non-implemented view engine, what effect has this had on our site? Let's do a rebuild, refresh the page, and see.

Step 4 - Tell Nancy What Views Your ViewEngine Serves

Aha - when I rebuild and refresh my page, my error's changed. This time I don't get a lovely Nancy error page, I get a grubby Asp.Net error page, "public IEnumerable<string> Extensions - System.NotImplementedException". I'm going to take a guess here that the IOC container in Nancy has seen our new ViewEngine assembly, and given it to the Nancy framework to wire it in. The framework is doing this using the Extensions property, so that it can figure out what files this new ViewEngine is responsible for. This doesn't mean that our Demo site is actually using this ViewEngine yet, just that Nancy's trying to add that view engine to the list of what's available.

So let's TDD out our Extensions property. In our NHamlViewEngineFixture class, let's add the following test

[Fact]
public void Extensions_should_return_nhaml()
{
    var viewEngine = this.engine.Extensions;
    viewEngine.ShouldEqualSequence(new[] { "haml" });
}

Run the test, watch it fail, then we'll add the following implementation code into our NHamlViewEngine class:

public IEnumerable<string> Extensions
{
    get { yield return "haml"; }
}

Interestingly for me, this was the first time I'd used the "yield" keyword in the wild. I'll not pretend to fully understand how it works, but a lot of the other Nancy view engines use this keyword to return a single string in place of an IEnumerable. Our test passes, so let's recompile the demo project and refresh our web page,

Step 5 - Initialize Your ViewEngine

Yuck - another Asp.Net error page. Apparently we now need to initialize our view engine. What do we need to do here? Let's take a look at some of the other view engines for inspiration.

  • Spark - When the Spark view engine is created, in the constructor it news up an instance of the Spark engine. This would be the point where in NHaml parlance we'd do an "XmlConfigurator.Configure()" call. The Spark view engine initializer then has the job of giving the Spark engine an implementation of it's view fetching mechanism (used for partials and master pages I'm guessing).
  • DotLiquid - A very similar story, the DotLiquid initialize method gives the DotLiquid engine a class that allows retrieval of view source files.
  • Razor - The Razor view engine has no initialize code - it's just empty.

So what do we do for NHaml? I've not even referenced the real NHaml view engine yet, and to be honest I want to do all that work in a separate session, so I'm going to go the lazy route and give NHamlViewEngine an empty initialize with a big fat TODO comment. But I suspect as soon as I've got the real NHaml view engine in here, I'll be creating an instance of ITemplateContentProvider so that NHaml can dig out partials and master pages.

Let's try another build and a refresh - I want to see Nancy dig into my view engine RenderView method with another "NotImplementedException", so that I know everything's wired in properly.

But wait... what's this, an empty page? Now that I didn't expect! I've got a "throw new NotImplementedException" left in my NHamlViewEngine.RenderView method, where's my error page! A bit of digging later, it turns out that if a view engine throws an exception, Nancy catches it and instead returns an empty string, no doubt for security reasons. So I don't get my exception, but at least having traced through the code in a debugger, I can see it firing.

That's it, that's the new view engine wired up and ready to be implemented. More on that in the next installment.


Permalink | Comments (1)

Comments (1) -

TheCodeJunkie Sweden

30 April 2012 21:34

TheCodeJunkie

Nice write up! One comment though. Do not confuse code comments with API comments. Code comments are comments that are littered around the code it self to describe complex / unreadable code. API comments (in this case XML comments) describe the API for the consumer, more specifically through intellisense.

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading