Book Image

Instant Nancy Web Development

By : Christian Horsdal
Book Image

Instant Nancy Web Development

By: Christian Horsdal

Overview of this book

Nancy is a C# web framework which aims to provide you ,the application developer, with an easy path to follow, where things will naturally fall into place and work. Nancy is a powerful, flexible, and lightweight web framework that allows you to get on with your job. Instant Nancy Web Development will give Readers practical introduction to building, testing, and deploying web applications with Nancy. You will learn how to take full advantage of Nancy to build clean application code, and will see how this code lends itself nicely to test driven development. You will also learn how to hook into Nancy to easily extend the framework. Instant Nancy Web Development offers you an in-depth exploration of all the major features of the Nancy web framework, from basic routing to deployment in the Cloud, and from model binding to automated tests. You will learn how to build web applications with Nancy and explore how to build web sites using Razor views. Next, you will learn how to build web based APIs suitable for JavaScript clients, mobile clients, and even desktop applications. In fact, you will learn how to easily combine the two into one. Finally, you will learn how to leverage Nancy to write clean and maintainable web applications quickly.
Table of Contents (7 chapters)

Taking a dependency – introducing the bootstrapper (Intermediate)


In this recipe, we will start persisting the Todo objects added to TodoNancy through POST requests to a data store. We will also persist edits done through PUT requests and remove Todos from the data store, which are removed through DELETE requests. The implementation of the data store for the Todo objects is not very interesting. It can easily be done against any database and is left as an exercise for you. For inspiration, you can have a look at the code download for this recipe where the data store is implemented against MongoDB.

This recipe will show you how Nancy uses dependency injection to make taking dependencies in modules easy and how to use a Nancy bootstrapper to configure these dependencies if necessary.

Getting ready

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

How to do it...

