But I don’t want to call Web Api controllers “Controller”!

A friend of mine was recently complaining about how Web API controller centric approach doesn’t really make sense, and that he prefers feature-oriented endpoints. While – in all honesty – I am not sure what he means by that, it got me thinking. Maybe, if we didn’t have to suffix Web API controllers “Controller”, that would make him happy?

More after the jump.

Why do I have to suffix my HTTP endpoints with “Controller”?

Let’s start by answering this simple question – why is this magical string “Controller” needed? Actually, the answer is as simple – because it’s hardcoded into the Web API source. The string “Controller” is checked against class names when trying to extract controller types from your API assemblies.

In fact, when discovering controllers, Web API will require the following:

  • 1. implements IHttpController
  • 2. is a public, non-abstract class
  • 3. has a “Controller” suffix

That of course, doesn’t mean we cannot change that.

Changing API controllers

Web API exposes for us a nice little service called IHttpControllerTypeResolver (which can be implemented from scratch) and it’s default implementation, DefaultHttpControllerTypeResolver (which can be partially overriden).

The resolver has a predicate it uses to determine controller types. We can inherit from the default service and create our own version.

Here is the default one, corresponding to the condition I just mentioned a moment ago:

Well, our custom one might be:

So, we get rid of any suffixing – any class name can be an HTTP endpoint – but instead we force the types to inherit from ApiEndpoint which is a theoretical base class we want to force all our API developers to implement. This base class should obviously inherit from ApiController, so that we can use the Web API pipeline correctly.

(This is just an example, of what you can do, and there is some value to it – for example I personally am forcing everyone around me to base the API off our base RestController<T>).

Of course the static method needs to sit inside some class (which, as you might expect should inherit from DefaultHttpControllerTypeResolver, since I just set we would be modifying it):

The service should be registered against your configuration

It’s almost ready now, expect we have to change one more thing, and that I suspect is an oversight from the Web API team. There is a class DefaultHttpControllerSelector, which stores a hardcoded controller suffix, “Controller”, in a public static but readonly variable.

The problem is, this value is used when caching resolved controllers, in an arbitrary substring inside an internal HttpControllerTypeCache class, in a little LINQ grouping:

Since we don’t want to:
a) reimplement an internal type responsinble for caching (insane) to remove that..
b) ..we could just reset the static suffix to string.empty. However, we don’t want to reimplement DefaultHttpControllerSelector just to be able to reset a readonly field

Then we can use a bit of reflection to save us time & effort. Notice this is a static field so it needs to be changed just once, so it’s not an issue to use a bit of reflection on application startup.

So somehwere inside our WebApiConfig or in a Main (if you self host) we could do this:

We might as well introduce a new suffix – and make sure it lines up with our precondition to discover controllers. It could be “Endpoint” or “Service”, or, for what it’s worth “Monkey”.

But since we set it to an empty string, we ignore any suffixes whatsoever.

Running it

Now, based on our new rules, if I create a new controller, I need to inherit off ApiEndpoint (could be, again, some specific ApiController variation for our team i.e. including base logging or whatnot) and I can name it any way I want.

Ok, so – and this is a message for my friend, and you know I’m talking to you, here you got ApiControllers, without “Controller”. In fact you can use a suffix “Feature” if you want :-)

Be Sociable, Share!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1293()

  • http://twitter.com/sbohlen Steve Bohlen

    Since ASP.NET MVC (and WebAPI) is OSS, have you considered initiating a pull request to ‘properly’ solve the oversight of the hardcoded setting in DefaultHttpControllerSelector? I’m betting they would accept a fix for this (and it would be great to get this corrected before MVC5 gets out the door!

    • http://www.strathweb.com/ Filip W

      ha! I think I’ll do that :)

  • http://twitter.com/jessewilliamson jessewilliamson

    The MVC/WebApid naming convention never made any sense to me when you also consider the inheritance chain. I’m already stating this is a Controller/ApiController, what’s the point of the class name constraint? Anyway, nice post, as usual.

    • http://www.strathweb.com/ Filip W

      yes I agree.

      Convention is often powerful, but here, like you mention, since we already implement a controller interface, why force the name too?

      Not to mention, in a single MVC/WebAPI project one can often end up with a lot of classes named the same (i.e. ProductController for both MVC/Web API) which makes it somewhat difficult to maneuver through files in Visual Studio tabs and so on.

      • JordanM

        The real problem is that the interface shouldn’t be called Controller either! Its isn’t a controller at all in the sense of a MVC controller. A much better name for the interface would be IHttpResource or something similar.

  • Pingback: Liens de la semaine #19 | FrenchCoding()

  • JordanM

    Great post. “Controller” is a really bad choice of name. It isn’t a controller!

  • http://www.zainrizvi.me/ Zain Rizvi

    This is a great post, but to not have to name your controllers “xyzController” you really just need two lines from the post (it took me a while to realize that):

    var suffix = typeof(DefaultHttpControllerSelector).GetField(“ControllerSuffix”, BindingFlags.Static | BindingFlags.Public);
    if (suffix != null) suffix.SetValue(null, string.Empty);

    Everything else is just extra configuration