28 Nov
2010

Introducing Nancy, a lightweight web framework inspired by Sinatra

Category:UncategorizedTag: , , , :

For a couple of weeks I have been keeping myself busy with open-source stuff. One of the things has been to spike out a web framework idea and later on turn it into a real project. The project is inspired, but not a clone, by the Sinatra web framework built on Ruby. The name, Nancy, is a reference to Nancy Sinatra, the daughter of Frank Sinatra.

There are quite of lot of things that I want to put into the framework, but it is functional in its current state. One of the goals for Nancy is to make it run on other environment and platforms, other than ASP.NET / IIS and there are spikes taking place to run it on Mono with FastCGI, making it possible to run on a bunch of other platforms. However, although this is the goal, the current source code does not provide any helpers to make that possible. Right now it only ships with an IHttpHandler that acts as an adaptor between ASP.NET / IIS and the Nancy engine.

The project is built using C# and makes use of xUnit, MSpec and FakeItEasy

The key component in a Nancy application is the modules. This is where you create actions, which are handlers for a given request type at a given path. Let me show you what I mean

public class Module : NancyModule
{
    public Module()
    {
        Get["/"] = parameters => {
            return "This is the site route";
        };

        Delete["/product/{id}"] = parameters => {
            return string.Concat("You requested that the following product should be deleted: ", parameters.id);
        };
    }
}

What you are looking at here is the foundation of a very small application that will responde to GET requests to the root URL of the site, and DELETE requests to /products/{id} where {id} is a parameter placeholder. All parameters will be captured and injected into the action, like you see with parameters.id.The entire route handling mechanism is swappable, so you could write your own handler that were able to interpreted the route syntax that you prefer. A module can also be declared with a module path, meaning that all action routes, that you declare in the module, will be relative the module path.

For example if you were to do

public class Module : NancyModule
{
    public Module() : base("/foo")
    {
        Get["/"] = parameters => {
            return "This is the site route";
        };

        Delete["/product/{id}"] = parameters => {
            return string.Concat("You requested that the following product should be deleted: ", parameters.id);
        };
    }
}

The the application would respond to requests sent to /foo and /foo/product/{id}. You can of course have as many modules as you want. Nancy will detect them all and figure out which action that should be invoked. There are also some nice ticks in there for return values. In the examples above you get the impression that you are expected to return a string, and this is not the case. In fact each action returns an instance of a Response type. What you are seeing is the result of some implicit cast operators. There are a couple of them declared

public class Module : NancyModule
{
    public Module()
    {
        Get["/"] = parameters => {
            return "Returning a string";
        };

        Get["/404"] = parameters => {
            return 404;
        };

        Get["/500"] = parameters => {
            return HttpStatusCode.NotFound;
        };
    }
}

All of these will work and send back a valid HttpResponse (including headers) to the client. You can of course explicitly return a Response instance which opens up for some nice customization. A module in Nancy also declares a pair of properties called View and Response. Both of these have an interface return type and each of them are empty marker interfaces that you can use to wire up extension methods. The View property is meant to be used for view engine integration and in an unpublished spike (still needs some more work) I’ve wired up the http://www.sparkviewengine.com/ so that Nancy is able to process spark files. This is an example of what that looks like

public class SparkModule : NancyModule
{
    public SparkModule()
    {
        Get["/user/{name}"] = parameters => {
            return View.Spark("user.spark", parameters);
        };
    }
}

Of course all of this is work in progress and the syntax might change. The goal is to support all of the popular view engines and if you are up to the task of implementing support for one of those, please let me know – I would love the help!

The Response property is meant to be used for extensions that help format the response in different ways. A test I have running locally is an extension method that enables me to return json formatted data.

public class JsonModule : NancyModule
{
    public JsonModule()
    {
        Get["/user/{name}"] = parameters => {
            return Response.AsJson(someObject);
        };
    }
}

Of course I would like for Nancy to ship with a healthy set of these response helpers, so feel free to chip in!

Oh, there is one last property that you can use and that is the Request property which gives you access to the current request information, so that you can use it from inside of your action handlers. Right now it is limited to the requested path and verb, but the goal is to have a rich representation of the current request – what stuff would you like to see in it?

