C
C#2y ago
Kiel

ASP.NET REST APIs with Custom Error Models

Hi all. I'm currently developing a REST API and have a few questions regarding error flow, as well as custom error models. I'd appreciate an answer to any or all of these questions, if possible! 1) There is a guide out there for error handling in REST APIs, https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-6.0, that seems to heavily imply the current paradigm seems to involve entirely exception-based patterns, versus returning (ex.) BadRequest(), NotFound(), etc. Is this really the right way to do things now? 2) If the answer to 1) is "no": Are custom error models considered an acceptable practice? The same guide linked above uses an error handle that formats errors as an RFC 7807(?) JSON object. In the past, I have used custom error models and simply returned that object to describe the error code and problem(s)/error(s). Are either types of error models preferred over the other? 3) If custom models are considered an OK practice: How do I avoid redundantly specifying the HttpStatusCode that is set/returned in my error response? Previously, my error returns in my controller methods would look something like this:
// ErrorModel.NotFound sets its HttpStatusCode property to 404/NotFound
if (!_something.Exists(...))
return NotFound(ErrorModel.NotFound(...));
// ErrorModel.NotFound sets its HttpStatusCode property to 404/NotFound
if (!_something.Exists(...))
return NotFound(ErrorModel.NotFound(...));
This is redundant because I'm returning NotFound(), which already sets the HttpStatusCode to 404. However, I can't think of a feasible way to set ErrorModel's HttpStatusCode property without this redundant (helper) static method. Is there a best practice for this as well? For example, somehow accessing the currently set HttpStatusCode to set the model's code dynamically?
3 Replies
Saber
Saber2y ago
i use FluentResults and a middleware that translates the FluentResults to ActionResults to communicate http status codes https://discord.com/channels/143867839282020352/156079822454390784/964620871174078514 small code snippet from the action filter I have implemented, doesn't contain everything to make it useful. Then within my code I can do stuff like
var entity = await _context.Foo.FirstOrDefaultAsync(x => x.Id == request.Id);
if (entity is null) {
return Results.NotFound($"Foo {request.Id} was not found.");
}

if (someCondition) {
return Results.Fail("Some Bad Request Message");
}

return Result.Ok(entity.ToDto());
var entity = await _context.Foo.FirstOrDefaultAsync(x => x.Id == request.Id);
if (entity is null) {
return Results.NotFound($"Foo {request.Id} was not found.");
}

if (someCondition) {
return Results.Fail("Some Bad Request Message");
}

return Result.Ok(entity.ToDto());
MODiX
MODiX2y ago
Deluxe#8269
if (context.Result is ObjectResult { Value: ResultBase result })
{
var value = result.IsFailed ? null : GetValue((dynamic) result);
context.Result = result switch {
{ IsSuccess: true } when value is null => new NoContentResult(),
{ IsSuccess: true } => new OkObjectResult(value),
// handle other cases
};
}

private object GetValue<T>(Result<T> result) => result.Value;
private object GetValue(ResultBase result) => null;
if (context.Result is ObjectResult { Value: ResultBase result })
{
var value = result.IsFailed ? null : GetValue((dynamic) result);
context.Result = result switch {
{ IsSuccess: true } when value is null => new NoContentResult(),
{ IsSuccess: true } => new OkObjectResult(value),
// handle other cases
};
}

private object GetValue<T>(Result<T> result) => result.Value;
private object GetValue(ResultBase result) => null;
Quoted by
<@!128771700182614016> from #web (click here)
React with ❌ to remove this embed.
Kiel
Kiel2y ago
looks neat, but...seems incredibly overengineered for something as simple as what I'm doing (at the moment - just a single controller). I'll check it out either way. Is the middleware you mentioned completely custom or is it a package I install alongside FluentResults?