This post is over 6 months old. Some details, especially technical, may have changed.

A Nancy Module that Behaves like a Rails Controller... Mother of God

If Nancy is inspired by Sinatra I thought I'd have a little fun a create an abstract class that created modules that kind of mimic the behaviour of Rails controllers. Code first, ask questions later...

public abstract class RailslikeControllerFor<TEntity> : NancyModule where TEntity : class
    public RailslikeControllerFor() : base("/" + typeof(TEntity).Name + "s")
        Get[@"/"] = Index;
        Get[@"/new"] = New;
        Post[@"/"] = Create;
        Get[@"/(?<id>[\d])"] = Show;
        Get[@"/(?<id>[\d])/edit"] = Edit;
        Put[@"/(?<id>[\d])"] = Update;
        Delete[@"/(?<id>[\d])"] = Destroy;

    protected virtual Nancy.Response Index(dynamic context) { return 404; }
    protected virtual Nancy.Response New(dynamic context) { return 404; }
    protected virtual Nancy.Response Create(dynamic context) { return 404; }
    protected virtual Nancy.Response Show(dynamic context) { return 404; }
    protected virtual Nancy.Response Edit(dynamic context) { return 404; }
    protected virtual Nancy.Response Update(dynamic context) { return 404; }
    protected virtual Nancy.Response Destroy(dynamic context) { return 404; }

    protected Nancy.Response AsView(object model = null)
        string method = new StackTrace().GetFrame(1).GetMethod().Name;
        string entity = typeof(TEntity).Name;

        return View[string.Format("{0}/{1}", entity, method), model];

To use it you just subclass the class and override the necessary methods. Like so.

public class QuestionModule : RailslikeControllerFor<Question>
    IQuestionsRepository questions;

    public QuestionModule(IQuestionsRepository questions)
        : base() { this.questions = questions; }

    protected override Response Index(dynamic context)
        return AsView(questions.GetAll());

What the base class does is create the necessary CRUDdy urls with a base route starting with the name of the entity you pass in, pluralised in the easiest way possible. In this case /questions. The rules match what you wold get for a controller in Rails.

Verb Path Action
GET /questions index display a list of all questions
GET /questions/new new return an HTML form for creating a new question
POST /questions create create a new question
GET /questions/:id show display a specific question
GET /questions/:id/edit edit return an HTML form for editing a question
PUT /questions/:id update update a specific question
DELETE /questions/:id destroy delete a specific question

There is a teeny-tiny bit of sugar in the AsView method that will look up the corresponding view. So the module above would resolve its Index method to questions/index and the view engine would find the correct file (eg. for Razor - questions/index.cshtml).

I've already said it's a bit of fun but I have actually used this at least once - there is probably more that could be added but it'll do for a first spike at least. Any use to anyone?

Published in .NET on December 30, 2011