Sunday, December 12, 2010

An Explosion of Alternative Web Frameworks

I’ve had a very enlightening last few weeks. I’m talking about the explosion in the number of alternative web frameworks for .NET and especially the emergence of alternatives to our old friend, the core ASP.NET infrastructure. Although a lot of this work has been around for a while, I’ve only just become aware of it.

The world of open source .NET frameworks has been vibrant for some time now. We’ve had Monorail for years and more recently FubuMvc and OpenRasta. So far OpenRasta is the only one which has attempted to break away from the stranglehold of ASP.NET and build it’s own HTTP abstraction.

With OpenRasta as the exception, all the existing frameworks (including ASP.NET MVC) have been targeted at ‘traditional’ web applications. By that, I mean applications where the majority of formatting and execution occurs on the server and to the browser the application appears as little more than static HTTP. But all this is changing. Two things are radically altering the development landscape; HTML5 and Mobile apps. both of these require infrastructure optimised to build scalable RESTful JSON APIs. We also need frameworks that will deploy without requiring an IIS instance and that preferably work on Linux with Mono.

Elsewhere, in the world of open source dynamic languages, things have been moving very fast. Node.js and Sinatra have generated enormous interest, as have the flexibility of web middleware abstractions like Rack and WSGI.

With these as inspiration, new .NET web frameworks have been popping up all over the place. Even more exciting is the emergence of common abstractions, so that we may be able to assemble web application stacks like Lego. Read on…

Manos

The first new framework that came to my attention a few weeks back was Manos by Jackson Harper. This is a lightweight web framework for Mono inspired by node.js and Sinatra. It uses the same IO library, libev, as node.js. The Herding Code podcast interviewed Jackson recently. It’s well worth a listen.

Here’s a snippet of a simple Manos application:

public class HelloWorld : ManosApp {

    public HelloWorld ()
    {
        Get ("/", ctx => ctx.Response.End ("Hello, World"));
    }
}

As you can see there’s a very simple mapping of a route to a delegate. In this case the path ‘/’ maps to a delegate that returns “Hello World” and then completes. Because the context is passed as a an argument to the delegate that handles the request, it’s trivial write asynchronous applications. In the podcast Jackson claimed that he can handle 10,000 open connections in his tests, that’s impressive.

Nancy

The next  framework to come to my notice was Nancy by Andreas HÃ¥kansson. This framework is heavily influenced by Sinatra. Currently it only works with ASP.NET, but the intention is to make it server agnostic. Here’s a snippet:

public class Module : NancyModule
{
    public Module()
    {
        Get["/"] = parameters => {
            return "Hello World";
        };
    }
}

Nancy has a dictionary that maps routes to delegates and the return value of the delegate dictates the response.

Nina

Around about the same time I also became aware of Nina by Dotan Nahum. Nina is also inspired by Sinatra (do you get the name references?).  Nina is based on ASP.NET, I couldn’t see if there was any intention of making it work on other platforms. Here’s Nina’s hello world:

public class HelloWorld : Nina.Application
{
    public HelloWorld()
    {
        Get("/", (m,c) => Text("Hello World"));
    }
}

Again we see the same pattern, mapping a route to a delegate. This time the delegate takes NameValueCollection of parameters and the HttpContext as its arguments and returns the response. Nina seems a little more developed than Nancy. It’s got full integration for most of the existing .NET view engines for example.

Kayak

Just yesterday I listened to Benjamin van der Veen talking to Scott Hanselman on Hanselminutes about Kayak. Kayak, like Manos is a full web stack and I understood from the interview that it also uses, or will use, libev. Kayak, like the other frameworks here has a direct route to method mapping:

public class MyService : KayakService 
{
    [Path("/")]
    public void Root() 
    {
        Response.Write("Hello, world.");
    }
}

OWIN

OWIN stands for Open Web Interface for .NET. This is even more exciting than the explosion in innovation around web frameworks itself. It’s a standard interface between servers and frameworks, currently under intense discussion at the .NET HTTP Abstractions group. This is important, because once it becomes an adopted standard it will mean that any server should work with any framework. So for example, I will be able to run OpenRasta on libev, or maybe Manos on IIS.

