Wednesday, July 15, 2009

HandleError and HTTP response codes.

After asking this question on Stack Overflow, I went out and used HandleError for my application exception handling. It works, as long as you turn on the customErrors configuration element. Then, after applying the HandleError attribute twice on my controller base class, I was able to get the exceptions NotFoundException and NoAccessException (my own, the names speak for themselves) turned into a nice rendering of the NotFound and NoAccess views.

Then I read up on HTTP status codes, and figured that HTTP code 500 is perhaps not the most appropriate for a situation in which an object with a certain Id cannot be found. In the future, for example, I might conclude that if the user wants to delete one of his objects, that object wouldn't be really deleted in the database but instead just flagged as such. In that case, my model should throw a DeletedException instead of a NotFoundException, so that I could inform the user that the object once existed but is no longer there. Also, I would want to return HTTP code 410 ("Gone") along with it, so that search engines can remove the entry from their indexes.

HandleError doesn't allow you to do that. But no worry, since I derived a new one from it, temporarily called MyHandleError (perhaps to be renamed HandleErrorWithStatus in the future) that would call the base OnException, and set the StatusCode in the Reponse after the correct View has been selected. Here’s the code to do it:

[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes",
Justification = "This attribute is AllowMultiple = true and users might want to override behavior.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class MyHandleErrorAttribute : HandleErrorAttribute
{
    private int statusCode = 500;

    public int StatusCode
    {
        get
        {
            return statusCode;
        }
        set
        {
            statusCode = value;
        }
    }

    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        // If custom errors are disabled, we need to let the normal ASP.NET exception handler
        // execute so that the user can see useful debugging information.
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
        {
            return;
        }

        if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
        {
            return;
        }

        if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
        {
            return;
        }

        base.OnException(filterContext);

        filterContext.HttpContext.Response.StatusCode = StatusCode;
    }

}

No comments:

Post a Comment