public class RequestModule : NancyModule
{
    public RequestModule()
    {
        Get["/"] = parameters => {
            return string.Format("You requested {0}", Request.Path);
        };
    }
}

One thing I would like to mention about the action handlers and their routes. If there are two or more routes that are a match to the current request, Nancy is going to select the one that has the most matching static path segments before a parameter placeholder is reached (but all segment has to be filled!). What does this mean? Take the following routes

/foo/{value}
/foo/bar/{value}

The first route has one static path segment (/foo) and the second one has two (/foo/bar). So for a request to

/foo/bar

The first route will be selected, but for

/foo/bar/baz

the second route will be selected. It also important to understand that in Nancy, all path parameters are greedy, not like in ASP.NET MVC where you can have one greedy (indicated by a star *) and has to be at the end. If you define a route

/foo/{value1}/bar/{value2}

you can invoke it with

/foo/this/is/some/segments/bar/and/then/some/more

and you will end up with

{value1} = /this/is/some/segments
{value2} = /and/then/some/more

Of course, like I said before, this is how the default route handler works and if you don’t like it you can write your own, all you have to do is implement a single interface and tell Nancy to use it.

So before I end this post, let me tell you about some of the things that are planned to be included in Nancy as soon as possible

  • A much richer request object. Nancy uses it’s own Request object and is not tied down the the one found in ASP.NET. I want to support a rich and easy to use model for request information. If you have any suggestions on the structure of this object, please let me know
  • The ability to inject dependencies into Nancy modules. I want you to be able to wire up Nancy to use your favorite IoC and have Nancy resolve constructor dependencies of Nancy modules
  • Conditions on actions. I want to add an optional predicate on actions like Get[“/foo”, () => somePredicate], to give Nancy the ability to select actionsat runtime. For example you might have two identical actions define, but you add a predicate on one of them that made sure that it was only selected if the client was a mobile device. Actions that has a predicate defines should have higher priority than those that do not
  • View engine integration. Like I said, the Nancy modules comes with the View property, which is of the type IViewEngine, where you can hook up view engine support. All you need is an adapter that returns a string (or a Response instance). Please let me know if you want to chip in and help wire up one or more view engines
  • Ship with a nice bunch of response formatters. These are created by attaching extension methods to the IResponseFormatter interface, which is the property type of the Response property on a Nancy module. I think the formatters should follow a naming convention where you name them As<something>
  • Self-composed framework. What is mean with this is that I want to build Nancy on top of a tiny, internal, IoC that is used to compose the framework at runtime. It should be exposed in a simple way so that you could swap out components (such as the route matcher, or module discovery mechanism) as you please
  • Request and Response interception. The idea is to provide a lightweight way to intercept Nancy requests before they hit the Nancy application and let you either pass the request to the Nancy application or prematurely send back a reply. Combines with the ability to intercept Responses sent by the Nancy application, it should give a nice way of extending Nancy with features like logging and caching. You can think of this as a sort of IHttpModule mechanism
  • NuGet presence
  • Command line (powershell?) support for spawning up a Nancy application project
  • Provide self-hosting somehow

There are a bunch of other stuff I have in my head, but I have to give them some more thought to distil proper ideas from them. But please, let me know if you can think of anything more! I want to keep Nancy lightweight and easy to use, so it will probably never be as open-ended as ASP.NET MVC, FubuMVC or Manos de Mano – but we’ll have to wait and see!

You can find the source code at my Nancy repository at GitHub. You can also reach me on Twitter at @TheCodeJunkie. If you want to talk about Nancy drop me a line in the comments or on Twitter and we can move onto e-mail, gtalk, skype or messenger if needed! I hope you like where Nancy is going!

