Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return custom error objects in Web API

I have a web API I'm working on using the MVC 4 Web API framework. If there is an exception, I'm currently throwing a new HttpResponseException. ie:

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

This returns an object to the client that is simply {"message":"Invalid id"}

I would like to gain further control over this response to exceptions by returning a more detailed object. Something like

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

How would I go about doing this? Is the best way to serialize my error object and set it in the response message?

I've also looked into the ModelStateDictionary a bit and have come up with this bit of a "hack", but it's still 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)); 

edit
looks like a custom HttpError is what I need. This seems to do the trick, now to make it extensible from my business layer...

var error = new HttpError("invalid stuff") {{"status", -1}, {"substatus", 3}}; throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, error)); 
like image 621
earthling Avatar asked Apr 26 '13 18:04

earthling


People also ask

How do I show error messages in REST API?

The most basic way of returning an error message from a REST API is to use the @ResponseStatus annotation. We can add the error message in the annotation's reason field. Although we can only return a generic error message, we can specify exception-specific error messages.

How can you handle errors in Web API?

You can customize how Web API handles exceptions by writing an exception filter. An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception.

How does Web API handle 400 error?

BadRequest: 400 Error A 400 error, BadRequest, is used when you have validation errors from data posted back by the user. You pass a ModelStateDictionary object to the BadRequest method and it converts that dictionary into JSON which, in turn, is passed back to your HTML page.


2 Answers

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()     }; } 
like image 148
FredM Avatar answered Sep 22 '22 19:09

FredM


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.

like image 31
earthling Avatar answered Sep 25 '22 19:09

earthling