Monday, October 10, 2011

Frictionless .NET Web App Development with Nancy


This post is about a low ceremony approach to web apps in .NET. It's about just writting the app, without the framework getting in the way. It's about the same sort of feeling that Mogens Heller Grabe talks about with his notion of frictionless persistence with MongoDB. Only here it's about the web part.

I'll take you through a short and simple URL shortener sample app done with the Nancy web framework. A couple of more posts will probably follow on the subjects of view engines, persistence and hosting for this app. Also with an eye on low ceromony and frictionsless development.

What is Nancy? 
Nancy is a lightweight open source web framework in .NET. It has a declared goal of being the developers super-duper-happy-path. Nancy is lightweight in the sense that the number of concepts and the amount syntax you have to grok is minimal. That's why I like it, and that's why it provides the frictionless experience I'm after here.

First things first: The first red-green cycle
One of the first very nice things to notice about Nancy is that it supports TDD really well (much more so than ASP.NET MVC), so lets start looking at the sample by looking at the first test:

    9 public class BaseUrlSpec
   10 {
   11   [Fact]
   12   public void should_respond_ok()
   13   {
   14     var app = new Browser(new DefaultNancyBootstrapper());
   15     var response  = app.Get("/", with => with.HttpRequest());
   16     var statusCode = response.StatusCode;
   17     Assert.Equal(HttpStatusCode.OK, statusCode);
   18   }
   19 }

This is just an ice breaker test to get us going: It just tests that our app responds with an http 200 OK status code. There are nevertheless a couple of things to notice:
  1. Nancy.Testing provides us with the Browser class, which gives our tests an easy way to interact with the app as if they were a browser: The browser object allows for doing GET, PUT, POST and DELETE, PATCH and OPTIONS against the app, including setting up the body, the headers and the protocol. In the case of the above we're doing a GET over http to the relative URL "/", no body, no special headers.
  2. Nancy.Testing also provides a BrowserResponse type that allows for testing all sorts of things on the response from the app. The variable 'response' in the above has this type. Here we just check the status code, but in later examples we'll do a lot more.
There, that's the red part.

To make this run we only have to write this little snippet of Nancy based code:

    4   using Nancy;
    5 
    6   public class ShortUrlModule : NancyModule
    7   {
    8     public ShortUrlModule()
    9     {
   10       Get["/"] =_ => HttpStatusCode.OK;
   11     }
   12   }

Firstly this little piece of code shows the central concept of Nancy: It provides a DSL for responding to http requests. It does so by letting you specify what code should run on GET, PUT, POST, DELETE, PATCH and OPTIONS requests for different URIs. In the above we only specify one such action for one URI: GET for '/'. Secondly it shows another defining trait of Nancy: The framework really tries to make things easy for you, in this case illustrated by the fact we just return a 200 OK status code, but Nancy accepts that and turns it into a full response for us.

GETting the form
So returning a 200 OK does not make for much of an app. What we want here is a very simple URL shortener, so lets turn the front page of the app into a form with a field for the URL the user wants shortened and a submit button. Again we start with a test:

   28     [Fact]
   29     public void should_contain_a_form_with_an_input_field_for_a_url_and_a_button()
   30     {
   31       //when
   32       var baseUrlGetResponse = app.Get("/", with => with.HttpRequest());
   33 
   34       //then
   35       baseUrlGetResponse.Body["form"]
   36         .ShouldExist();
   37 
   38       baseUrlGetResponse.Body["input#url"]
   39         .ShouldExistOnce();
   40 
   41       baseUrlGetResponse.Body["label"]
   42         .ShouldExistOnce().And
   43         .ShouldContain("Url: ");
   44 
   45       baseUrlGetResponse.Body["input#submit"]
   46         .ShouldExistOnce();
   47     }

Here we see some more of Nancys test emphasis: The BrowseResponse type mentioned above provides a Body property that lets our tests search through the body of the response using CSS selectors and make assertions on the results using a set of fluent 'Should' style extensions. For instance the above asserts that there is exactly one input tag with the id 'url' and that there is one label tag which furthermore contains the text 'Url: '. This, I must sat, is just awesome, and is a very good level to test at: Just below the pixels of the UI, and just above the real http stack of the real web server.

To make this run we need only do slightly more than in the previous cycle: We alter the application code to this:

 4   using Nancy;
 5 
 6   public class ShortUrlModule : NancyModule
 7   {
 8         public ShortUrlModule()
 9         {
10           Get["/"] = _ => View["index.html"];
11         }
12   }

