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!