It will enable separate innovation in severs and frameworks, so you’ll be able to take a high performance scalable sever and marry it with your framework of choice. More than that, it’ll enable independent development of web middleware. Say I want to write a compression library, I won’t have to choose a particular framework to host it in, I simply make it talk OWIN at both ends and it will plug into any combination of framework and sever.

Here is the interface as it currently stands, but remember it’s under intense development at the moment and is likely to change:

namespace Owin
{
    /// <summary>
    /// An HTTP application.
    /// </summary>
    public interface IApplication
    {
        /// <summary>
        /// Begins the asynchronous process to get the <see cref="IResponse"/>.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="callback">The callback.</param>
        /// <param name="state">The state.</param>
        /// <returns>The <see cref="IAsyncResult"/> that represents the asynchronous invocation.</returns>
        IAsyncResult BeginInvoke(IRequest request, AsyncCallback callback, object state);

        /// <summary>
        /// Ends the asynchronous process to get the <see cref="IResponse"/>.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>The <see cref="IResponse"/>.</returns>
        IResponse EndInvoke(IAsyncResult result);
    }
    
    /// <summary>
    /// The HTTP request provides the requested method, uri, headers, application items, and input body.
    /// </summary>
    public interface IRequest
    {
        /// <summary>
        /// Gets the request method.
        /// </summary>
        string Method { get; }

        /// <summary>
        /// Gets the requested uri.
        /// </summary>
        string Uri { get; }

        /// <summary>
        /// Gets the headers.
        /// </summary>
        /// <remarks>
        /// Each header key may have one or more values matching the HTTP spec.
        /// Example:
        ///   GET / HTTP/1.0
        ///   Accept: text/html;application/xml
        ///
        ///   Generates a headers dictionary with key "Accept" containing string "text/html;application/xml"
        /// </remarks>
        IDictionary<string, IEnumerable<string>> Headers { get; }
        
        /// <summary>Gets the application-specific items or settings.</summary>
        IDictionary<string, object> Items { get; }

        /// <summary>
        /// Begins the asynchronous read from the request body.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="count">The count.</param>
        /// <param name="callback">The callback.</param>
        /// <param name="state">The state.</param>
        /// <returns>An IAsyncResult that represents the asynchronous read, which could still be pending.</returns>
        /// <see href="http://msdn.microsoft.com/en-us/library/system.io.stream.beginread.aspx"/>
        IAsyncResult BeginReadBody(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
        
        /// <summary>
        /// Ends the asynchronous read from the request body.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>The number of bytes returned from the stream.</returns>
        /// <see href="http://msdn.microsoft.com/en-us/library/system.io.stream.endread.aspx"/>
        int EndReadBody(IAsyncResult result);
    }
    
    /// <summary>
    /// The HTTP response provides the status, headers, and body from an <see cref="IApplication"/>.
    /// </summary>
    public interface IResponse
    {
        /// <summary>
        /// Gets the status code and description.
        /// </summary>
        /// <remarks>The string should follow the format of "200 OK".</remarks>
        string Status { get; }

        /// <summary>
        /// Gets the headers.
        /// </summary>
        /// <remarks>
        /// Each header key may have one or more values matching the HTTP spec.
        /// Example:
        ///   HTTP/1.1 200 OK
        ///   Set-Cookie: foo=bar
        ///   Set-Cookie: baz=quux
        /// 
        ///   Generates a headers dictionary with key "Set-Cookie" containing string ["foo=bar";"baz=quux"]
        /// </remarks>
        IDictionary<string, IEnumerable<string>> Headers { get; }

