Sunday, October 16, 2011

Frictionless .NET Web App Development with Nancy Part II - Introducing a View Engine

In the last post I introduced a litte sample web app coded up with the Nancy web framework. The code presented in that post left a few things open for later enhancement. This post takes a stab one of those things: The part where a string.Format is used to create a bit of html, which is then just returned directly to the client, namely the POST action in this code:

 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     }

Specifically the last method, 'ShortenedUrlView' is bad code: It's brittle, it doesn't produce valid HTML, and it's open for script injection. Therefore I want to separate that bit of view code from the module code: I want a view engine. Luckily Nancy supports a number of standard view engines(Razor, Spark, DotLiquid, NDjango) each of which are only a NuGet package away, but it also comes with its own appropriately named Super Simple View Engine, SSVE. For this sample SSVE will do, so let's just stick with that.

We add a file called shortened_url.sshtml to the Views folder and put this into it:

<html>
  <body>
    <a id="shorturl" href="http://@Model.Host/@Model.ShortUrl">
      http://@Model.Host/@Model.ShortUrl
    </a>
  </body>
</html>

As with Razor views the @ indicates code snippets. In the above we insert 'Host' and 'ShortUrl' properties from the view model in the view.

To use this we also need to modify the Nancy module code a bit. We need to return a View[] instead of simply a string:

   21         private Response ShortenUrl()
   22         {
   23             string longUrl = Request.Form.url;
   24             var shortUrl = ShortenUrl(longUrl);
   25             urlMap[shortUrl] = longUrl;
   26 
   27       return View["shortened_url", 
   28         new { Host = Request.Headers.Host, ShortUrl = shortUrl }];
   29         }
   30 
   31         private string ShortenUrl(string longUrl)
   32         {
   33             return "a" + longUrl.GetHashCode();
   34         }
   35 

We saw use of View[] last time, this time around we provide a model object along with the view name. This object is passed to the view code as the @Model.

That's it. Introduced a view engine. No friction.

The code for this sample is still available on GitHub. Now with the updates described here.