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)

Using async handlers (Advanced)


In this recipe, we make one of the handlers in the TodosModule project asynchronous. Doing so is relevant for any handler that makes the current thread wait for something during processing—typically some sort of I/O. This could be a call to an external service, file access, or, as in our case, access to a database. Think about the deployment to AppHarbor we did in the Hosting Nancy on the Cloud (Intermediate) recipe. The Nancy application is on AppHarbor, but the MongoDB database is on another service. This means that whenever our handlers' call the database, it incurs a remote call, which in turn means that the thread will be waiting a while for the response. A lot of the times, we don't notice this wait, but nonetheless it means that we have a thread sitting around doing nothing for a while. If we make the database call asynchronously, the thread could do something else meanwhile. If we make the whole handler asynchronous, the thread could even process another request while the database call is pending.

At the time of writing, this feature of Nancy has not reached the final release, so we will be switching over to using a beta release. This means that the code shown here may well need to be changed if you are running on a later and final version of the Nancy async features.

Getting ready

As usual, just grab your copy of the code from the last recipe, and you are ready to start this one.

How to do it...

  1. In order to install the Nancy async beta with NuGet, we need to add the feed from https://www.myget.org/F/nancyasync/ as a source in the package manager in Visual Studio.

  2. Next, update the Nancy NuGet package to the newest beta version. This will add async support to Nancy. In order to update to a beta version, include the IncludePrerelease option in the NuGet command:

    PM> Update Nancy –IncludePrerelease
  3. Run all tests to see if updating the Nancy package had any negative effects. The RequestLoggingTests.Should_log_error_on_failing_request test will fail because the error pipeline is now async. Due to the way in which .NET handles propagating exceptions in async code, FormatException we are expecting to log is wrapped inside AggregateExcption. We can get the error logging we want by changing just one line in the bootstrapper as follows:

        private void LogUnhandledExceptions(IPipelines pipelines)
        {
          pipelines.OnError.AddItemToStartOfPipeline((ctx, err) =>
          {
            log.ErrorException(string.Format("Request {0} \"{1}\" failed. Exception: {2}", ctx.Request.Method, ctx.Request.Path, err.ToString()), err);
            return null;
          });
        }
  4. We are now ready to take advantage of async in our route handlers, so we go into the TodosModule project and change the handler for "/" to the following:

          Get["/", true] = async (_, __) =>
          {
            var allTodos = await todoStore.GetAll();
            return Negotiate
              .WithModel(allTodos.Where(todo => todo.userName == Context.CurrentUser.UserName).ToArray())
              .WithView("Todos");
          };
  5. We changed the signature of the delegate handling the route and also added an extra parameter to the route. We will get back to these changes in the following How it works… section. For now, we need to compile the code again. That is, we need to make the todosStore.GetAll()call return a Task. The IDataStore interface becomes as follows:

      public interface IDataStore
      {
        Task<IEnumerable<Todo>> GetAll();
        long Count { get; }
        bool TryAdd(Todo todo);
        bool TryRmove(int id, string userName);
        bool TryUpdate(Todo todo, string userName);
      }
  6. As an exercise, you now have to update your implementation of IDataStore accordingly. The updated version of the MongoDataStore class is in the code download for this recipe. Furthermore, you will need to update a few tests that fake out the call as we just made async. In each of these tests, the fake return values have to be wrapped in the Task objects and these tasks need to be started. As an example, look at the following test from DataStoreTests:

        [Fact]
        public void Should_remove_deleted_todo_from_datastore()
        {
          var returnValue = new Task<IEnumerable<Todo>>(() => new [] { new Todo { id = 1 }, new Todo { id = 2 } });
          returnValue.Start();
          A.CallTo(() => fakeDataStore.GetAll())
           .Returns(returnValue);
    
          sut.Delete("/todos/1");
    
          A.CallTo(() => fakeDataStore.TryRmove(1, A<string>._)).MustHaveHappened();
        }
  7. When you have made similar changes to all tests, they should all pass again, and the application should work just like it used to except now one of our handlers is asynchronous.

How it works...

Let's take another look at the handler for GET "/todos/:

      Get["/", true] = async (_, __) =>
      {
        var allTodos = await todoStore.GetAll();
        return Negotiate
          .WithModel(allTodos.Where(todo => todo.userName == Context.CurrentUser.UserName).ToArray())
          .WithView("Todos");
      };

The first thing to notice is that the signature of the handler changed from a lambda taking one argument to a lambda taking two arguments. The first argument is the same dynamic parameter argument as before where we made the handler async. The second argument is a cancellation token. The cancellation token is of the System.Threading.CancellationToken type and is a standard part of programming with .NET Task<T> objects. The cancellation token is used to propagate notifications to tasks if they are to be cancelled.

The second thing to notice is that there are now two arguments to the indexer on Get. The first argument is the route as usual, but the second argument is new. The second argument is either a Boolean expression or a Func<NancyContext, bool> argument. In both the cases, the second argument tells Nancy whether the handler should be executed asynchronously or not. Using a Func<NancyContext, bool> argument allows your code to inspect the NancyContext for each individual request before deciding whether or not to handle it asynchronously, giving your application code quite fine-grained control. In our case, we have simply decided upfront to always do async handling.

There's more...

Handlers for other HTTP methods apart from GET can also be asynchronous and so can Before, After, and OnError hooks. Each of these is made async in a similar fashion to how our handler for GET "/todos/" was made async. All in all, it is straightforward to make your Nancy applications completely async if you want and if your application logic does not rely on being executed synchronously.