I want to cover some of the common reasons that you’ll get back some 4xx error codes from your API response to serve as a checklist and hopefully eliminate some debugging time.
1. Invalid Route
This is an obvious one – check that you are calling a route that exists. You probably already know by default (if you haven’t touched the route configuration) it will look for a word suffixed by controller in your class that inherits from ApiController (technically you need a class that implements IHttpController and IDisposable’s ExecuteAsync and Dispose, respectively). If I have controller called EntityController and I issue a
GET as /api/entity/1 then it will work, but if I tried ‘api/foobar’ or ‘api/bar’ it would not; unless I specify a route prefix on the controller to use explicitly use foobar, along with a route on the action. Make sure the route template defined matches up with the call you’re making (by default you have a /api/ in the URL, and if you’re testing this locally and didn’t do anything special, you’ll probably need to specify a port as well).
Let’s look at some examples:
With a GET issued to api/entity, I would get a 200 for this block of code:
The Web API framework tries to identify the action by default by looking at the action specified in the request, and matching it to the methods within the controller. If the corresponding HTTP attribute is decorated on the action it will be more precise to match. It will also look at any route template and parameters to help decide on matching (passing in DTOs, etc.) If any of your methods contain the word ‘get’, then it will match on that method for an HTTP GET. If nothing matches (you’ve really confused it) then it will fall back to POST (only HTTP posts will be honored, everything else will 405).
So given this code:
The following behaviors occur:
POSThttp://localhost:5430/api/entity/B – this will 200 because it matched on B
POSThttp://localhost:5430/api/entity/A – this will 200 because it matched on B (even though we used the word A, that’s irrelevant because it matched on the HTTP verb attribute first)
GEThttp://localhost:5430/api/entity/A – 200 (matched on the first method A with [HttpGet])
GEThttp://localhost:5430/api/entity/B – 200 (again, matched on the first method A with [HttpGet])
DELETEhttp://localhost:5430/api/entity/B – 405 (method not allowed as there is nothing specified for delete).
Now what if we switched the
[HttpPut] for B() above and issued a
POST again? It would 405 as the only actions available have been accounted for with HTTP attributes decorated on them (in this case
If, however, we introduced a new method inside of this controller:
and issued a
POST? It would match on Blah() because one method hasn’t been accounted for and it defaults to
Now regarding route prefixes, let’s see some other cases where it could 404:
GET issued to api/foobar, I would get a 404 for this block of code:
GET issued to api/foobar, I would get a 200 for this block of code:
Note the Route established on the action. You don’t have to specify one unless you want to. The method is named Get, but it could be anything you want now that we have a clear definition on the controller by way of it’s RoutePrefix and Route. The HTTP verb is specified as well to help resolve ambiguity should I need to create more gets.
2. Controller Class – Correct Inheritance?
A few times I’ve seen cases where someone mistakenly created the controller by hand and forgot to ensure it inherits from
ApiController! Make sure your class looks something like this:
I saw two other cases where some problems occurred at a client that I wanted to share. The first was where there was some mingling of MVC and Web API in a way where the developer had the code inherit from Controller (MVC) instead of ApiController. Another time, the project has a lot of controller inheritance (A controller inherits from a base controller that itself then inherits from ApiController). Sometimes common functionality in the base controller is required and your team may forget to specify that their new controller inherits from the base controller. It may not always throw an error or be obvious and you can end up spending time trying to figure out why something is null.
3. Route Template & Configuration
Sometimes the routing templates get misconfigured which can result in a 404. It might help if you push the reset button temporarily (by removing your code in the
WebApiConfig and replacing it with the default Web API code) to make sure the controller can be found. Sometimes I’ve seen people setup the project where .NET lets you configure it for both MVC and Web API and the using statements or route templates get confused with MVC and things go haywire. As a reminder, this is the default for Web API:
4. Parameter Binding & Attribute Routing
These next few are easy to avoid but can happen to the best of us sometimes. Let’s say you have a block of code in your controller like this:
Web API will attempt to bind the value passed in the route after foo (i.e. foo/1) to the parameter in the method signature (testField). The caveat is that these names must match, however, and so if your parameter name does not match the route’s parameter name, then it will return a 404 to you.
This code would then work:
Because the parameter field in the route matches the parameter field in the method signature. Another thing to be aware of is if the data type does not match, then it will return a 404 as well. So passing in a value of 1A or HelloWorld would result in a 404 (i.e. /api/foo/hello).
5. Unsupported Media Type
Content negotiation is the process of selecting the best representation for a given response when there are multiple representations available (RFC 2616). Getting a 415 is very common when you’re using something like Fiddler to test out your application and you think the JSON body you’re passing matches up with the DTO / Model, yet you get a 415 Unsupported Media Type. Content-type is defined in RFC 2616 as indicating “the media type of the entity-body sent to the recipient or, in the case of the HEAD method, the media type that would have been sent had the request been a GET”. So for example with an
HttpPost let’s say I had the following bit of code in my controller:
If I made a call to Web API and failed to specify the content-type (let’s say I was passing JSON in the body), depending upon whether I defined any custom media type formatters (Web API ships with formatters for XML, JSON, BSON, and form-urlencoded data but you can create custom ones as well) I may get an error. Let’s take a vanilla Web API project and try the following call in Postman:
You’ll notice I left out the content type. When the call is made, the following error occurs:
As you can see, the rest client defaulted to text/plain and there was no MediaTypeFormatter registered in WebApi to process this sort of content-type, thus it failed. This is very common to forget in a REST client for testing or even in jQuery or some other front-end technology when making a call. For example:
Note the passing of the accept and content type headers as application/json during this request to my web API.
There are a myriad of other cases where 4xx responses could occur and you probably noticed the lack of coverage on other obvious ones such as a 401 when you don’t have the correct access. I felt that was a particularly easy one to solve (you didn’t pass a web token, or you’re not authorized to access a resource) and wanted to focus on some more typical ones when a developer is first coming on to a Web API project and is frustrated at getting a 4xx without much explanation by the framework. By far the most common reason I’ve seen is that some configuration was not correct, almost always in WebApiConfig or some startup where the route was changed or MVC dlls somehow were not playing nicely, so be sure to check that especially if your team has made any customization to the route template!
If you have some particularly frustrating 4xx scenarios you wanted to share, or had some suggestions for improvement, I’d love to hear it! Please feel free to get in touch with us.