and add the following index.html file to a Views folder:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
   <html>
     <body>
       <form method="post">
       <label>Url: </label>
      <input type="text" name="url" id="url"/>
      <input type="submit" value="shorten" id="submit"/>
    </form>
  </body>
</html>

Now we see Nancy trying to make things easy again: Just indicate in the application code that you want to return a view by saying View["index.html"], and Nancy looks in your views folders, finds the view and off we go.

POSTing the form
Next step is reacting to a POST of the form we just made, by shortening the URL. Let's set up a test for that:

 49     [Fact]
 50     public void should_return_shortened_url_when_posting_url()
 51     {
 52       //when
 53       var baseUrlPostResponse = app.Post("/",
 54         with =>
 55         {
 56           with.FormValue("url", "http://http://www.longurlplease.com/");
 57           with.HttpRequest();
 58         });
 59 
 60       baseUrlPostResponse.Body["a#shorturl"]
 61         .ShouldExist().And
 62         .ShouldContain("http://");
 63     }

Here we see how the app object is used for sending a POST to the app, and along with it the appropriate form values; in this case just the url. This starts to show the flexibility and ease of use of the Browser type.

Considering how we've handled the GET request until now, handling the POST is as straightforward:

 3   using System.Collections.Generic;
 4   using Nancy;
 5 
 6   public class ShortUrlModule : NancyModule
 7   {
 8         private static readonly Dictionary<string, string> urlMap = new Dictionary<string, string>();
 9 
10         public ShortUrlModule()
11         {
12             Get["/"] = _ => View["index.html"];
13             Post["/"] = _ => ShortenUrl();
14         }
15 
16         private string ShortenUrl()
17         {
18             string longUrl = Request.Form.url;
19             var shortUrl = ShortenUrl(longUrl);
20             urlMap[shortUrl] = longUrl;
21 
22             return ShortenedUrlView(shortUrl);
23         }
24 
25         private string ShortenUrl(string longUrl)
26         {
27             return "a" + longUrl.GetHashCode();
28         }
29 
30         private string ShortenedUrlView(string shortUrl)
31         {
32             return string.Format("<a id=\"shorturl\" href=\"http://{0}/{1}\">http://{0}/{1}</a>", Request.Headers.Host, shortUrl);
33         }
34     }

This is all pretty straight forward. The Nancy parts of this are:
  1. The use of POST...which does just what you expect.
  2. The return of a string in the last method. That bubles all the way up, and becomes the repsonse back to Nancy. Again Nancy is nice to us, sets the content type to text/html and the status code to 200 OK. This is not the nicest code on my part, so I might just return to refactor it in a future post.
The above of course is not very clever about the shortening, and does not exactly persist the short URLs in a good permanent place. I'll return to the persistence in a furture post.

Redirect
Only one part is missing for a simplistic but functioning URL shortener: The app must redirect the shortened URLs to the original longer one.
Again lets start with the test:

 65     [Fact]
 66     public void should_redirect_to_original_url_when_getting_short_url()
 67     {
 68       //when
 69       var baseUrlPostResponse = app.Post("/",
 70         with =>
 71         {
 72           with.FormValue("url", "http://www.longurlplease.com/");
 73           with.HttpRequest();
 74         }).GetBodyAsXml();
 75 
 76       var shortUrl = baseUrlPostResponse
 77         .Element("a")
 78         .Attribute("href").Value
 79         .Split('/')
 80         .Last();
 81 
 82       //then
 83       app.Get("/" + shortUrl, with => with.HttpRequest())
 84         .ShouldHaveRedirectedTo("http://www.longurlplease.com/");
 85     }

We do the POST again, find the short URL with a bit of LINQ-to-xml and then do a GET to that. And then just assert that the redirect happened. Pretty straight forward except maybe for the Linq-to-xml part.

To make the last test run we add this to our application code:

 14  Get["/{shorturl}"] = param =>
 15  {
 16     string shortUrl = param.shorturl;
 17     return Response.AsRedirect(urlMap[shortUrl.ToString()]);
 18  };


which is an action for a whole set of URIs, namely the URIs matching the URI template "/{shorturl}". In the action we access the 'shorturl' part of the URI through the param.shorturl; all parts of the templatized URI will be availbe like that through the dynamic param. Nice.

That's it. We have a simplistic URL shortener. No friction.

The source is availble on github, and more Nancy information is avaible on the Nancy web site.

2 comments:

  1. Thanks for providing a wonderful site.It is good and very informative .It help to create a design.Good effort.Keep it up.

    ReplyDelete