Routes and model binding (Intermediate)

Exclusive offer: get 50% off this eBook here
Instant Nancy Web Development [Instant]

Instant Nancy Web Development [Instant] — Save 50%

Leverage the powerful and lightweight .NET-based Nancy Web Framework with this book and ebook

$12.99    $6.50
by Christian Horsdal | October 2013 | Open Source Web Development

This article by Christian Horsdal, author of Instant Nancy Web Development, we take a look at how to handle the other HTTP verbs apart from GET and how to work with dynamic routes such as /custumer/42, where 42 is the customer ID. We will also look at how to work with JSON data and how to do model binding.

In this recipe, we will see the Todo application, which is the running example of the article, take shape. In fact at the end of this recipe, you will have a backend—if hooked up correctly— that works with the canonical JavaScript library todo samples. The downloadable code for this recipe is attached with the backbone.js todo sample.

(For more resources related to this topic, see here.)

Getting ready

This section builds on the previous section and assumes you have the TodoNancy and TodoNancyTests projects all set up.

How to do it...

The following steps will help you to handle the other HTTP verbs and work with dynamic routes:

  1. Open the TodoNancy Visual Studio solution.
  2. Add a new class to the NancyTodoTests project, call it TodosModulesTests, and fill this test code for a GET and a POST route into it:

    public class TodosModuleTests { private Browser sut; private Todo aTodo; private Todo anEditedTodo; public TodosModuleTests() { TodosModule.store.Clear(); sut = new Browser(new DefaultNancyBootstrapper()); aTodo = new Todo { title = "task 1", order = 0, completed = false }; anEditedTodo = new Todo() { id = 42, title = "edited name", order = 0, completed = false }; } [Fact] public void Should_return_empty_list_on_get_when_no_todos_have_been
    _posted() { var actual = sut.Get("/todos/"); Assert.Equal(HttpStatusCode.OK, actual.StatusCode); Assert.Empty(actual.Body.DeserializeJson<Todo[]>()); } [Fact] public void Should_return_201_create_when_a_todo_is_posted() { var actual = sut.Post("/todos/", with => with.JsonBody(aTodo)); Assert.Equal(HttpStatusCode.Created, actual.StatusCode); } [Fact] public void Should_not_accept_posting_to_with_duplicate_id() { var actual = sut.Post("/todos/", with => with.JsonBody(anEditedTodo)) .Then .Post("/todos/", with => with.JsonBody(anEditedTodo)); Assert.Equal(HttpStatusCode.NotAcceptable, actual.StatusCode); } [Fact] public void Should_be_able_to_get_posted_todo() { var actual = sut.Post("/todos/", with => with.JsonBody(aTodo) ) .Then .Get("/todos/"); var actualBody = actual.Body.DeserializeJson<Todo[]>(); Assert.Equal(1, actualBody.Length); AssertAreSame(aTodo, actualBody[0]); } private void AssertAreSame(Todo expected, Todo actual) { Assert.Equal(expected.title, actual.title); Assert.Equal(expected.order, actual.order); Assert.Equal(expected.completed, actual.completed); } }

  3. The main thing to notice new in these tests is the use of actual.Body.DesrializeJson<Todo[]>(), which takes the Body property of the BrowserResponse type, assumes it contains JSON formatted text, and then deserializes that string into an array of Todo objects.
  4. At the moment, these tests will not compile. To fix this, add this Todo class to the TodoNancy project as follows:

    public class Todo { public long id { get; set; } public string title { get; set; } public int order { get; set; } public bool completed { get; set; } }

  5. Then, go to the TodoNancy project, and add a new C# file, call it TodosModule, and add the following code to body of the new class:

    public static Dictionary<long, Todo> store = new Dictionary<long, Todo>();

  6. Run the tests and watch them fail. Then add the following code to TodosModule:

    public TodosModule() : base("todos") { Get["/"] = _ => Response.AsJson(store.Values); Post["/"] = _ => { var newTodo = this.Bind<Todo>(); if (newTodo.id == 0) newTodo.id = store.Count + 1; if (store.ContainsKey(newTodo.id)) return HttpStatusCode.NotAcceptable; store.Add(newTodo.id, newTodo); return Response.AsJson(newTodo) .WithStatusCode(HttpStatusCode.Created); }; }

  7. The previous code adds two new handlers to our application. One handler for the GET "/todos/" HTTP and the other handler for the POST "/todos/" HTTP. The GET handler returns a list of todo items as a JSON array. The POST handler allows for creating new todos. Re-run the tests and watch them succeed.
  8. Now let's take a closer look at the code. Firstly, note how adding a handler for the POST HTTP is similar to adding handlers for the GET HTTP. This consistency extends to the other HTTP verbs too. Secondly, note that we pass the "todos"string to the base constructor. This tells Nancy that all routes in this module are related to /todos. Thirdly, notice the this.Bind<Todo>() call, which is Nancy's data binding in action; it deserializes the body of the POST HTTP into a Todo object.
  9. Now go back to the TodosModuleTests class and add these tests for the PUT and DELETE HTTP as follows:

    [Fact] public void Should_be_able_to_edit_todo_with_put() { var actual = sut.Post("/todos/", with => with.JsonBody(aTodo)) .Then .Put("/todos/1", with => with.JsonBody(anEditedTodo)) .Then .Get("/todos/"); var actualBody = actual.Body.DeserializeJson<Todo[]>(); Assert.Equal(1, actualBody.Length); AssertAreSame(anEditedTodo, actualBody[0]); } [Fact] public void Should_be_able_to_delete_todo_with_delete() { var actual = sut.Post("/todos/", with => with.Body(aTodo.ToJSON())) .Then .Delete("/todos/1") .Then .Get("/todos/"); Assert.Equal(HttpStatusCode.OK, actual.StatusCode); Assert.Empty(actual.Body.DeserializeJson<Todo[]>()); }

  10. After watching these tests fail, make them pass by adding this code to the constructor of TodosModule:

    Put["/{id}"] = p => { if (!store.ContainsKey(p.id)) return HttpStatusCode.NotFound; var updatedTodo = this.Bind<Todo>(); store[p.id] = updatedTodo; return Response.AsJson(updatedTodo); }; Delete["/{id}"] = p => { if (!store.ContainsKey(p.id)) return HttpStatusCode.NotFound; store.Remove(p.id); return HttpStatusCode.OK; };

  11. All tests should now pass.
  12. Take a look at the routes to the new handlers for the PUT and DELETE HTTP. Both are defined as "/{id}". This will match any route that starts with /todos/ and then something more that appears after the trailing /, such as /todos/42 and the {id} part of the route definition is 42. Notice that both these new handlers use their p argument to get the ID from the route in the p.id expression. Nancy lets you define very flexible routes. You can use any regular expression to define a route. All named parts of such regular expressions are put into the argument for the handler. The type of this argument is DynamicDictionary, which is a special Nancy type that lets you look up parts via either indexers (for example, p["id"]) like a dictionary, or dot notation (for example, p.id) like other dynamic C# objects.

