Running the risk of asking an opinion-based question, I would like to know which approach you use to handle exceptions or unexpected results in your ASP.NET core web API.
I have seen two approaches (maybe there is another, better, one):
Result pattern
Exceptions are not handled by the controller, instead a service handles exceptions. The controller actions have a signature ActionResult<Result<T>> where T is a resource/data object and Result<T> is a wrapper with additional fields such as Errors and ResultType .
Example:
// BlogPostsController.cs
...
private readonly IBlogPostsService _service;
[HttpGet]
public async Task<ActionResult<Result<BlogPost>>> GetPostById(Guid postId)
{
var result = await _service.GetPostById(postId);
return result.ResultType switch
{
ResultType.NotFound => NotFound(result),
ResultType.Ok => Ok(result),
ResultType.Unexpected => BadRequest(result)
}
}
Custom exceptions + middleware
Exceptions are not handled by the controller, instead a service throws custom exceptions which are handled by a middleware.
Example:
// BlogPostsController.cs
...
private readonly IBlogPostsService _service;
[HttpGet]
public async Task<IActionResult> GetPostById(Guid postId)
{
return await _service.GetPostById(postId);
}
--------------------
// BlogPostsService.cs
public async Task<BlogPost?> GetPostById(Guid postId)
{
var post = await _db.Where(post => post.Id == postId).SingleOrDefaultAsync();
if (post == null)
throw new PostNotFoundException($"Post {postId} not found");
return post;
}
----------------------
// ExceptionMiddleware.cs
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (PostNotFoundException)
{
var details = new ExceptionDetails()
{
Status = 404,
Detail = "Post does not exist"
};
await context.Response.WriteAsync(JsonConvert.SerializeObject(details));
}
...
}
Resultpattern pretty successfully so far.