35 thoughts on “Introducing Nancy, a lightweight web framework inspired by Sinatra

  1. A quick look through the code (really just the NancyModule.cs) I would like to see support for a Server variable, and eventually support for Session/Cookies/User/etc – though I understand the effort for that is a bit high, and probably better spent on some of your already planned features.

    All in all, this looks really cool – it reminds me a bit of the “Manos de Mono” project as well. I am really glad there are people like me that love the C# language but aren’t happy with the frameworks available for generating web content. Kudos!

  2. Congratulations on the ability and creativity to even get this far.

    One thing I don’t see is reasons (a “sales” pitch) why I (or anyone else) may want to use this?

    So who is your target audience? What kind of projects do you envision this being ideal for?

    Thanks

    Nick

  3. @Nick,

    The project is the result of a discussion with a friend were we had the same ideas on how stuff should work. In regards to a target audience… well I haven’t really profiled it yet, too early to say. That said, I have two full web based applications that I plan in trying to build on top of Nancy

    I really want to dogfood it so that ideas that the things that I put into Nancy have been extracted from real business needs.

  4. Your idea of being able to intercept Request / Response objects sounds a bit like Rack. With the DLR and iis7 could you make this framework “rackable”?

  5. Can you explain why someone would want to use this?

    At the moment, I don’t quite “get” it. (I’m probably at a disadvantage, cause I’m not familiar with Sinatra.) Is this intended to replace ASP.Net’s routing module, or provide an alternate “engine” from ASP.Net and ASP.Net-MVC for building apps?

  6. This looks more like an alternative to OpenRasta in the .Net space. What are the advantages of Nancy over OR would you say?

  7. Looks great so far. I have used Sinatra before and I can see where you were going with this project. Sinatra brings power in simplicity and most .NET developers don’t see value in that. I think the target market will be small to start with because of that, but still worth it. I look forward to seeing how this turns out!

  8. Don’t feel offended dude, but you have just reinvented the wheel.
    Maybe the project was good for your ego or to upgrade your skills but is overall… useless. We’ve got monorail, we’ve got asp.net mvc. Both are extendable, both support IoC. Both are far more mature.

  9. @Alex

    “Reinvented the wheel” is a phrase that should be eliminated. Wheels aren’t reinvented, they are re-engineered. A wheel that works on a Camry will not work on a Porsche, they have different requirements.

    So, if there is a valid engineering niche that this project fills (and you could only answer that question if you are familiar with Sinatra), then this “wheel” is fine.

  10. Looks great! The syntax looks as concise as you’ll probably be able to get it for c# right now. The very first thing I tried to do is grab it as a NuGet package.

    1 for NuGet presence Command line (powershell?) support for spawning up a Nancy application project. That would be awesome.
    Thanks for putting this together!!

  11. Can you give an example of why someone would decide to use Nancy instead of one of the existing .NET-frienly web servers? Is Nancy going to be faster or more secure or something? What’s a good use case for why one would consider Nancy?

  12. @Alex Gotta say man, that is a pretty poor stance to take on something like this. The frameworks you mentioned, Monorail and ASP.NET MVC both fit very different spaces than Sinatra does for the Ruby world as they are more like Rails. This framework looks to fit that same lightweight niche for the .NET world…I totally applaud the developers efforts here. I think a big part of the problem with the .NET community is exactly the attitude that you displayed in your comment. Doubly sad that it was the first comment on the post 🙁

  13. Hey great work, seems promising! hope to see the community getting involved into this great project. I’ll be following it and helping whenever I can!

  14. @Alex
    “Don’t feel offended Matz, but you have just reinvented the wheel.
    Maybe the project was good for your ego or to upgrade your skills but is overall… useless. We’ve got Perl, we’ve got Python. Both are dynamic, both support OOP. Both are far more mature.”

    “Don’t feel offended Linus, but you have just reinvented the wheel.
    Maybe the project was good for your ego or to upgrade your skills but is overall… useless. We’ve got Windows, we’ve got OS/2. Both work on IBM computers, both are graphical. Both are far more mature.”

    Don’t feel offended Alex, but you’re an idiot.

  15. It’s a shame C# doesn’t have inline regexp literals like javascript / ruby / python or you could have had more elegant syntax in your routing. As a former C# programmer gone rogue on node.js, I can appreciate the work you’ve done.

    Best of luck with the project.

  16. @Eoin Coffey,

    There have certainly been talks about “Rack” like capabilities when discussing this with people close to me. Also, after the announcement there were discussions about the same thing on twitter. I think we are looking are some pretty interesting times a head for .net web frameworks. I especially like that stuff like the Moncai and AppHarbor hosting services (basically Heroku for .net) are emerging.

  17. @Remi Despres-Smyth well my personal reasons are that it’s very light weight and opinionated. There is also a shift in mindset when you are thinking about how you design your app because you are putting stuff out there are different paths and request types (ReST). Right now I can build a very simple web app using Nancy and a couple of lines of code.

  18. @Dokie I honestly couldn’t say since I’ve never used or seen OpenRasta (I know, strange isn’t it!). That said Sebastian and I are going to talk a bit over the next couple of days about some stuff he is working on.

  19. @Kevin Radcliffe NuGet is a high priority! There are a couple of things I want to get into the core before though. I would love to take contributions for a console utility for spawning up Nancy applications! Fork, Commit, Pull Request! 😉

  20. @Charlie Robbins Actually I don’t see why I couldn’t. Right now Nancy is aware of a IRouteResolver interface, it’s responsible to taking a request object and an enumerable of RouteDescription (more or less a DTO for actions that fit the requested http verb) and from there it figures out which action is a match.

    It’s completely decoupled from rest of Nancy. The current implementation is what interpreted the route syntax I blogged about, including the parameters and turning them into a dynamic object which will then be injected into the lambda when it’s invoked

    So it should be possible to update the current implementation / write a new one that could take something like

    /foo/{[0-9]{0,3}[A-Z] }

    and figure out that the second segment is a regex and use that in the matching. Definitely see this being possible. I wouldn’t call it a top priority right now, but if you feel like spiking one out then I would be more than happy to talk about the implementation!

  21. @Brett I really don’t have a sales gimmick for Nancy. Right now we are looking at less than 15 hours of coding for an idea that grew into what we see now. However it’s going to be a personal preference when it comes down to it.

    Too me frameworks like ASP.NET MVC are really great but sometimes a bit too bulky and too much ceremony to get you going. I also like the Nancy syntax for defining actions, it’s a lot more tears than putting your routes in one place, your views in one place, slapping on attributes to control the behavior of said actions in said controllers etc.

    The Hello World Nancy app could be done in 5 lines of code (if you don’t could using-statements and namespace declaration) or so.

    I see this as being a very good framework to lean against when you are building rich clients that talk json, use stuff like client side templates and so on.

    That said, since there is going to be view engine support I see no reason you cannot build much richer server applications also.

    One funny fact is that Nancy is very decoupled from ASP.NET/IIS so in theory you could host Nancy in pretty much any application type you wanted (console, winform etc) and have routes represent what ever you want

  22. @JC Grubbs, common, man, what a hell do u mean by “lightweight niche” – hello world ? ) Or do u really think that asp.net mvc or monorail are huge and enterprise and it takes month to build a simple app ?

    Andreas says – “The Hello World Nancy app could be done in 5 lines of code (if you don’t could using-statements and namespace declaration) or so”.
    U can do it in asp.net mvc with 1 line of code and this is true, you can go and check yourself.

    Regarding community attitude, community is not your Mom to support you when u do something stupid. We are here also to point to mistakes.
    It just proves how poor are your knowledge about .net world if you think it needs Nancy.

    @ Mark Rendle, so what ? Ruby is a clone of Python with a slightly better syntax. Do u see much difference? There is nothing innovative about Ruby, it’s Rails and only Rails that made it popular.

    Linux was a free alternative for Unix, referring to OS/2 seems weird to me. And innovation about Linux was not the OS itself but the open-sourced code and community driven development.

    Regarding your last statement – “Stupid is as stupid does”.

    To sum up, I would just kindly advice Andreas to invest his time and enthusiasm into something better, cause this Nancy was definitely born dead.

  23. @Alex comments duly noted. However I do feel I can spend my time as I please and publish whatever code I want. If this is not something you want to read about or use – simply don’t.

    I hope you are a contributor to the open-source community around stuff like Monorail, Fubu and ASP.NET MVC Contrib because that’s where *you* think the effort should be put. This is where I put my efforts, thank you.

    I have never gone into this with the assumption that its going to be the defacto standard. This is my work, I enjoy it.. if anyone else would like to use it, despite your reservations, then they are free to do so.

  24. The project is the result of a discussion with a friend were we had the same ideas on how stuff should work. In regards to a target audience… well I haven’t really profiled it yet, too early to say. That said, I have two full web based applications that I plan in trying to build on top of NancyI really want to dogfood it so that ideas that the things that I put into Nancy have been extracted from real business needs.

Comments are closed.