![]() |
This is really 502 - Bad Gateway I'm pretty sure it's not the cat's fault. |
EDIT: I actually recommend against a lot of what I'm saying here now. Web API endpoints shoudl always be returning HttpResponseMessage, and those messages could always be created by Request.CreateResponse(); I'll write more on this later.
So it seems to me that not a lot of people have figured out what they should be doing when they want to throw an error from their Web API, but give something back to the client that contains some sort of information about what happened. If you're here you might be here because you've realized that simple throwing any old exception from your Web API results in a "500: Internal Server Error" with exactly nothing in the body of the response that might explain to the client what went wrong.
There are a few things at play here. The quick and dirty version is you're probably throwing the wrong type of exception, and returning the wrong type of status code. Let me explain:
You need to be throwing HttpResponseException
When you throw just any old error, ASP.Net interprets that as an error in the operation of your web application. In other words, it thinks (and rightfully so) that you've experienced an "internal server error". As such, it just sends out a 500 error. You might think, "Well great, but why doesn't it send out the message in my exception? Why can't they do that for me? Shouldn't that be done for me?" Well, actually no, you don't want to send an explanation with a 500 error, but I'll get to that.
If you throw an HttpResponseException, ASP.Net knows that you're attempting to throw an error that you'd like to communicate back to the client with specific information. This, however, does not mean that you should use a 500 error, or that there were all of a sudden be a body in with that 500 error. Because there shouldn't be.
You should be returning 4XX errors, not 5XX errors
The reason the developers of this technology opted not to send the contents of your exception out with a 500 error is because 5XX errors are meant to be sent out as a notification that an error occurred on the server, and there's nothing the client can do about it. Think of it like that annoying "engine maintenance" light on the dashboard of your car, if you're getting that, just pounding on the gas harder isn't likely to do anything to fix the issue. It's an internal issue.
400 errors are your friends. 4XX errors are there to state that an error occurred and there's something the client can do about it. There is a whole list of them, but the two you'll probably need the most are 404 and 400. 404 everyone knows... "not found". This can be returned when content isn't found. For example if a user queries from some widget by id, and the id is invalid... your app didn't find it, so 404. 400 is for a bad request. That means the server got the request okay, but there was something wrong with it and it shouldn't b e resent unless something about it changes. (I'm sure somewhere the authors of RFC 2616 want to slit their wrists after reading my grotesque butchery of their words).
For a more complete list read the definitions yourself. Just be sure to actually read the definitions, some of them have rather generic sounding names but very specific meanings. For example, "406 - Not Acceptable" which actually means that the type requested in the Accept header isn't something the server can return, so if the client requested "text/fibbertyjibbets" rather than "application/json" they're S.O.L. and they're going to get a 406. Anyhow, the point is, read the definitions before you use them. Odds are, most of the time, 400 and 404 will do.
So 4XX errors will actually return a reason along with them, as well as a body that offers some explanation. This is way better than a 500: Internal Server Error with no explanation. And it's also the more correct response.
But how to do this in ASP.Net Web API?
Since ASP.Net Web API can return a variety of content types: XML and JSON for example, I think the most appropriate thing to do is return a very simple string with the basic reason in it. It can be interpreted easily by any client, and it's simple to implement.
What I did was add the following methods to my custom ApiController base class. Then when I needed to use them, I basically call:
throw Conflict("Invalid somethingerother");
or throw NotFound("Record not found");
This does everything I need it to do to return something valid to the client.
/// <summary> /// creates an <see cref="HttpResponseException"/> with a response code of 400 /// and places the reason in the reason header and the body. /// </summary> /// <param name="reason">Explanation text for the client.</param> /// <returns>A new HttpResponseException</returns> protected HttpResponseException BadRequest(string reason) { return CreateHttpResponseException(reason, HttpStatusCode.BadRequest); } /// <summary> /// creates an <see cref="HttpResponseException"/> with a response code of 404 /// and places the reason in the reason header and the body. /// </summary> /// <param name="reason">Explanation text for the client.</param> /// <returns>A new HttpResponseException</returns> protected HttpResponseException NotFound(string reason) { return CreateHttpResponseException(reason, HttpStatusCode.NotFound); } /// <summary> /// Creates an <see cref="HttpResponseException"/> to be thrown by the api. /// </summary> /// <param name="reason">Explanation text, also added to the body.</param> /// <param name="code">The HTTP status code.</param> /// <returns>A new <see cref="HttpResponseException"/></returns> private static HttpResponseException CreateHttpResponseException(string reason, HttpStatusCode code) { var response = new HttpResponseMessage { StatusCode = code, ReasonPhrase = reason, Content = new StringContent(reason) }; throw new HttpResponseException(response); }
Here's an example of one of the above methods in use:
public Widget GetById(int id) { using(var context = new MyDataContext()) { var widget = context.Widgets.FirstOrDefault(x.Id == id); //if we don't have the widget, throw our HTTP error. if(widget == null) throw NotFound("Could not find widget: " + id); return widget; | }
You can of course implement this same basic idea however you like, but the principle would remain the same. It's good to have a better understanding of HTTP status codes and what they mean, and it's good to use them appropriately.
What you DO NOT want to do
You do not want to return the guts of a whole Exception object in the body of your error response. You don't need to, or want to be sending the whole stack trace and other sensitive information to any old client that makes a bad request. It's just not a good idea. Stack traces and other such things should be recorded for you by a good logging solution, and they should never, ever be returned to clients. Particularly unknown clients.
You do not want to return the wrong HTTP status codes. Will it break anything? Probably not. Will it make you look like a chump that didn't read the RFC before you used the code? Yes. Don't do it. Don't be a chump.
EDIT: I've changed this slightly. It was really late and I was tired when I was writing this. I meant 400 error, not 409. 409 is when you have a state conflict. Like for example a double submit of some data, or data in an unexpected state that is causing an issue. It's could be valid for nearly any error I suppose, but it seems a little more specific tied to the state of some data than 400.