Introduction
No, there’s no flying pigs, and Satan isn’t wearing a woolly hat, but this is a post about WebAPI (boooo!) I hit an “interesting” issue today, and felt a blog post was in order in case anyone else is bitten by this rather annoying “feature”.
I added a very simple route definition:
config.Routes.MapHttpRoute( name: "foo", routeTemplate: "{controller}/{name}", defaults: new { name = RouteParameter.Optional }, constraints: new { controller = "foos" });
And a very simple controller:
namespace Bar { using System.Web.Http; using Bar.Hypermedia; public class FoosController : ApiController { private readonly IFooService fooService; public FoosController(IFooService fooService) { this.fooService = fooService; } [HttpGet] public IEnumerable Root() { return this.fooService.GetFoos(); } [HttpGet] public Foo Root(string name) { return this.fooService.GetFoo(name); } } }
It couldn’t get much more simple – just a controller that returns a collection of Foos, or a single Foo, depending on whether the name of the Foo was specified. I fired up curl and getting a single Foo was fine, but browsing to /foos/ came back with:
HTTP Error 403.14 – Forbidden
The Web server is configured to not list the contents of this directory.
Err.. what?!
The Solution
The solution was easy – just ask a friendly neighbourhood ASP.Net WebAPI MVP to fix it for me 😉 Filip stepped up and confirmed all my code was fine, but after some head scratching noticed that I had my FoosController.cs inside a folder called Foos, and renaming that folder made the route work fine. It seems that if IIS/Asp.Net sees a folder that exactly matches the URL you’re asking for (/foos in this case) then it takes over and ignores any routes you have setup. Renaming folders would be a major pain in the backside, but luckily you can disable this behaviour by adding one line to the global.asax:
public class WebApiApplication : HttpApplication { protected void Application_Start() { // Stop IIS/Asp.Net breaking our routes RouteTable.Routes.RouteExistingFiles = true; WebApiConfig.Register(GlobalConfiguration.Configuration); } }
So many thanks to Filip for helping me solve this one – I’m sure I’d have lost time, and hair, trying to figure it out on my own. I’d consider this behaviour to be a bug, but I’m sure it’s actually a feature in someone’s mind. If only there was a better way to write HTTP APIs in .net…. 😉