        /// <summary>
        /// Gets the body <see cref="IEnumerable&lt;object&gt;"/>.
        /// </summary>
        /// <returns>The response as an <see cref="IEnumerable&lt;object&gt;"/>.</returns>
        /// <remarks>
        /// The <see cref="IEnumerable&lt;object&gt;"/> is not guaranteed to be hot.
        /// This method should be considered safe to generate either a cold or hot enumerable
        /// so that it _could_ be called more than once, though the expectation is only one call.
        /// </remarks>
        IEnumerable<object> GetBody();
    }
}    

Read the spec for the interface here: http://owin.github.com/owin/

Like I said, these are very interesting times. Keep an eye on these projects, I expect we’ll all be building web applications very differently in a few years time.

10 comments:

jacksonh said...

Hey, first off excellent work on this post. One thing I should clarify on Manos though, is the "sinatra style" method of routing is just one of the options. In fact, its probably the one I frown upon using the most, but its there for quick and dirty testing. For the most part I think people should be using implicit or explicit method routing. Here are a few quick examples:

// Automatically maps to 'Index'
public void Index (IManosContext ctx)
{
}

// Will map to 'Admin' and let the routing find a method below
// maybe something like 'Admin/Index'
public AdminModule Admin {
get;
set;
}

// Maps to Foobar, pulls 'foo' out of the supplied data (query, post, or url data)
public void Foobar (IManosContext ctx, string foo)
{
}

// Maps to all three routes:
[Route ("Index", "Home", "Foobar")]
public void Foo (IManosContext ctx)
{
}


I use Sinatra style routing a lot with testing, but I'm not a huge fan of it for app development, because I think it makes it harder to make self contained reusable components. Something like an Admin module that handles all its routing itself.

Glenn Block said...

Very nice recap Mike. It is exciting to see the work the community is doing. With Owin it is great to see a bunch of folks coming together and designing the Apis in a communal fashion. Owin is like the CommonServiceLocator of HTTP, only it probably won't suffer the same abuses :-)

Ken Egozi said...

@Mike - shameless plug - my own WebOnDiet (on github).

I wasn't aware of Nina and Nancy when I started this, and Manos was too-much. Anyway, WOD is probably not as mature as the others listed here, as I am evolving it along with my needs only - it is running my personal blog (actually a not-yet-published reshuffled version of my blog). OWIN is a very nice surprise, I'll look into it.

@Jackson - modules such as Admin can use the explicit "Sinatra style" routing, if you add relative-path to the mix

Martin said...

The .NET HTTP Abstractions group is indeed a very interesting place right now. It's nice to see some movement in the .NET stack in this space.

Mike Hadlow said...

@Jackson. Thanks for the clarification. So how does the framework recognise a method as a handler in the implicit case?

@Glen. I'm looking forward to seeing OWIN in WCF :)

@Ken. That's a great name :) Sorry I missed it.

Reshef Mann said...

Just wanted to point out ServiceStack

Andreas HÃ¥kansson said...

Hi

Great post. We already have Nancy running on WCF as well and the code should work out of the box on mono (haven't had time to try it out, but MoMA doesn't report any issues).

Nancy will be running on OWIN and if someone would be interested in contributing the feature, drop me a line on my twitter account @TheCodeJunkie. It really shouldn't be much work since Nancy is already running on its own set of abstractions so all that would be needed is an OWIN adapter

We also plan on supporting more hosts like the WCF HTTP stuff that Glenn Block is working on. The hosting model for Nancy is pretty easy and I'm sure we could get it running on most things, all you have do is fork, commit, push, pull request and we'll suck it right in! ;)

Thanks!

Mike Hadlow said...

@Reshef, Yes ServiceStack is nice, I would have included it if I'd know about it :p

@Andreas, thanks! I'm really looking forward to seeing convergence around OWIN FTW :)

Martin said...

Mike,

Here's another shameless plug. Tinyweb is a toy framework (sitting on ASP.NET) I wrote with the aim of being really simple and small.

Introduced here: http://invalidcast.com/2010/12/my-new-black

Dotan Nahum said...

Hi Mike,
Nina's name reference is a couple of letters extracted from 'NSinatra' which i thought to be too long by itself :)
eventhough premature optimization is the root of all evil, one of Nina's goals is to have high performance, simply since this was the main requirement when building it -- to have a simple restful handler with little moving parts.