This post is over 6 months old. Some details, especially technical, may have changed.

Micro Web Frameworks in .NET 101: Tinyweb

Tinyweb takes a slightly different view than the other web frameworks I have talked about. In fact it takes a fairly opinionated approach to the way your structure your applications code and forces you to think in terms of resource endpoints rather then big monolithic modules or controllers. It's certainly true that projects based on the other frameworks such as Nancy and Jessica can be architected in such a way but Tinyweb ensures that you don't start cutting corners and making allowances for lazy code by simply not providing the ability to do it!

Getting Started - Hello World

Lets get Tinyweb first from good old reliable Nuget,

Install-Package Tinyweb

With Tinyweb installed we can go ahead and create a RootHandler this name is the one exception to the Tinyweb handler naming convention which I'll touch on in a minute.

public class RootHandler
{
    public IResult Get()
    {
        return Result.String("Hello World");
    }
}

In our handler I also defined the a Get method which returns an IResult this will act as our endpoint. One last thing before we magic up a web page is to initialise Tinyweb via our Global.asax.cs so Tinyweb can do its bootstrapping discovery voodoo stuffs.

public class Global : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        Tinyweb.Init();
    }
}

Et voila! Run the app and once again we have a hello world.

Tinyweb Features

Even in that small Hello World example there are plenty of Tinyweb features but you'll also notice that Tinyweb really doesn't get in the way at all.

Handlers

Each Tinyweb Handler represents a single resource endpoint. With the exception of RootHandler the URL endpoint for each handler is inferred from the name of the handler. For example a handler named HelloHandler will react to /hello, HelloWorldHandler will react to /hello/world. You can see from these examples that casing of the handler name is important in terms of the url endpoint generation.

The next thing about handlers is that they respond to 4 methods each one corresponding to the 4 main Http Verbs - Get(), Post(), Put() and Delete - the only requirement of these is that they return an IResult

Model Binding and Arguments

Model binding is also provided by handlers actions - just pass in an object and Tinyweb will do it's best to bind request values to this object (accepts primitives, collections and plain C# objects).

Alternatively if you want to do some work under the hood you can make sure the RequestContext is passed in by simply passing it instead. This gives you access to the bowels of the request to do with what you please.

Advanced Routes

It's also possible to override the default routing convention by declaring a handler level variable called route of type Route. This example shows how we can override handlers default route /hello/world and go with the more understandable "/helloworld".

public class HelloWorldHandler
{
    Route route = new Route("helloworld");

    public IResult Get()
    {
        return Result.String("Hello World");
    }
}

Route Parameters

In the spirit of RESTFulness you may also want to accept parameters as part of your URL and we can do that as well with this route class as well as optionally suppling default values for parameters,

public class HelloWorldHandler
{
    Route route = new Route("hello/{name}");
    // OR WITH A DEFAULT VALUE FOR PARAMS
    Route route = new Route("hello/{name}", new { name = "World" });

    public IResult Get(string name)
    {
        return Result.String("Hello " + name);
    }
}

A powerful little approach.

Results

All Tinyweb results (e.g. what the response returns) implement the IResult interface so it's simple enough to implement your own custom result type though it is probably unnecessary for the most part as Tinyweb offers a range of result types straight away via the Result classes static methods.

  • String
  • File
  • Json
  • XML
  • JsonOrXml (returns either Json or XML depending on the request headers)
  • Html
  • Redirect (to a specific handler or URL)

Tinyweb also offers a View class that can render views written with Spark and Razor so rendering a view can be as simple as

public class HelloWorldHandler
{
    Route route = new Route("hello/{name}", new { name = "World" });

    public IResult Get(string name)
    {
        return View.Razor<string>(name, "hello.cshtml");
    }
}

Filters

Filters allow us to intercept requests both before and after they are processed on both a per handler and global level.

Each handler can optionally contain an After and/or a Before method that will, not surprisingly, be called after and before each handler request (I'll let you guess which one does which) e.g.

public class RootHandler
{
    public void Before()
    {
        Logger.Log("Before Executed");
    }

    public IResult Get()
    {
        return Result.String("Hello World");
    }

    public void After()
    {
        Logger.Log("After Executed");
    }
}

Alternatively if you want the same before or after filter applied across all handlers you can create a Filter class by creating a class appended with the word Filter. We can recreate the same handler above with a global filter for logging,

public class LoggingFilter
{
    public void Before()
    {
        Logger.Log("Before Executed");
    }

    public void After()
    {
        Logger.Log("After Executed");
    }
}

public class RootHandler
{
    public IResult Get()
    {
        return Result.String("Hello World");
    }
}

Before and After methods can also return IResult objects if you want to work with the actual response.

Error Handling and Debugging

Not all of us write flawless code and so sometimes things go south and exceptions start throwing their weight around. Tinyweb has a global hook that is useful for capturing such errors as they bubble up to the surface. The TinyWeb class has a static property OnError that accepts an Action that can be used to handle the exception e.g.

protected void Application_Start(object sender, EventArgs e)
{
    Tinyweb.Init();
    Tinyweb.OnError = (exception, context, data) =>
    {
        Logger.Log(exception);
    };
}

Another useful tool when debugging your app is the Tinyweb.WhatHaveIGot(). It's a convenience method that can be used to print out all the matched routes and filters.

Conclusion

On the surface the focus of Tinyweb may seem only moderately different from other frameworks but after playing with it for a while it become apparent that the opinionated approach really makes you think about your projects structure and API. I'm certainly of the opinion that this is a damn good thing1. Tinyweb gives me just enough framework to do pretty much everything I need - which I would expect from a real micro-framework. On top of that it's flexible and intuitive and another handy utility in my tool belt.

1 I've already said that it is possible to make the likes of Nancy and Jessica behave in this, either by ensuring that all module adhere to this pattern, or creating an abstract base class that enforces this sort of structure so I am in no way bashing the alternatives. I like choice.

Published in .NET on July 17, 2011