Tuesday, March 20, 2012

HTTP protocol breaking in ASP.NET MVC

HTTP clients (such as browsers) are designed to handle different error codes differently and there are a lot of reasons why server-side errors have different status codes than those triggered by users. Depending on status code, responses are cached differently, web crawlers are indexing differently and so on.

Recently, durring error handling review in our project, I've learned how ASP.NET MVC obeys HTTP protocol rules in terms of status codes. And unfortunately, there are some pretty easy cases where it doesn't. See this simple controller:

public class TestController : Controller
{
public ActionResult Index(int test, string html)
{
return Content("OK");
}
}

MVC handles missing controllers/actions properly, as 404 Not Found:

Let's now try to call the Index action without parameters:

MVC couldn't bind parameter values to an action and throws an exception, which yields 500 Internal Server Error status code. According to the HTTP protocol, it means that something unexpected happened on the server, but it is server's own problem, not that the request was wrong ("hey, I have some problems at the moment, can't help you, come back later"). But that's not true, I wouldn't say that missing parameter is an unexpected situation, and definitely it's the request what is wrong. The protocol has better solutions for that kind of situations - like 400 Bad Request ("hey, I've tried to help you but you're doing something wrong and I can't understand you").

Another example:

MVC has some validation rules that protects the server from potentially malicious requests, like cross-site scripting. But again, those cases are handled with 500 Internal Server Error, despite that it's obviously the client's fault - again 400 Bad Request will work here better. Purely from the HTTP protocol point of view, 500 Internal Server Error here is like admitting that the malicious request actually broke something on the server.

How can we fix these two? For example by modifying the response generated by MVC on error. We can add this code to our Global.asax.cs:

protected void Application_Error()
{
var lastError = Server.GetLastError();
if (lastError is ArgumentException || lastError is HttpRequestValidationException)
{
Server.ClearError();
Response.StatusCode = (int) HttpStatusCode.BadRequest;
}
}

It checks for type of exception thrown and changes the status code to more appropriate 400 Bad Request in these two cases mentioned.

No comments:

Post a Comment