Global customization of error responses from .bet core 3.1 webapi

落爺英雄遲暮 提交于 2021-02-11 12:19:53

问题


I'm using app.UseExceptionHandler("/error"); to customize the output of any unhandled exceptions.

However, validation errors use the framework (ApiController) and automatically return a default formatted error.

I see that I can overwrite this using InvalidModelStateResponseFactory but is there a way to have one global location to handle all of these?

I understand that one is for 500 responses and the other is 400 responses but I'd like to consolidate if possible.


回答1:


If you just display simple error message,you could use UseStatusCodePagesWithReExecute and UseExceptionHandler middleware like below:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStatusCodePagesWithReExecute("/errors/{0}");
    app.UseExceptionHandler("/errors/500");
    //...
}

ErrorsController:

[ApiController]
[Route("[controller]")]
public class ErrorsController : ControllerBase
{
    [Route("{code}")]
    public IActionResult InternalError(int code)
    {
        return new ObjectResult(new ApiResponse(code));
    }
}

Custom ApiResponse:

public class ApiResponse
{
    public int StatusCode { get; }

    public string Message { get; }

    public ApiResponse(int statusCode, string message = null)
    {
        StatusCode = statusCode;
        Message = message ?? GetDefaultMessageForStatusCode(statusCode);
    }

    private static string GetDefaultMessageForStatusCode(int statusCode)
    {
        switch (statusCode)
        {

        case 404:
            return "Resource not found";
        case 500:
            return "An unhandled error occurred";
        case 400:
            return "Model error";
        default:
            return null;
        }
    }
}

How to test:

1.For 500:

[HttpGet]
public IActionResult Get()
{
    throw new Exception("adasd");
}

2.For 400:

[HttpPost]
public IActionResult Get(TestModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();//can't pass the ModelState to it
    }
    return Ok();
}

If you want to display the detailed ModelState error,you need to know that model validation is before action,so you should add an action filter based on my previous answer:

public class ApiValidationFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(new ApiBadRequestResponse(context.ModelState));
        }
    }
}

Custom ApiBadRequestResponse:

public class ApiBadRequestResponse : ApiResponse
{
    public IEnumerable<string> Errors { get; }

    public ApiBadRequestResponse(ModelStateDictionary modelState)
        : base(400)
    {
        if (modelState.IsValid)
        {
            throw new ArgumentException("ModelState must be invalid", nameof(modelState));
        }

        Errors = modelState.SelectMany(x => x.Value.Errors)
            .Select(x => x.ErrorMessage).ToArray();
    }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.Filters.Add(typeof(ApiValidationFilter));
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStatusCodePagesWithReExecute("/errors/{0}");
    app.UseExceptionHandler("/errors/500");

    //...
}

Result:

Reference:

https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api




回答2:


You can using error middleware (refer my question here for that Or follow below code:

If you want to handle error in response then using OnActionExecuted insteed.

services.AddMvc(opt =>
{
    opt.Filters.Add(typeof(ValidateModelStateAttributeFilter));
})

public class ValidateModelStateAttributeFilter : ActionFilterAttribute
    {
        /// <summary>
        /// 
        /// </summary>
        public ValidateModelStateAttributeFilter()
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
                        .SelectMany(v => v.Errors)
                        .Select(v => v.ErrorMessage)
                        .ToList();

                string combinError = errors.Any() ? string.Join("\n", errors) : string.Empty;

                var responseObj = new DomainExceptionContract
                {
                    Key = "BadRequest",
                    Message = combinError
                };

                context.Result = new JsonResult(new { Data = responseObj })
                {
                    StatusCode = 400
                };
            }
        }
    }


来源:https://stackoverflow.com/questions/62985803/global-customization-of-error-responses-from-bet-core-3-1-webapi

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!