April 5, 2013

Nancy vs ASP.NET MVC - Static content

In this post I cover how to serve static content such as stylesheets and scripts with Nancy and ASP.NET MVC.

It may seem odd to blog about something so trivial. Surely the use of static assets is as simple as dropping them into your web site directory? Unfortunately this is not the case with Nancy - there are some caveats to using a framework that is not tied to a specific hosting platform.

The source code for this series can be found on Github.

Since I didn’t want to start something from scratch I went over to Initializr and downloaded the responsive template (built on top of HTML5 boilerplate). The download included stylesheets, scripts, an index page, a custom 404 page, favicon and touch icons:

Initiaizr Files

ASP.NET MVC

I copied the files from the download into the root of my project and arranged them into my preferred structure - I like to have all my assets under an /assets directory e.g. /assets/css and /assets/js.

Then I just copied the HTML code from the index.html file provided into the Index.cshtml view I created in the previous post, remembering to add the welcome message back in.

Referencing the assets is simple and as of MVC 4, we can use a virtual path that will automatically be expanded when processed by the Razor View Engine. For example:

<link rel="stylesheet" href="~/assets/css/normalize.min.css">
<link rel="stylesheet" href="~/assets/css/main.css">

<script src="~/assets/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js"></script>

Note that this is a feature of the Razor View Engine, not MVC, so you can also benefit from this if using the Razor View Engine with Nancy.

The only thing left to do is to wire up the custom 404 page that was provided by the template. To do this we just add the following to web.config:

 <system.web>
   ...
   <customErrors mode="RemoteOnly">
     <error statusCode="404" redirect="~/404.html"/>
   </customErrors>
 </system.web>

If you want to use your custom error page when returning a HttpNotFound status code result from your controllers you should also add the following to the <System.WebServer> section:

  <system.webServer>
    ...
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/404.html" responseMode="ExecuteURL"/>
    </httpErrors>
  </system.webServer> 

Now if we hit F5 we get our newly styled MVC web site:

Running MVC Site

Nancy

Getting static content to work with Nancy requires a bit more effort and often catches out those developers new to the framework. That’s why they have a blog post explaining how to handle static content on the Nancy Wiki.

I must admit, I found jumping through these hoops a little frustrating. However, it is important to remain objective when comparing the two frameworks. As I mentioned at the beginning of this article Nancy is not tied to any one hosting environment and with this flexibility comes a few caveats. Not all hosting environments can serve static files which is why static files are handled by Nancy; So long story short - you need to tell Nancy where your static files are located.

This is done using conventions and you can add your own by adding a bootstrapper and overriding ConfigureConventions:

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override void ConfigureConventions(NancyConventions nancyConventions)
    {
        base.ConfigureConventions(nancyConventions);

        nancyConventions.StaticContentsConventions.Add(
            StaticContentConventionBuilder.AddDirectory("/assets")
        );
    }
}

Here I’m telling Nancy that the contents of the /assets directory are static files.

While Nancy will serve your custom favicon.ico automatically, it will not do the same with any other static files that sit in the root of your site - things like robots.txt and the various touch icon variations included in the template.

Since Nancy doesn’t like it if you map the root directory for static content (for security reasons I think) I created a new directory “_root” and moved all my root static files to there. I then set up the following convention:

 nancyConventions.StaticContentsConventions.Add(
     StaticContentConventionBuilder.AddDirectory("/", "/_root", new[] { ".png", ".txt" })
 );

This is actually quite a neat feature of Nancy since the location from which you serve your assets does not have to match their location on disk. This can keep your root directory nice and clean.

With my static conventions registered I updated the Home.html view I created in the previous post as per the Index.html from the template, again remembering to add the welcome message back in.

If we were using the Razor View Engine we could reference our static assets as described above for ASP.NET MVC. Since we’re using the Super Simple View Engine (the default engine included with Nancy) we need to instead use the Path helper:

  <link rel="stylesheet" href="@Path['~/assets/css/normalize.min.css']">
  <link rel="stylesheet" href="@Path['~/assets/css/main.css']">

  <script src="@Path['~/assets/js/lib/modernizr-2.6.2-respond-1.1.0.min.js']"></script>

The final thing to do is set up our custom 404 page. Doing this isn’t quite as straightforward as making the changes to web.config (remember we’re not tied to a specific host). Instead we can create a custom IStatusCodeHandler which as the name suggests is a handler for a specific status code:

public class Custom404Handler : IStatusCodeHandler
{
    private readonly IViewFactory viewFactory;

    public Custom404Handler(IViewFactory viewFactory)
    {
        if (viewFactory == null)
        {
            throw new ArgumentNullException("viewFactory");
        }

        this.viewFactory = viewFactory;
    }

    public void Handle(HttpStatusCode statusCode, NancyContext context)
    {
        var viewRenderer = new DefaultViewRenderer(viewFactory);
        var response = viewRenderer.RenderView(context, "404");
        context.Response = response;
        context.Response.StatusCode = HttpStatusCode.NotFound;
    }

    public bool HandlesStatusCode(HttpStatusCode statusCode, NancyContext context)
    {
        return statusCode == HttpStatusCode.NotFound
            && context.Request.Headers.Accept.Any(x => x.Item1 == "text/html");
    }
}

This handler will override the response whenever the status code is 404 (Not Found) and the client is requesting HTML. Here we override the response returning the 404.html view from the root of the site.

Hitting F5 we see the newly styled Nancy site:

Running Nancy Site

Conclusion

It’s pretty clear that configuring Nancy to serve static content requires a bit more effort. However this is just one of the compromises you have to make if you want a framework that has much richer hosting capabilities.

If you were to conclude that ASP.NET MVC is easier to get up and running, at least for a new developer, you’d probably be right; But if you stay tuned to the rest of the series you’ll soon find that it is the more complex tasks; things like dependency injection, creating themeable view engines or setting up session-per-request that Nancy takes in it’s stride.

Please feel free to comment with thoughts/feedback below.

Update

As per Phillip’s comment below, it is possible to let IIS handle the files directly which (assuming you don’t need to support any other hosting environment) is much faster. To do this I could have added the following to web config:

<location path="assets">
  <system.webServer>
    <handlers>
      <remove name="Nancy"/>
    </handlers>
  </system.webServer>
</location>

© 2022 Ben Foster