9

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.

Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
BLAZORLOVER
  • 1,971
  • 2
  • 17
  • 27
  • documentation : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling – Nkosi Oct 18 '17 at 15:18
  • 1
    You are focusing on the wrong approach. You should avoid the base controller on exception approach you are trying as well as the try catch in the controller. Controllers should be kept as lean as possible and the responsibility should be consolidated into another class. This appears to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Nkosi Oct 18 '17 at 15:23
  • @Nkosi Can you provide a simple, concise example of how to take the above controller and return a custom response when an exception is thrown? – BLAZORLOVER Oct 18 '17 at 15:24
  • 1
    @Nkosi updated with a clear example of what I'm looking for. Looking for practical solutions here, not theory. – BLAZORLOVER Oct 18 '17 at 15:27
  • It seems they removed the ability for you to do what you ask. You might want to embrace the new paradigms ASP.NET promotes - like building a custom middleware. This is not theory - it's simply making things work in the generally accepted manner - given your constraints. – BlakeH Oct 18 '17 at 15:38
  • @BlakeH do you have a concise example of how to achieve this via the new approach? – BLAZORLOVER Oct 18 '17 at 15:42
  • 2
    A base controller is usually a bad idea in MVC/WebApi because it tightly couples [cross cutting concerns](https://msdn.microsoft.com/en-us/library/ee658105.aspx) to your controllers through inheritance. A better approach is to use [filters](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters) so you can share functionality while still making it easy to opt in/out at the controller or action level if you need to. – NightOwl888 Oct 18 '17 at 15:45

3 Answers3

6

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:

  • Controllers actions should be aware only of normal flow. No 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?

  • use provided by ASP.NET Core error handling approaches (like ExceptionHandler, or Exception Filters), it allows to analyze exceptions and generate appropriate/different responses accordingly. Look into related SO Error handling in ASP.NET Core question for the example. There is also the error-handling section in documentation.
Set
  • 47,577
  • 22
  • 132
  • 150
3

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());
        }
    }
Derek
  • 8,300
  • 12
  • 56
  • 88
0

If you're using .Net Core, a great way to handle errors is to use the Exception Handling Middleware.

See these articles for more details:

https://code-maze.com/global-error-handling-aspnetcore/

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-5.0

This enables the removing of error handling logic, which is a cross cutting concern, into a dedicated component, allowing your controllers to remain thin and have a single responsibility - ultimately making your code/application more maintainable and robust.

JTech
  • 3,420
  • 7
  • 44
  • 51