miércoles, octubre 30, 2013

Manejar excepciones en ASP.Net Web API (Handling Exceptions in ASP.NET Web API)

Cuando en una aplicación ASP.Net Web API se produce una excepción en una función interna (por ejemplo busco un cliente que no existe y no tengo controlada esta situación), si no se captura la excepción se enviará un error HTTP 500 (Internal Server Error) con el mensaje genérico

<Error><Message>An error has occurred.</Message></Error>

Si queremos enviar un mensaje que aporte más información al cliente podemos crear una clase derivada de
ExceptionFilterAttribute, tal como esta

  1. public class MyExceptionFilter:ExceptionFilterAttribute
  2. {
  3. public override void OnException(HttpActionExecutedContext context)
  4. {
  5. HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.InternalServerError)
  6. {
  7. Content = new StringContent("Se ha producido un error en la app."),
  8. ReasonPhrase = "Se ha producido un error en la app."
  9. };  
  10. context.Response = msg;
  11. }
  12. }

Para usarla de forma global (que todos los controllers la usen) podemos añadir la siguiente línea al Application_Start() de Global.asax:

GlobalConfiguration.Configuration.Filters.Add(new WebAPIExceptionsDemo.MyExceptionFilter());

O bien, para usarla para determinados controllers, añadir a estos el atributo creado:

  1. [MyExceptionFilter]
  2. public class CustomerController
  3. {
  4. ...

Esta entrada es un extracto del artículo http://www.codeguru.com/csharp/.net/net_asp/handling-exceptions-in-asp.net-web-api.htm.

Estrategia a seguir para el manejo de excepciones (Exception handling strategy)

En esta entrada copio el post http://stackoverflow.com/questions/14973642/how-using-try-catch-for-exception-handling-is-best-practice tal cual por parecerme muy interesante:

[...]

My exception handling strategy is :
  • To catch all unhandled exceptions by hooking to the Application.ThreadException event, then decide :
    • For a UI application: to pop it to the user with an apology message (winforms)
    • For a Service or a Console application: log it to a file (service or console)
Then I always enclose every piece of code that is run externally in try/catch :
  • All events fired by the Winforms infrastructure (Load, Click, SelectedChanged...)
  • All events fired by third party components
Then I enclose in 'try/catch'
  • All the operations that I know might not work all the time (IO operations, calculations with a potential zero division...). In such a case, I throw a new ApplicationException("custom message", innerException) to keep track of what really happened
Additionally, I try my best to sort exceptions correctly. There are exceptions which:
  • need to be shown to the user immediately
  • require some extra processing to put things together when they happen to avoid cascading problems (ie: put .EndUpdate in the finally section during a TreeView fill)
  • the user does not care, but it is important to know what happened. So I always log them:
    • In the event log
    • or in a .log file on the disk
It is a good practice to design some static methods to handle exceptions in the application top level error handlers.
I also force myself to try to:
  • Remember ALL exceptions are bubbled up to the top level. It is not necessary to put exception handlers everywhere.
  • Reusable or deep called functions does not need to display or log exceptions : they are eigher bubbled up automatically or rethrown with some custom messages in my exception handlers.
So finally :
Bad:
// DON'T DO, THIS IS BAD
try
{
    ...
}
catch 
{
   // only air...
}
Useless:
// DONT'T DO, THIS IS USELESS
try
{
    ...
}
catch(Exception ex)
{
    throw ex;
}
What I do at the top level:
// i.e When the user clicks on a button
try
{
    ...
}
catch(Exception ex)
{
    ex.Log(ex); // Log exception
    // ex.LogAndDisplay(ex); // Log exception, then show it to the user with apologies...
}
What I do in some called functions:
// Calculation module
try
{
    ...
}
catch(Exception ex)
{
    // Add useful information to the exception
    throw new ApplicationException("Something wrong happened in the calculation module :", ex);
}

// IO module
try
{
    ...
}
catch(Exception ex)
{
    throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex);
}
There is a lot to do with exception handling (Custom Exceptions) but thoses rules I try to keep in mind are enough for the simple applications I do.