Building microservices with ASP.NET Core (without MVC)

There are several reasons why it makes sense to build super-lightweight HTTP services (or, despite all the baggage the word brings, “microservices”). I do not need to go into all the operational or architectural benefits of such approach to system development, as it has been discussed a lot elsewhere.

It feels natural that when building such HTTP services, it definitely makes sense to keep the footprint of the technology you chose as small as possible, not to mention the size of the codebase you should maintain long term.

In this point I wanted to show a couple of techniques for building very lightweight HTTP services on top ASP.NET Core, without the use of any framework, and with minimal code bloat.

Prerequisites

What I’ll be discussing in this article is based on ASP.NET Core 1.2 packages which at the time of writing have not shipped yet.

I am using the CI feed of ASP.NET Core, so my Nuget.config looks like this:

When version 1.2 ships to Nuget, this will no longer be required.

ASP.NET HTTP endpoints without MVC

ASP.NET Core allows you to define HTTP endpoints directly on top of the OWIN-like pipeline that it’s built around, rather than using the full-blown MVC framework and its controllers to handle incoming requests. This has been there since the very beginning – you could use middleware components to handle incoming HTTP requests and short circuit a response immediately to the client. A bunch of high profile ASP.NET Core based projects use technique like this already – for example Identity Server 4.

This is not a new concept – something similar existed (albeit in a limited fashion) in classic ASP.NET with HTTP modules and HTTP handlers. Later on, in Web API you could define message handlers for handling HTTP requests without needing to define controllers. Finally, in OWIN and in Project Katana, you could that by plugging in custom middleware components too.

Another alternative is to specify a custom IRouter and hang the various endpoints off it. The main difference between this approach, and plugging in custom middleware components is that routing itself is a single middleware. It also gives you a possibility for much more sophisticated URL pattern matching and route constraint definition – something you’d need handle manually in case of middlewares.

ASP.NET Core 1.2 will introduce a set of new extension methods on IRouter, which will make creation of lightweight HTTP endpoints even easier. It would be possible to also polyfill earlier versions of ASP.NET Core with this functionality by simply copying these new extensions into your project.

Setting up the base for a lightweight HTTP API

Here is the project.json for our microservice. It contains only the most basic packages.

We are using the bare minimum here:

  • Kestrel and IIS integration to act as server host
  • routing package
  • logging and configuration packages

In order to keep our code to absolute minimum too, we can even ditch the Startup class concept for our API setup, and just do all of the backend API code in a single file. To do that, instead of hooking into the typical Startup extensibility points like Configure() and ConfigureServices() methods, we’ll hang everything off WebHostBuilder.

WebHostBuilder is quite often ignored/overlooked by ASP.NET Core developers, because it’s generated by the template as the entry point inside the Program class, and usually you don’t even need to modify it – as it by default points at Startup class where almost all of the set up and configuration work happens. However, it also exposes similar hooks that Startup has, so it is possible to just define everything on WebHostBuilder directly.

Our basic API configuration is shown below. It doesn’t do anything yet in terms of exposing HTTP endpoints, but it’s fully functional from the perspective of the pipeline set up (router is wired in), logging to console and consuming configuration from JSON and environment variables.

I love this approach because it’s so wonderfully concise. In roughly 20 lines of code we have an excellent base for a lightweight HTTP API. Naturally we could enrich this with more features as need – for example add in your own custom services or add token validation using the relevant integration packages from Microsoft.Security or IdetntityServer4.

Adding HTTP endpoints to our solution

The final step is to add our HTTP endpoints. We’ll do that using the aforementioned extension methods which will be introduced in ASP.NET Core 1.2. For demonstration purposes we’ll also need some sample model and emulated data, so I’ll be using my standard Contact and ContactRepository examples.

The code below goes into the Configure() extension method on the WebHostBuilder as it was noted before already. It shows the HTTP handlers for getting all contacts and getting a contact by ID.

This code should be rather self descriptive – we delegate the look up operation to the backing repoistory, and then simply write out the result to the HTTP response. The router extension methods also gives us access to route data values, making it easy to handle complex URIs. On top of that, we can use regular ASP.NET Core route templates with all the power of the constraints which is very handy – for example, just like you’d expect, contacts/{id:int} will not be matched for non-integer IDs.

Additionally, I helped myself a bit by adding a convenience extension method to write to the response stream.

The final step is to add in the HTTP endpoints that would modify the state on the server side:

  • POST a new contact
  • PUT a contact (modify existing)
  • DELETE a contact

We’ll need an extra extension method to simplify that – that is because have to deserialize the request body stream into JSON, and we’d also like to validate the data annotations on our model to ensure the request payload from the client is valid. Obviously it would be silly to repeat that code over and over.

This extension method is shown below, and it makes use of JSON.NET and the System.ComponentModel.DataAnnotations.Validator.

Notice that the method will short-circuit a 400 Bad Request response back to the client if the model is not valid (for example a required field was missing) – and it will pass the validation errors too.

The HTTP endpoint definitions are shown next.

And that’s it – you could improve this further by adding for example convenience methods of reading and casting values from RouteDataDictionary. It is also not difficult to enhance this code with authentication and even integrate the new ASP.NET Core authorization policies into it.

Our full “microservice” code (without the helper extension methods, I assume you’d want to centralize and reuse them anyway) is shown below – and I’m quite pleased with the result. I find it a very appealing, concise way of building lightweight APIs in ASP.NET Core.

The full source code is here on Github.

Be Sociable, Share!

  • Dan

    Awesome!
    You are the man!

  • http://stuffleftatparty.com/ Krzysztof Zmorzyński

    This looks nice.

    But on the other hand I’d argue, that NancyFx setup and syntax is a little bit easier.

  • http://blog.mantziaris.eu/ Sotirios Mantziaris

    Do we have at this point DI capabilities?

    • nilphilus

      Yes, becouse why not? even simple ‘Console App’ is DI-cappable

  • http://www.ifacture.fr/ Franck Quintana

    Smart, concise and beautiful.

  • jon49

    Reminds me of simple Node.js

  • http://www.nimaara.com/ Nima Ara

    How would you get hold of: IHostingEnvironment and ILoggerFactory in the Configure delegate? As they can’t be requested from the DI as one could using the Configure method in a Startup (or other) class.

  • Joshua Drake

    Small correction in the 2nd paragraph under _ASP.NET HTTP endpoints without MVC_ “you could that by ” appears you are missing a word “you could do that by “. (No need to approve, just an easy way to note it without hunting down a direct message channel.)

  • Giorgos Papadakis

    like NancyFx

    • Abhijeet

      Surprising Nancy never got the traction / recognition it deserved!

  • http://abatishchev.ru abatishchev

    Why do you write json to response explicitly rather than rely on client’s Accept header?

  • https://www.docati.com Robert te Kaat

    How much lighter is this, compare to a regular webapi? This code seems to be doing much of the same stuff, only the options are limited.