Errores - Excepciones


Los errores y excepciones fueron gestionados de una manera centralizada. No fueron capturados mediante Try - Catch así que ascienden la pila de llamadas hasta ser recogidos por un filtro personalizado que mapea el codigo de estado Http y responde a Front con un código de error para mostrar un mensaje dado. En la API se añadió el siguiente código para llevarlo a cabo:

  • En App_Start se añade una clase FilterConfig.cs:
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAtribute());
        }
    }
  • En Global.asax.cs se añade la siguiente línea al Application_Start() que llama a la clase anterior:
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            ...
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            ...
        }
    }
    
  • Nota: Se utilizó GlobalFilterCollection porque existía una referencia a System.Web.Mvc en el proyecto API. Si quisieramos realizarlo con WEBAPI "puro" (using System.web.Http.Filters) bastaría utilizar HttpFilterCollection modificar la línea del Global.asax por:
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterWebApiFilters(GlobalConfiguration.Configuration.Filters)
    
    Y modificar la clase FilterConfig como:
    public class FilterConfig
    {
        public static void RegisterWebApiFilters(HtttpFilterCollection filters)
        {
            filters.Add(new ExceptionHandlerFilter());
        }
    }
  • Creamos una enumeración con codigos de errores internos. Front conocerá estos códigos para mostrar un mensaje u otro:
    public enum APIExceptionType
    {
        HttpException = 0,
        BadRequest = 1,
        InternalError = 2,
        //  Seccion 1
        TotalValueCalculationError = 3
    }
    
  • Creamos una clase que herede de Exception adaptada a la aplicación:
        public class APIException : Exception
        {
            public APIExceptionType ExceptionCode { get; private set; }
            public string ExtraInformation { get; private set; }
            public APIException(APIExceptionType exceptionCode, string extraInformation = null)
            {
                ExceptionCode = exceptionCode;
                ExtraInformation = extraInformation;
            }
            public string ToJson()
            {
                return string.Format("{{code : {0} , info: {1}}}", ExceptionCode, ExtraInformation);
            }
        }
    
  • Creamos un ExceptionHandlerFilter personalizado que hereda de ExceptionFilterAttribute y modifica OnException para capturar tanto las excepciones no controladas como las forzadas. En principio se pasa solo el código de error asociado en la enumeración aunque se preparó (comentado) una respuesta Json con más información:
    public class ExceptionHandlerFilter : ExceptionFilterAttribute
    {
        public IDictionary Mappings
        {
            get;
            private set;
        }
        public ExceptionHandlerFilter()
        {
            Mappings = new Dictionary();
            Mappings.Add(typeof(InvalidOperationException), HttpStatusCode.BadRequest);
            Mappings.Add(typeof(ArgumentOutOfRangeException), HttpStatusCode.BadRequest);
            Mappings.Add(typeof(ArgumentNullException), HttpStatusCode.BadRequest);
            Mappings.Add(typeof(ArgumentException), HttpStatusCode.BadRequest);
        }
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception == null) return;
            var exception = context.Exception;
            if (exception is HttpException)
            {
                var httpException = (HttpException)exception;
                context.Response = context.Request.CreateResponse((HttpStatusCode)httpException.GetHttpCode(), APIExceptionType.HttpException);
            }
            else if (Mappings.ContainsKey(exception.GetType()))
            {
                var httpStatusCode = Mappings[exception.GetType()];
                context.Response = context.Request.CreateResponse(httpStatusCode, APIExceptionType.BadRequest);
            }
            else if(exception is APIException)
            {
                var apiException = (APIException)exception;
                context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, apiException.ExceptionCode);
                //TODO: Exception as Json with {code : xxx, info: yyy} structure
                //context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, apiException.ToJson());
            }
            else
            {
                context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, APIExceptionType.InternalError);
            }
        }
    }
    
  • ¿Cómo se forzaría un error en la capa controladores o servicios?
    if (calculoejemplo == false) throw new APIException(APIExceptionType.TotalValueCalculationError);
    



Anotar que algunos de los Tests se desarrollaron esperando una respuesta de Excepción. Para ello se decoraron con:

[ExpectedException(typeof(APIException))]