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));
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.
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.
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.
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() }; }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With