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)

Adding views (Intermediate)


In this recipe, we will add a couple of Razor views to the application built in the previous recipes. We will see how our module code controls which view to use and how to set up tests for the views.

Getting ready

You need the code built until the Content negotiation and more model binding (Advanced) recipe. If you haven't coded along, you can start from the code for the Content negotiation and more model binding (Advanced) recipe in the code download.

I will assume you have a working knowledge of Razor, so if you are new to Razor, you should probably brush up on Razor before diving into this recipe.

How to do it...

The following steps will help you add views to your application:

  1. As always, we start by adding a test. This test is added to TodosModuleTests and does two new things: first, it sets the Accept header on the GET request to "text/html", which colloquially means "give me back some HTML", and second, it asserts on the contents of the body of the response. The actual variable has the BrowserResponse type. The Body property on BrowserResponse objects represents the body of the response from the handler code in the Nancy module. When this body contains HTML, the BrowserResponse type supports looking up parts of this HTML using CSS selectors:

        [Fact]
        public void Should_be_able_to_get_view_with_posted_todo()
        {
          var actual = sut.Post("/todos/", with =>
          {
            with.JsonBody(aTodo);
            with.Accept("application/json");
          })
          .Then
          .Get("/todos/", with => with.Accept("text/html"));
    
          actual.Body["title"].ShouldContain("Todos");
          actual.Body["tr#1 td:first-child"]
            .ShouldExistOnce()
            .And
            .ShouldContain(aTodo.title);
        }
  2. The test posts a Todo object to our application and then gets it back in the form of HTML. The tests assert that the body of the response to the GET request should contain a title tag that contains the "Todos"text and that the first td tag inside the first tr tag found in the body should contain the title of the todo posted right before.

  3. At this point this test fails. Run and see for yourself. Take a look at the error message you get from Nancy. It's a bit long, but it should contain a part similar to the following code:

    Unable to locate view 'Todo[]'
    Currently available view engine extensions: sshtml,html,htm
    Locations inspected: views/todos/Todos/Todo[]-da-
    DK,views/todos/Todos/Todo[],todos/Todos/Todo[]-da-
    DK,todos/Todos/Todo[],views/todos/Todo[]-da-
    DK,views/todos/Todo[],todos/Todo[]-da-
    DK,todos/Todo[],views/Todos/Todo[]-da-
    DK,views/Todos/Todo[],Todos/Todo[]-da-
    DK,Todos/Todo[],views/Todo[]-da-DK,views/Todo[],Todo[]-da-
    DK,Todo[]Root path: C:\projects\nancy-quick-
    start\src\recipe-6\TodoNancy\TodoNancyTests\bin\Debug
    
  4. This actually is very useful information. First, it tells us that Nancy is trying to locate a view called Todo[]. Second, it tells us which file extensions Nancy expects views to have: sshtml, html, or htm. Third, it tells us all the places Nancy looked for a view in a file called Todo[] with one of the listed extensions.

  5. The first problem to fix is that we want the view to be called Todos and not Todo[]. In order to do this, we change the Get handler in the TodosModule class to this:

          Get["/"] = _ =>
            Negotiate
            .WithModel(todoStore.GetAll())
            .WithView("Todos");
  6. The previous code tells Nancy to use content negotiation to control the format of the response and to use todoStore.GetAll() as the model; that is, if the request indicates that it accepts JSON, the result of todoStore.GetAll() will be serialized to JSON in the body of the response. If the incoming request accepts HTML, Nancy will now look for the "Todos"view in all the places indicated in the previous error message, and pass the result of todoStore.GetAll() into the view as the model object.

  7. As stated in the beginning of the recipe, we want our views to be Razor views, so at this point we install the Nancy.ViewEngines.Razor NuGet package in the TodoNancy project. This will add references to the Razor assembly and the Nancy adaptor for Razor, but in order for Nancy to start using the Razor View Engine, we need to make sure the assembly is loaded. This is done most conveniently on the bootstrapper where we add the following line of code:

        private RazorViewEngine ensureRazorIsLoaded;
  8. Now we are ready to add the new Razor view. To do so, create a Views folder in the TodoNancy project, and add an HTML file under the Views folder called as Todos.cshtml. Then, go to the properties of this new file (either right-click on it in Solution Explorer or press Alt + Enter while it is selected in Solution Explorer) and set the Copy to Output Directory property to Copy always. Now replace any content in the new Todos.cshtml file with the following code snippet:

    @inherits 
    Nancy.ViewEngines.Razor.NancyRazorViewBase<TodoNancy.Todo[]>
    
    <html>
      <head><title>Todos</title></head>
      <body>
        <h1>All todos</h1>
        <table>
          <th>Title</th><th>Order</th><th>Completed</th>
          @foreach (var todo in @Model)
          {
            <tr id="@todo.id"><td>@todo.title</td><td>@todo.order</td><td>@todo.completed</td></tr>
          }
        </table >
        <h1>Add todo</h1>
        <form action="/todos/" method="post">
          <input type="text" name="title" value="title" />
          <input type="number" name="order" value="0" />
          <input type="checkbox" name="completed" />
          <input type="submit" value="add" />
        </form>
      </body>
    </html>
  9. This is a strongly typed Razor view that works on an array of Todo objects. It contains a table with a row for each Todo object. This is the table we assert on in our test, which should now pass.

  10. The Todos view also contains a form tag that is set up to post in /todos/. In the code download for this recipe, you will find a test for this form, but I will leave it out here because it is similar to the tests we added previously.

  11. Try to run the application and go to /todos in your browser. You should see the Todos view showing whatever views you have in your implementation of todoStore. You should also see the form, but if you fill in the form and click on add, you get an Internal Server Error. If you look at the details of the error, you will see that Nancy cannot locate an appropriate view to use. To fix this, add a test that does a POST and accepts text/html and then change the handler for Post in TodosModule as shown in the following code snippet:

          Post["/"] = _ =>
          {
            var newTodo = this.Bind<Todo>();
            if (newTodo.id == 0)
              newTodo.id = todoStore.Count + 1;
    
            if (!todoStore.TryAdd(newTodo))
              return HttpStatusCode.NotAcceptable;
    
            return Negotiate.WithModel(newTodo)
              .WithStatusCode(HttpStatusCode.Created)
              .WithView("Created");
          };
  12. For this to work, we need to add another view called Created.cshtml. I will leave it up to you to implement the Created view (or you can look in the code download).

There's more...

Nancy doesn't support Razor views alone; out of the box, Nancy comes with Super Simple View Engine, which—as the name indicates—is a simple (yet fairly powerful) view engine with a syntax akin to Razor. The Super Simple View Engine expects files to have the .sshtml extension, which is why this particular extension is listed when Nancy can't find a view.

Furthermore, there are NuGets for various other view engines such as Spark, Nustache, and others.

As is always the case with Nancy, you can also roll on your own if you cannot find support for your favorite view engine. In order to do this, you basically implement the IViewEngine interface against your favorite view engine and let Nancy automatically wire your new view engine up with the rest of the framework.