35

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));
1
  • Do not include solution to question please (post a separate answer instead). Commented May 24 at 0:31

2 Answers 2

46

These answers are way more complicated than they need to be.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new HandleApiExceptionAttribute());
        // ...
    }
}

public class HandleApiExceptionAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var request = context.ActionContext.Request;

        var response = new
        {
             //Properties go here...
        };

        context.Response = request.CreateResponse(HttpStatusCode.BadRequest, response);
    }
}

That's all you need. It's also nice and easy to unit test:

[Test]
public async void OnException_ShouldBuildProperErrorResponse()
{
    var expected = new 
    {
         //Properties go here...
    };

    //Setup
    var target = new HandleApiExceptionAttribute()

    var contextMock = BuildContextMock();

    //Act
    target.OnException(contextMock);

    dynamic actual = await contextMock.Response.Content.ReadAsAsync<ExpandoObject>();

    Assert.AreEqual(expected.Aproperty, actual.Aproperty);
}

private HttpActionExecutedContext BuildContextMock()
{
    var requestMock = new HttpRequestMessage();
    requestMock.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

    return new HttpActionExecutedContext()
    {
        ActionContext = new HttpActionContext
        {
            ControllerContext = new HttpControllerContext
            {
                Request = requestMock
            }

        },
        Exception = new Exception()
    };
}
Sign up to request clarification or add additional context in comments.

3 Comments

Make sure you have using System.Net.Http;
It's nice, but when using the PostSharp aop approach, you can't just set the context response, there is no way to do access the context so you have to over-engineer stuff :(
see (jakeydocs.readthedocs.io/en/latest/migration/webapi.html) for usage of WebApiConfig in app_start. what creates the app_start directory
9

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.

1 Comment

Why are you hiding the Message property? Wouldn't it be safer to call the base ctor and pass the message that way?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.