ASP.NET HttpResponseException and ActionFilter / ExceptionFilter

I think one of my biggest gripes with ASP.NET MVC/WebAPI has to be the HttpResponseException and the way it interacts with the request pipeline, and therefore, ActionFilters and ExceptionFilters.

The MSDN documentation for HttpResponseException describes it as an exception that allows for a given HttpResponseMessage to be returned to the client. It also shows that it derives from the Exception class.

When I use ASP.NET, I implement the Unit of Work (UoW) pattern, whereby each request has it’s own DB transaction, and at the end of the request an ActionFilter is used to decide if the transaction should be Committed (i.e. no exception occurred), or Rolled back (i.e. an exception occurred). This is done by checking the HttpActionExecutedContext.Exception field and making sure it is either null or has a value.

My view is that the HttpResponseException should be there to allow a coder to throw an exception at any point (as something has gone wrong) and then allow for any DB changes to rollback automatically. It is slightly more helpful than a normal exception as we can choose the specific HTTP status code to be returned to the client giving a reason why the request has terminated.

Unfortunately, this is not the case, as described on the MSDN documentation for exception handling:

“An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception”.

It goes on to describe the HttpResponseException as a special case. This leaves us, the developer, in an awkward situation.

Unfortunately, what it does not specifically describe, however is implied, is that on an ActionFilter, a HttpResponseException will not set the Exception property of the filter context, and therefore it is not possible to tell if the DB transaction should be rolled back or not.

public void EndTransaction(HttpActionExecutedContext filterContext)
{
    var session = _sessionFactory.GetCurrentSession();

    if (session == null) return;
    if (!session.Transaction.IsActive) return;

    if (filterContext.Exception == null)
    {
        session.Flush();
        session.Transaction.Commit();
    }
    else
    {
        session.Transaction.Rollback();
    }

    TransactionHandled = true;
}

Consider retrieving an entity from the database, if the entity is not found, we want to return 404 to the requester. However, if a HttpResponseException were to be thrown with HttpStatusCode.NotFound then we would not want to commit any previous changes to the DB in the request, as we consider the entire request to be an atomic transaction. However, this is not the case, and I have seen this mistake made all too many times, and developers are confused as to why, when they have thrown an exception, the DB transaction has still been committed with partial changes.

In my preferred implementation, I use an ActionFilter to handle the DB transaction commit/rollback and an ExceptionFilter to handle either sending an error response to the client (with a status code etc. for “known” errors), or masking the error with a 500 (for “unknown errors“). I do this by having my own HttpDetailedException class (the “known” errors) which derives from Exception. A global ExceptionFilter is used which checks the exception type, and will provide HttpStatusCode result for HttpDetailedException derived classes, and a simple 500 for all other Exception types. This also provides a useful hook point for email logging / DB logging etc.

I have varying constructors for the HttpDetailedException class which allow HttpStatusCodes to be set (as with HttpResponseException), and which also handle constructing the response from the original request for ease of use.

I hope to eventually have an example of all of this on GitHub when I have some time to put it together – Watch this space!

You may also like...

Leave a Reply

Your email address will not be published.