proudmonkey / AutoWrapper

A simple, yet customizable global exception handler and Http response wrapper for ASP.NET Core APIs.
MIT License
677 stars 82 forks source link

Replacing IExceptionFilter #108

Open zoinkydoink opened 3 years ago

zoinkydoink commented 3 years ago

I am using mediatr to throw ValidationException once it finds errors, then in my API project I have a global exception handler that catches these and returns them to the caller, also it catches NotFound. I feel like some of the code in my exception handler might be reduntant so I would like to make it as clean as possible.

I would like to achieve the following

  1. Continue to use Mediatr to trap validation errors and throw ValidationException when it finds them
  2. Wrap my responses so there is a "result" property when its successful (autowrapper does this already)
  3. When there are validation errors, show them as a list (autowrapper does this but I couldnt get it to work with my code/global exception handler
  4. for my NotFoundException (custom), return the message in the exception and return 404

Should I be throwing ApiException inside my globalexception handler? can global exception handler be completely removed and still be able to trap validation exception from mediatr and trap NotFound items and return a 404,

sorry for the confusion, I simple just want to add the functionality of AutoWrapper to my existing code to get the whole wrapping to my result, either good or bad returns

Global Exception Handler

 public class HttpGlobalExceptionFilter : IExceptionFilter
    {
        private readonly IWebHostEnvironment _env;
        private readonly ILogger<HttpGlobalExceptionFilter> _logger;

        public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
        {
            _env = env;
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            var msg = context.Exception.Message;

            switch (context.Exception)
            {
                case ValidationException ex:
                {

                    context.HttpContext.Response.ContentType = "application/json";
                    context.HttpContext.Response.StatusCode = (int) HttpStatusCode.BadRequest;
                    context.Result = new JsonResult(ex.Errors);
                    msg = string.Empty;
                    foreach (var error in ex.Errors)
                        msg += $"{error.ErrorMessage}: {error.AttemptedValue}" + Environment.NewLine;
                    break;
                }
                case NotFoundException:
                    msg = context.Exception.Message;
                    context.Result = new NotFoundError(context.Exception.Message);
                    context.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
                    break;
                default:
                {
                    var json = new JsonErrorResponse
                    {
                        Messages = new[] {"An error occur.Try it again."}
                    };

                    if (_env.IsDevelopment()) json.DeveloperMessage = context.Exception.ToString();

                    context.Result = new InternalServerErrorObjectResult(json);
                    context.HttpContext.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
                    break;
                }
            }

            _logger.LogError(new EventId(context.Exception.HResult), context.Exception, msg);
            context.ExceptionHandled = true;
        }

        private class NotFoundError : ObjectResult
        {
            public NotFoundError(object error)
                : base(error)
            {
                StatusCode = StatusCodes.Status404NotFound;
            }
        }

        private class JsonErrorResponse
        {
            public string[] Messages { get; set; }

            public string DeveloperMessage { get; set; }
        }

        public class InternalServerErrorObjectResult : ObjectResult
        {
            public InternalServerErrorObjectResult(object error)
                : base(error)
            {
                StatusCode = StatusCodes.Status500InternalServerError;
            }
        }
    }

Mediatr Validation Behaviour

    public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
    {
        private readonly IEnumerable<IValidator<TRequest>> _validators;

        public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
        {
            _validators = validators;
        }

        public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
            RequestHandlerDelegate<TResponse> next)
        {
            var context = new ValidationContext<TRequest>(request);
            var failures = _validators
                .Select(v => v.Validate(context))
                .SelectMany(result => result.Errors)
                .Where(f => f != null)
                .ToList();

            if (failures.Count != 0) throw new ValidationException(failures);

            return next();
        }
    }