The following steps will show you how to manage the dependencies your modules may have:

  1. First, we add a new file to the NancyTodoTests project, call it DataStoreTests, and add the tests below to it. These tests use the FakeItEasy mock library (https://github.com/FakeItEasy/FakeItEasy), which is a straightforward and flexible mocking framework, that I find complements the style of Nancy.Testing well.

      public class DataStoreTests
      {
        private readonly IDataStore fakeDataStore;
        private Browser sut;
        private readonly Todo aTodo;
    
        public DataStoreTests()
        {
          fakeDataStore = A.Fake<IDataStore>();
          sut = new Browser( with =>
          {
            with.Dependency(fakeDataStore);
            with.Module<TodosModule>();
          });
    
          aTodo = new Todo() {id = 5, title = "task 10", order = 100, completed = true };
        }
    
        [Fact]
        public void Should_store_posted_todos_in_datastore()
        {
          sut.Post("/todos/", with => with.JsonBody(aTodo));
    
          AssertCalledTryAddOnDataStoreWtih(aTodo);
        }
    
        private void AssertCalledTryAddOnDataStoreWtih(Todo expected)
        {
          A.CallTo(() =>
            fakeDataStore.TryAdd(A<Todo>
              .That.Matches(actual =>   
                {
                  Assert.Equal(expected.title, actual.title);
                  Assert.Equal(expected.order, actual.order);
                  Assert.Equal(expected.completed, actual.completed);
                   return true;
                 }
    )))
            .MustHaveHappened();
        }
  2. This test uses a HTTP POST method to send a new todo object to our TodoNancy application and then asserts that a similar Todo object was added to the fake data store created in the constructor. The way the Browser object is created in the constructor is of special interest. Until now, we have created Browser objects based on DefaultNancyBootstrapper. From now on, we will need to take a bit more control over how the Browser object is set up. In particular, we will set up the fake data store object, fakeDataStore, as a dependency and we tell Nancy to only look for one module, namely TodosModule.

  3. To satisfy this test, we will first add an IDataStore interface to the TodoNancy project as shown here:

      public interface IDataStore
      {
        IEnumerable<Todo> GetAll();
        long Count { get; }
        bool TryAdd(Todo todo);
        bool TryRmove(int id);
        bool TryUpdate(Todo todo);
      }
  4. Then, we need to change the TodosModule class so that it takes a dependency on IDataStore:

        public TodosModule(IDataStore todoStore) : base("todos")
  5. And lastly, we need to change the handler for POST in TodosModule to use todoStores as follows:

          Post["/"] = _ =>
          {
            var newTodo = this.Bind<Todo>();
            if (newTodo.id == 0)
              newTodo.id = todoStore.Count + 1;
    
            if (!todoStore.TryAdd(newTodo))
              return HttpStatusCode.NotAcceptable;
    
            return Response.AsJson(newTodo).WithStatusCode(HttpStatusCode.Created);
          };
  6. We then move on to test the use of IDataStore by the other handlers. The implementation details of these are left to you, the reader, or can be found in code download for this recipe. Such a test will drive changes to the TodosModule class so that it becomes as follows:

      public class TodosModule : NancyModule
      {
        public static Dictionary<long, Todo> store = new Dictionary<long, Todo>();
    
        public TodosModule(IDataStore todoStore) : base("todos")
        {
          Get["/"] = _ => Response.AsJson(todoStore.GetAll());
    
          Post["/"] = _ =>
          {
            var newTodo = this.Bind<Todo>();
            if (newTodo.id == 0)
              newTodo.id = todoStore.Count + 1;
    
            if (!todoStore.TryAdd(newTodo))
              return HttpStatusCode.NotAcceptable;
    
            return Response.AsJson(newTodo)
              .WithStatusCode(HttpStatusCode.Created);
          };
    
          Put["/{id}"] = p =>
          {
            var updatedTodo = this.Bind<Todo>();
            if (!todoStore.TryUpdate(updatedTodo))
              return HttpStatusCode.NotFound;
    
            return Response.AsJson(updatedTodo);
          };
    
          Delete["/{id}"] = p =>
          {
            if (!todoStore.TryRmove(p.id))
              return HttpStatusCode.NotFound;
    
            return HttpStatusCode.OK;
          };
        }
      }
  7. At this point, our NancyTodos application is ported over to use the IDataStore interface to store the todo object, but there is still no implementation of the IDataStore interface; so, if you try to run the application or any of the other tests, you get a very long exception message at the bottom of which you find this:

    Nancy.TinyIoc.TinyIoCResolutionException
    Unable to resolve type: TodoNancy.IDataStore   at Nancy.TinyIoc.TinyIoCContainer.ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options)   at Nancy.TinyIoc.TinyIoCContainer.ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options)
  8. This tells us that Nancy is—naturally—not able to find an implementation of IDataStore. This in turn means Nancy will not start up the application because it needs an IDataStore implementation for the TodosModule class.

  9. Any implementation of IDataStore will do and I will leave this to you. In the code for download, you can find an implementation that works against the MongoDB (http://www.mongodb.org/) document database. There is a problem though. The IDataStore MongoDB implementation needs a connection string in order to connect to a MongoDB server. Nancy cannot know this connection string, so our application code needs to provide the connection string to the MongoDB data store. This kind of setup in Nancy applications belongs in a bootstrapper. In our case, we extend the default Nancy bootstrapper in order to preserve most of the defaults and just a little bit extra as follows:

      public class Bootstrapper : DefaultNancyBootstrapper
      {
        protected override void ConfigureApplicationContainer(TinyIoCContainer container)    {
            base.ConfigureApplicationContainer(container);
    
            var mongoDataStore = new MongoDataStore("mongodb://localhost:27010/todos");
            container.Register<IDataStore>(mongoDataStore);
        }
  10. The TodosModuleTests function needs its setup code tweaked a little bit to become as follows:

        public TodosModuleTests()
        {
          var database = MongoDatabase.Create("mongodb://localhost:27017/todos");
          database.Drop();
    
          sut = new Browser(new Bootstrapper());
          aTodo = new Todo
          {
            title = "task 1", order = 0, completed = false
          };
          anEditedTodo = new Todo()
          {
            id = 42, title = "edited name", order = 0, completed = false
          };
        }
  11. Now all the tests should pass again and the application should start up without errors.

How it works...

In the preceding section, we introduced a dependency in the TodosModule class. As discussed in the Building and running your first Nancy application (Simple) recipe, Nancy will instantiate all modules during the application start up and will do so again when a request for a module comes in. In order to instantiate TodosModule, Nancy will need an instance of IDataStore. This pattern is called Dependency Injection and is well documented elsewhere (for instance, on Martin Fowlers bliki http://www.martinfowler.com/articles/injection.html). Nancy uses a container to resolve dependencies. The container that Nancy uses by default is TinyIoC, and we saw how to set up a dependency with TinyIoC in the bootstrapper code. The TinyIoC container by default supports autowiring by scanning assemblies. This means that without any setup, TinyIoC will be able to resolve dependencies in cases where there is only one possible implementation for the dependency.

The TinyIoC container can be swapped out further through the bootstrapper code. There are a number of NuGet packages with the necessary bootstrapper code to use other containers; for instance, Castle Windsor, StructureMap, Autofac, Unity, and Ninject.

There's more...

The bootstrapper allows for much more than just setting up the container. For instance, in the Adding static content (Intermediate) recipe, we will use the bootstrapper to set up conventions for locating static files, and in the Handling cross-cutting concerns – Before, After, and Error Hooks (Intermediate) recipe, we will use the bootstrapper to set up a pipeline in which we have the custom code running before and after each request handler is executed.

In fact, the bootstrapper allows you to customize just about everything in your Nancy application, including Nancy's internals. We have only scratched the surface in this recipe.