Many of our current controllers look like this:
[HttpPost]
public List<Foo> Post([FromBody]Bar model)
{
if (model == null)
{
throw new ArgumentNullException();
}
try
{
// business logic
}
catch (Exception ex)
{
// logging
}
return dto;
}
A lot of code is being repeated here though. What I'd like to do is implement a base controller that handles exceptions so I can return a standardized response with fields like Payload
, Success
, Error
, etc.
Prior to .net core this was possible by providing an override of OnException
however this doesn't appear to work with a .net core api controller. How do I go about consolidating this exception logic to return a custom response when things go awry in my controller bodies?
I'd like this, as a starting point:
[HttpPost]
public StandardFoo Post([FromBody]Bar model)
{
if (model == null)
{
throw new ArgumentNullException();
}
// business logic
return new StandardFoo(){Payload: dto};
}
Where exceptions thrown by model validation or business logic
bubble up to some piece of logic that returns a new StandardFoo
with a property containing the exception details.
If shortly, you should not catch and process exceptions in your controllers.
Instead, you need to separate normal and error flows in your code and then process error flow separately. One of the main approaches to indicate that normal flow is not possible is to raise the .NET Exceptions (and you use it). But:
try-catch
logic and so on.For input validation use ActionFilter
. You may have global filters for all controllers or define specific per action. See Filters section in documentation. ASP.NET Core allows also do Model Validation.
During controller action execution you should raise exceptions as soon as possible and stop further pipeline execution. And yes, the Exception may be raised on any of the levels (action level, Service/Business layer, DA layer, etc).
How to handle the raised exception then?
I would recommend creating a custom action filter. This can be wrapped around every incoming request in the WebApiConfig Register method(See below).
In my example, I am checking that the model state is valid.
If it's not, I create an ErrorResponse and send back a Bad Request.
You don't have to simply send back the model state like in the example below, you could return anything you actually want.
This way it becomes uniform across all endpoints that have a model that needs to be validated as well as any other checks you want to do at this point in the pipeline.
Note: Because we are registering this attribute globally we dont then have to declare it anywhere else, from this point on, all incoming traffic be inspected by this class.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
public override bool AllowMultiple
{
get { return false; }
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ValidateModelAttribute());
}
}
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