There's more...

In addition to the handlers for GET, POST, PUT, and DELETE, which we added in this recipe, we can go ahead and add handler for PATCH and OPTIONS by following the exact same pattern.

Out of the box, Nancy automatically supports HEAD and OPTIONS for you. To handle the HEAD HTTP request, Nancy will run the corresponding GET handler but only return the headers. To handle OPTIONS, Nancy will inspect which routes you have defined and respond accordingly.

Summary

In this article we saw how to handle the other HTTP verbs apart from GET and how to work with dynamic routes. We will also saw how to work with JSON data and how to do model binding.

Resources for Article:


Further resources on this subject:


Instant Nancy Web Development [Instant] Leverage the powerful and lightweight .NET-based Nancy Web Framework with this book and ebook
Published: September 2013
eBook Price: $12.99
See more
Select your format and quantity:

About the Author :


Christian Horsdal

Christian Horsdal is an independent consultant working with clients as an architect, consultant, and developer. He works with clients of all types, from small start-ups to large global companies.

Christian is an expert .NET architect and developer who mixes and matches commercial, open source, and tailor-made components in a quest to create simple and lean solutions that allow for quick and agile development.

Christian can be found online through his website http://www.horsdal-consult.dk/.

Books From Packt


 WCF 4.5 Multi-Layer Services Development with Entity Framework
WCF 4.5 Multi-Layer Services Development with Entity Framework

 .Net Framework 4.5 Expert Programming Cookbook
.Net Framework 4.5 Expert Programming Cookbook

Visual Studio 2012 and .NET 4.5 Expert Development Cookbook
Visual Studio 2012 and .NET 4.5 Expert Development Cookbook

Microsoft .NET Framework 4.5 Quickstart Cookbook
Microsoft .NET Framework 4.5 Quickstart Cookbook

Instant .NET 4.5 Extension Methods How-to [Instant]
Instant .NET 4.5 Extension Methods How-to [Instant]

Ext.NET Web Application Development
Ext.NET Web Application Development

Instant Ext.NET Application Development [Instant]
Instant Ext.NET Application Development [Instant]

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
m
X
n
g
T
8
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software