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.
Recently, I discovered MediatR. At its simplest, it sends a request through a handler that returns a response.
You use the above handler like so:
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:
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.
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:
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:
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:
If you load up “/hello” in a browser, you should now see “Hello!”, while you’ll still see your earlier console message.
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:
And finally, a functional, self hosted example using SimpleInjector for the container.
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.
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…