Dynamic per-controller HttpConfiguration in ASP.NET Web API - StrathWeb

Strath

September 29th, 2013

Dynamic per-controller HttpConfiguration in ASP.NET Web API

Cause it doesn't have to be done only through attributes

Recently I faced an interesting problem, where we needed to provide controllers with controller-specific configuration – but based on settings only known at runtime.

In Web API, per-controller configuration is a very useful, yet little known feature (aside from a great blog post by Mike Stall), as it allows you to create configuration profiles and assign them to specific controllers.

However it is only supported statically – through attributes, so it cannot be altered at runtime. Let’s have a look at how you might be able to hack away at it.

Controller configuration – the classic way

In a traditional per controller configuration, you have to implement an IControllerConfiguration interface as an Attribute and decorate a given controller with it.

For example:

What Web API pipeline would do, is that in the HttpControllerDescriptor, the first time a given controller is accessed, it would look for the per controller configuration attributes on it in a private method:

It would then copy the default configuration (pointing to the global HttpConfiguration) and merge its ParameterBindingRules, Services and Formatters with the ones supplied through the custom configuration (or rather, through HttpControllerSettings).

Unfortunately the fact that it’s only applicable through an attribute, prevents us from providing this controller-scoped configuration at runtime. You could add attributes at runtime through TypeDescriptor but they would have to be retrieved through it too; Web API reads them through the good old GetCustomAttributes() instead, and that means the config has to be applied at compile time.

Controller configuration – the dynamic way

This is a hacky way, but I feel like it’s justifiable. It all comes down to accessing a private constructor of HttpConfiguration – the one that is responsible for merging the old configuration and HttpControllerSettings and producing a controller-scoped configuration.

We could do that through reflection and create a custom extension method. Mind you, we’ll cache the config afterwards so we can take the performance hit once.

In this method we create new instance of HttpControllerSettings and apply the changes provided by the configuring user. Then we invoke a private constructor of HttpConfiguration that creates a configuration object adjusted by the HttpControllerSettings

Next, we need some interafce/storage to configure the controllers, for example a dummy Dictionary would be enough for this demo:

In the configuration of Web API we could now dynamically add controllers with settings to it. For example, let’s imagine TestController should use only JSON, then we simply register that in the mapping:

Of course, this will still not work, because we don’t invoke our new configuration extension method anywhere, and that is the last piece to our puzzle – a customized IHttpControllerActivator.

What it does is it will check if the per-controller configuration mapping contains an entry for the current controller, and if it does, apply the custom config on the HttpControllerDescriptor. Then it will simply use Web APIs DefaultHttpControllerActivator to create an ApiController instance.

Since we don’t want to pay the reflection penalty, we also cache the configuration (we cannot cache ApiController as Web API would error out! they are nto reusable). At this point you may want to implement some logic to invalidate the cache under your own specific conditions or scenarios.

You now need to register the activator:

From now on, navigating to TestController will use the config with JSON formatter only. All other controllers, still use the global configuration.

Summary

While this is a very hacky way to approach the problem, it does solve a considerable limitation in Web API, and allows you to apply custom configuration at runtime.

This might be something the team would like to look at, and allow more flexible way of applying per-controller configuration, rather than just through attributes.

Be Sociable, Share!