Вернуть пользовательские объекты ошибок в веб-API
У меня есть веб-API. Я работаю над использованием структуры MVC 4 Web API. Если есть исключение, я в настоящее время бросаю новое исключение HttpResponseException. то есть:
if (!Int32.TryParse(id, out userId))
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid id"));
Это возвращает объект клиенту, который просто {"message":"Invalid id"}
Я бы хотел получить дополнительный контроль над этим ответом на исключения, вернув более подробный объект. Что-то вроде
{
"status":-1,
"substatus":3,
"message":"Could not find user"
}
Как мне это сделать? Является лучшим способом для сериализации моего объекта ошибки и установки его в ответном сообщении?
Я также немного посмотрел на ModelStateDictionary
и придумал этот бит "взлома", но он все еще не чистый вывод:
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));
изменить
похоже, что пользовательский HttpError
- это то, что мне нужно. Это похоже на трюк, теперь, чтобы сделать его расширяемым из моего бизнес-уровня...
var error = new HttpError("invalid stuff") {{"status", -1}, {"substatus", 3}};
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, error));
Ответы
Ответ 1
Я думаю, что это сделает трюк:
Создайте собственный класс исключений для бизнес-уровня:
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;
}
}
Создайте статический метод для создания HttpError
из экземпляра MyException
. Я использую отражение здесь, поэтому я могу добавить свойства к MyException
и всегда возвращать их без обновления 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;
}
В настоящее время у меня есть настраиваемый атрибут для общего обработчика исключений. Все исключения типа MyException
будут обрабатываться здесь:
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));
}
}
Я буду играть с этим немного больше и обновлять, поскольку я нахожу дыры в этом плане.
Ответ 2
Эти ответы намного сложнее, чем они должны быть.
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);
}
}
Это все, что вам нужно. Это также приятно и легко 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()
};
}
Ответ 3
Взгляните на следующую статью. Это поможет вам получить контроль над вашими исключениями и сообщениями об ошибках в сети: Web Api, HttpError и поведение исключений