exceptionasp.net-mvc-4asp.net-web-api

How can I return custom error objects in Web API?


In my web API using the MVC 4 Web API framework, if there is an exception I'm throwing a new HttpResponseException:

if (!Int32.TryParse(id, out userId))
    throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid id"));

This returns an object to the client: {"message":"Invalid id"}. I want to return a more detailed object:

{
 "status":-1,
 "substatus":3,
 "message":"Could not find user"
 }

How would I go about this? Is the best way to serialize my error object and set it in the response message? I've looked into ModelStateDictionary and came up with this, but it's not a clean output:

var msd = new ModelStateDictionary();
msd.AddModelError("status", "-1");
msd.AddModelError("substatus", "3");
msd.AddModelError("message", "invalid stuff");
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, msd));

Solution

  • I think this will do the trick:

    Create a custom exception class for the business layer:

     public class MyException: Exception
     {
        public ResponseStatus Status { get; private set; }
        public ResponseSubStatus SubStatus { get; private set; }
        public new string Message { get; private set; }
    
        public MyException()
        {}
    
        public MyException(ResponseStatus status, ResponseSubStatus subStatus, string message)
        {
            Status = status;
            SubStatus = subStatus;
            Message = message;
        }
     }
    

    Create a static method to generate a HttpError from an instance of MyException. I'm using reflection here so I can add properties to MyException and always have them returned w/o updating Create:

        public static HttpError Create<T>(MyException exception) where T:Exception
        {
            var properties = exception.GetType().GetProperties(BindingFlags.Instance 
                                                             | BindingFlags.Public 
                                                             | BindingFlags.DeclaredOnly);
            var error = new HttpError();
            foreach (var propertyInfo in properties)
            {
                error.Add(propertyInfo.Name, propertyInfo.GetValue(exception, null));
            }
            return error;
        }
    

    I currently have a custom attribute for a general exception handler. All exceptions of type MyException will be handled here:

    public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            var statusCode = HttpStatusCode.InternalServerError;
    
            if (context.Exception is MyException)
            {
                statusCode = HttpStatusCode.BadRequest;
                throw new HttpResponseException(context.Request.CreateErrorResponse(statusCode, HttpErrorHelper.Create(context.Exception)));
            }
    
            if (context.Exception is AuthenticationException)
                statusCode = HttpStatusCode.Forbidden;
    
            throw new HttpResponseException(context.Request.CreateErrorResponse(statusCode, context.Exception.Message));
        }
    }
    

    I'll play around with this a bit more and update as I find holes in this plan.