Running multiple ASP.NET Web API pipelines side by side

Over the past 4 years or so, I have worked on many Web API projects, for a lot of different clients, and I thought I have seen almost everything.

Last week I came across an interesting new (well, at least to me) scenario though – with the requirement to run two Web API pipelines side by side, in the same process. Imagine having /api as one Web API “instance”, and then having /dashboard as completely separate one, with it’s own completely custom configuration (such as formatter settings, authentication or exception handling). And all of that running in the same process.

More after the jump.

The problem

On IIS, normally you would solve it having to separate web application assemblies and deploying them into different virtual directories. It gets more interesting, if you try to do this in the same process though.

Sure, this is what OWIN allows you to do – run multiple frameworks or “branches”, side by side. But turns out this is not that easy with the Web API OWIN integration.

Normally you’d go about doing this the following, right?

Seems reasonable, typical usage of the OWIN middleware architecture.

The problem with this set up is that Web API doesn’t necessarily lend itself very well to such set up. This is mainly related to the way Web API discovers controllers and routes.

Since you want to have two pipelines side by side, in the same assembly, you want to have only specific controllers visible to specific pipeline.

By default Web API will look throughout the entire AppDomain for controllers and attribute routes, meaning that both of your pipelines will discover everything and you will end up with a huge mess.

The solution

In order to address that you can define the convention to divide your controllers into specific “areas” or, as we called it from the beginning, pipelines.

Then you can tell Web API configuration object, to only deal with this specific subset of types when discovering controllers and mapping attribute routes. This can be achieved in several ways – namespaces, attributes and so on. The simplest of which is probably using a base marker type though.

Let’s introduce two base controller types representing two different Web API pipelines that we defined earlier – /api and /dashboard.

Now we need to be able to set up HttpConfiguration, that’s used to define our Web API server, to respect these base controllers when looking for types that should be used as controllers and when discovering attribute routes.

This can be done by introducing a custom IHttpControllerTypeResolver and IDirectRouteProvider, that would only work with controllers that subclass a specific base class. These implementations are shown below.

In both cases, we are extending the default implementations – DefaultHttpControllerTypeResolver and DefaultDirectRouteProvider.

While Web API will still treat all AppDomain types as potential “controller” candidates, in TypedHttpControllerTypeResolver we use the same default constraint to discover controllers (must be class, must be public, must not be abstract) that Web API uses, but additionally we throw in the requirement to subclass our predefined base controller.

Similarly, for TypedDirectRouteProvider, Web API will inspect all controllers in the AppDomain, but we can define that routes should be picked up only from those controllers that extend our predefined base.

The final step is just to wire this in, which is very easy – the revised Startup class is shown below.

And that’s it! No more cross-pipeline conflicts, and two Web API instances running side by side.

Share the post!

  • abatishchev

    I think it would better to match by namespace, or keep controllers in 2 different assemblies.

    • Filip W

      this can be achieved in several ways, base type is just an example

  • Tor Christian Angeltveit

    Thank you. Saved my day :)

  • Jeff S

    Aren’t you calling MapHttpAttributeRoutes twice per config in this case? Once with a resolver, and once with no param. Wouldn’t this cause duplicate key errors?

  • Harry McIntyre

    Is there a way to do this for core?

  • Steve A

    How would you achieve this using the GlobalConfiguration.Configure(WebApiConfig.Register) method?