Understanding Nancy - Sinatra for .Net

by Guido Tapia

in software-engineering,

March 22, 2011

For a project we are currently working on here at PicNet we decided to forgo the bloat of ASP.Net and Mvc and go for a super light weight web platform. We tried Kayak but this was a little too ‘bare’ so we then shifted our attention to Nancy which is a Sinatra clone (well, “inspired” project) in .Net.

We’ve now been working with Nancy for a few weeks and have thoroughly enjoyed the experience. One of the tougher parts of getting started with Nancy is the lack of documentation and tutorials, so I thought I would put what I’ve learned over the last week or so down on paper in the hopes that others may benefit.

This post is an introduction to the internal components of Nancy and how the nuts and bolts work (from a high level). In upcoming posts I hope to delve deeper into some of these areas and do a tutorial to bring all this into context.

Host Capturing http requests and sending responses back to the client are left to the ‘Host’. The host is really a web server and Nancy comes pre-bundled with a couple of options. You can use IIS, WCF and finally Nancy comes with its own “Self” implementation of the low level http hosting functionality. Both WCF and Self hosts can be used in windows services and in command line apps.

The host is also responsible for initialising and delegating request processing to the NancyEngine. Internally (and hence not really important uless developing Nancy) the host usually uses a NancyBootstrapper to initialise the engine. The bootstrapper is responsible for initializing all modules of the system, we will iterate through most of those models in this document.

Example: Setting up a WCF host with http and windows authentication binging:

WebHttpBinding whb =
  new WebHttpBinding(WebHttpSecurityMode.TransportCredentialOnly);
whb.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
var host = new WebServiceHost(new NancyWcfGenericService(), new Uri(serverUrl)); host.AddServiceEndpoint(typeof (NancyWcfGenericService), whb, ""); ServiceMetadataBehavior smb = new ServiceMetadataBehavior { HttpGetEnabled = true }; host.Description.Behaviors.Add(smb); host.Open();

Bootstrapper Internally, Nancy uses the bootstrapper to initialise all required modules and the DI container that will be used by other core modules. The default Nancy project comes with its own IoC implementation but if you are a fan of other IoC containers then Nancy comes pre-bundled with support for Ninject, StructureMap, Unity and Windsor.

NancyEngine The NancyEngine is the brains that the Host delegates requests to. The engine manages the request lifecycle. When the Engine receives a request from the Host, the engine:

  • Creates the request context
  • Invokes the pre-request hook (if specified). If the pre-request hook returns a Response then then no Routing is used as Nancy assumes the Request has been processed.
  • If the pre-request hook does not return a Response then Nancy looks for a valid Route to handle this Request. Routes are managed by Modules. (Note: Modules also have pre/post request hooks described below).
  • The post-request hook is then called with the current context.

If you are not writing your own bootstrapper I would leave the pre/post hooks alone and use the NancyModule’s Before/After pipeline support.

NancyContext The NancyContext has a reference to the Request, Response (once it has been resolved) and the Request or context Items. The context Items is just an in-memory dictionary ideal for storing Request wide data.

Request The Request is a pretty standard http request having a Body, Cookies, Session, Files, Form, Headers, the Method (get/post/etc), Protocol, Query and Uri details.

NancyModule The module(s) register the routes handled by the system. Each module can have a root path (module path) meaning that it is only responsible for that path branch.

Modules can also define pre request hooks by adding items to the Before member. post-request hooks are added using the After member. The rules for pre/post hooks are the same as for the NancyEngine hooks. Basically if a pre-request hook returns a response then processing of that Request ends and the Response is passed to the post-request hook.

The module also has access to the current context.

Example: Setting up simple routes in a custom NancyModule

public class MyModule : NancyModule
{
  public IEModule() {
      Get["/"] = x => GetMainWindow();
      Get["/resources/{name}"] = x =>Response.AsFile(x.name);
      Get["/dl/{filePath}"] = x =>
        GetDownloadResponse(Response.AsImage(filePath), filePath);
      Post["/open/{filePath}"] = x =>
        GetDownloadResponse(Response.AsImage(filePath), filePath);
      Post["/ul/{dirid}/{fileName}"] = x => UploadFile(x.dirid, x.fileName);
    }
  }
  private Response GetDownloadResponse(Response r, string fileName) {
    r.Headers.Add("Expires",
      DateTime.Now.AddSeconds(1).ToUniversalTime().ToString());
    r.Headers.Add("content-disposition",
      "attachment;filename=" + new FileInfo(filePath).Name);
    return r;
  }
...

Response Each request has to return a Response to the client. There are currently implicit casts in-place that allow you to return objects of the following types:

All of the non Response types are simply wrapped in a Response. The Response has the StatusCode, Headers, Cookies, ContentType and the Contents (as a stream).

The module also has access to helper methods that you can use to create Reponses. These are:

  • Reponse.AsFile
  • Reponse.AsCss
  • Reponse.AsImage
  • Reponse.AsJs
  • Reponse.AsJson
  • Reponse.AsRedirect
  • Reponse.AsXml

Views / ViewEngines Nancy comes pre-packed with support for NDjango, Razor and Spark. Of course you could also just server html.

Example: Delegating rendering of model data to the current View engine:

Get["/"] = x =>
{
  IEnumerable model =
    DB.BeerEvents.FindAllByEventDate(DateTime.Now.to(DateTime.Now.AddYears(1)));
  model = model.OrderBy(e=>e.EventDate).Take(10);
  // Current view engine will render this model
  return View["views/index",model.ToArray()];
};

Conclusion That is pretty much every important module in Nancy. Who would have thought that a fully functional web framework was conceptually so simple. Next bat time we will go through a step by step tutorial in getting started with Nancy.

Thanks

Guido Tapia Software Development Manager PicNet