Over the break, I wanted to build a quick nutrition web app.

My normal go-to framework for web applications is Nancy. It’s pretty lean and easy to get going, but… lately it’s been feeling rather heavy. It has a lot of features I never use (razor views, for instance), and I end up building out a good bit of bootstrap code to turn it all off and manually configure the bits I want.

So… what to do…

MediatR

Recently, I discovered MediatR. At its simplest, it sends a request through a handler that returns a response.

1
2
3
4
5
6
7
public class Ping : IRequest<string> { }
public class PingHandler : IRequestHandler<Ping, string> {
public string Handle(Ping request) {
return "Pong";
}
}

You use the above handler like so:

1
var pong = mediator.Send(new Ping()); //returns "Pong"

What’s interesting about MediatR is all the configuration magic happens at your IoC container. Handlers are generated by the container, injecting any dependencies. The request and response objects never have to know anything about the handler in-between.

And infrastructure and other concerns can be moved to decorators that your original code never has to know about:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LoggingDecorator<TRequest, TResponse>
: IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
IRequestHandler<TRequest, TResponse> inner;
public LoggingDecorator(IRouteRequestHandler<TRequest, TResponse> inner)
{
this.inner = inner;
}
public TResponse Handle(TRequest message)
{
var sw = Stopwatch.StartNew();
var results = this.inner.Handle(message, cancellationToken);
sw.Stop();
Console.WriteLine(String.Format("Processed {0} in {1}ms", message.ToString(), sw.ElapsedMilliseconds));
return results;
}
}

Handlers like the above LoggingDecorator can ‘decorate’ handlers, wrapping them for all sorts of things - validations, logging, authorizations, etc.

I won’t go much more into this, but check out Jimmy Bogard’s blog post “Tackling cross-cutting concerns with a mediator pipeline” for more information on using this pattern.

Owin

After using MediatR for a bit, I realized that it covered the majority of what I wanted a web framework to do. It sent requests, handled them, and returned responses. It was just missing the ‘web’ part. How hard could it be, right? Surprisingly, it turns out to be not really all that hard.

You’ve probably heard of “Owin”, but might not know anything about it other than it’s ‘the next thing!’ or slightly more useful, it’s an “Open Web Interface for .NET”.

If you’ve worked with nodejs for a web app, you’re probably familiar with their middleware layers. It allows you to process an HTTP request through a pipeline, where each ‘layer’ can do whatever it needs to with the request - respond, log, etc. - all in a standardized way. Owin brings that to .NET. And combined with async/await, it’s quite easy to create your own middleware.

The quickest way to get going with Owin is to use the Microsoft’s Owin nuget packages. They provide a light layer on top of the base Owin requests api and server packages. Here’s an empty, self hosted web server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Owin;
using System;
public class Program
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
}
}
public static void Main(string[] args)
{
var url = "http://localhost:12345";
using (Microsoft.Owin.Hosting.WebApp.Start<Startup>(url))
{
Console.WriteLine("Listening at " + url);
Console.ReadLine();
}
}
}

If you run this example, you’ll see it doesn’t really do anything. Any request you run against the server will just come back 404 not found.

So, let’s add something to the Configuration function:

1
2
3
4
5
app.Use(async (context, next) =>
{
Console.WriteLine("Something is happening!");
await next();
});

Now if you run the example and request something against the server, you’ll still get 404, but you’ll also get that console message. You’ve successfully created your first Owin middleware!

So… what’s going on?

The app.Use call is defining an async function that runs when a request comes in. Each request will go through that function. You can have multiple ‘Use’ functions that are called in order when a request comes in, with each one calling the “next()” function in the pipeline. The context passed contains the information about the request - the headers, the content and response streams - all the normal things you’d expect from a web request.

Let’s add another, more complicated middleware that actually does something:

1
2
3
4
5
6
7
8
9
10
11
12
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/hello")))
{
using (var writer = new System.IO.StreamWriter(context.Response.Body))
{
await writer.WriteLineAsync("Hello!");
}
context.Response.StatusCode = 200;
}
await next();
});

If you load up “/hello” in a browser, you should now see “Hello!”, while you’ll still see your earlier console message.

PingPongr

That brings me to PingPongr, the product of all this discovery.

The PingPongr nuget package has only one dependency: the core Owin package.

Here’s an example API that should look pretty familiar after seeing the earlier mediator code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using PingPongr;
using System.Threading;
using System.Threading.Tasks;
//A request is unique per route
public class Ping : IRouteRequest<Pong>
{
public string Hi { get; set; }
}
//responses can be shared between routes
public class Pong
{
public string Reply { get; set; }
}
public class PingHandler : IRouteRequestHandler<Ping, Pong>
{
public async Task<Pong> Handle(Ping message, CancellationToken cancellationToken)
{
return await DoSomethingCoolAsync();
}
}

And finally, a functional, self hosted example using SimpleInjector for the container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using Owin;
using PingPongr;
using PingPongr.OwinSupport;
using SimpleInjector;
using System;
public class Program
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
//setup the container
var container = new Container();
var assemblies = new[] { typeof(Program).Assembly };
//register all the route request handlers
container.Register(typeof(IRouteRequestHandler<,>), assemblies);
container.Verify();
//an instance factory is how request handlers are built from the container.
var factory = new InstanceFactory(container.GetInstance);
//the lib comes with a json media handler built with SimpleJson
var mediaHandlers = new[] { new DefaultJsonMediaHandler() };
//the routes are found using reflection via the RouteBuilder
var routes = RouteBuilder.WithAssemblies(assemblies).GetRoutes();
foreach (var r in routes) Console.WriteLine(r.Path);
//setup the PingPongr router
//(Note: we're setting this up manually, but it could be created by the container)
IRouter router = new Router(
routes,
mediaHandlers,
factory
);
app.UsePingPongr(router);
}
}
public static void Main(string[] args)
{
using (Microsoft.Owin.Hosting.WebApp.Start<Startup>("http://localhost:12345"))
{
Console.ReadLine();
}
}
}

A few things to note:

  • The InstanceFactory works exactly like MediatR’s SingleInstanceFactory, so the container examples from the MediatR project can be used directly.
  • The RouteBuilder object does a bit of reflection to discover possible routes (IRouteRequests) and cache them for use in the router.
  • Any HTTP method is accepted on a matching route. Route matching is strictly by the path.
  • All handlers are async. Welcome to the future ;).

Over all, the library ended up quite lean and an easy read if you’re curious about the inner workings. The Tests and Sandbox example projects should also help.

Future

The package is still currently marked as pre-release until it’s seen some real world use. At the very least, the default JSON implementation needs some tweaks to check a broader range of request media types.

At some point I’d also like to build a standard TCP/IP server for the library as an alternative to Owin making the library even more useful for inter-process communication for micro-services.

My hope is to keep the core library very lean, though. The less it’s doing, with the least amount of magic, the more maintainable it is for me (and you) to use in projects in the long run.

Oh, also in the future, I hope to complete that nutrition app I talked about…

comments powered by Disqus