【问题标题】:What is the best possible way to send custom error responses in .net core web api在.net core web api中发送自定义错误响应的最佳方法是什么
【发布时间】:2019-09-11 13:47:55
【问题描述】:

我正在使用.Net Core 2.2 制作一个.net Core WebApi。 API 已准备就绪,但失败消息和响应是我遇到的问题。

现在,我的反应如下所示

json

{
    "empId":1999,
    "empName":"Conroy, Deborah",
    "enrollmentStatus":true,
    "primaryFingerprintScore":65,
    "secondaryFingerprintScore":60,
    "primaryFingerprint":null,
    "secondaryFingerprint":null,
    "primaryFingerprintType":null,
    "secondaryFingerprintType":null}
}

我创建了一个 json 格式化程序类并编写了以下代码

public class SuperJsonOutputFormatter : JsonOutputFormatter
{
    public SuperJsonOutputFormatter(
        JsonSerializerSettings serializerSettings,
        ArrayPool<char> charPool) : base(serializerSettings, charPool)
    {
    } 

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context,
        Encoding selectedEncoding)
    {

        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (selectedEncoding == null)
            throw new ArgumentNullException(nameof(selectedEncoding));
        using (TextWriter writer =
            context.WriterFactory(
                context.HttpContext.Response.Body,
                selectedEncoding))
        {

            var rewrittenValue = new
            {
                resultCode = context.HttpContext.Response.StatusCode,
                resultMessage =
                ((HttpStatusCode)context.HttpContext.Response.StatusCode)
                    .ToString(),
                result = context.Object
            };

            this.WriteObject(writer, rewrittenValue);
            await writer.FlushAsync();
        }
    }

我希望所有错误代码都作为通用错误消息发送,例如下面的 JSON。

状态正常:

{
    "status" : True,
    "error"  : null,
    "data" : {
        {
            "empId":1999,
            "empName":"Conroy, Deborah",
            "enrollmentStatus":true,
            "primaryFingerprintScore":65,
            "secondaryFingerprintScore":60,
            "primaryFingerprint":null,
            "secondaryFingerprint":null,
            "primaryFingerprintType":null,
            "secondaryFingerprintType":null}
        }
    }   
}

对于其他状态,例如 404、500、400、204

{
    "status" : False,
    "error"  : {
        "error code" : 404,
        "error description" : Not Found
    },
    "data" : null   
}

【问题讨论】:

    标签: asp.net-core exception asp.net-core-mvc asp.net-core-webapi


    【解决方案1】:

    我希望所有错误代码都作为通用错误消息发送,例如下面的 JSON

    你快到了。您需要做的是启用您的 SuperJsonOutputFormatter。

    对格式化程序的一点改变

    首先,您的格式化程序没有返回具有您想要的相同架构的 json。所以我创建了一个虚拟类来保存error codeerror description 的信息:

    公共类错误描述{ 公共错误描述(HttpStatusCode statusCode) { this.Code = (int)statusCode; this.Description = statusCode.ToString(); } [JsonProperty("错误代码")] 公共 int 代码 {get;set;} [JsonProperty("错误描述")] 公共字符串描述 {get;set;} }

    并将您的 WriteResponseBodyAsync() 方法更改如下:

    ... 使用 (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) { var statusCode = context.HttpContext.Response.StatusCode; var rewrittenValue = 新 { 状态 = IsSucceeded(statusCode), 错误= IsSucceeded(statusCode) ?空:新的错误描述((HttpStatusCode)statusCode), 数据=上下文.对象, }; this.WriteObject(writer, rewrittenValue); 等待 writer.FlushAsync(); }

    这里的IsSucceeded(statusCode) 是一个简单的帮助方法,您可以根据需要自定义:

    private bool IsSucceeded(int statusCode){
        // I don't think 204 indicates that's an error. 
        //     However, you could comment out it if you like
        if(statusCode >= 400 /* || statusCode==204 */ ) { return false; }
        return true;
    }
    

    启用格式化程序

    其次,要启用您的自定义 Formatter,您有两种方法:一种方法是将其注册为全局 Formatter,另一种方法是为特定的 Controller 或 Action 启用它。就个人而言,我认为第二种方式更好。因此,我创建了一个 Action Filter 来启用您的格式化程序。

    这是一个过滤器的实现,它可以动态地启用您的自定义格式化程序:

    public class SuperJsonOutputFormatterFilter : IAsyncActionFilter{
        private readonly SuperJsonOutputFormatter _formatter;
        // inject your SuperJsonOutputFormatter service
        public SuperJsonOutputFormatterFilter(SuperJsonOutputFormatter formatter){
            this._formatter = formatter;
        }
        // a helper method that provides an ObjectResult wrapper over the raw object
        private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj){
            var wrapper = new ObjectResult(obj);
            wrapper.Formatters.Add(this._formatter);
            context.Result= wrapper;
            return wrapper;
        }
    
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            ActionExecutedContext resultContext = await next();
            // in case we get a 500
            if(resultContext.Exception != null && ! resultContext.ExceptionHandled){
                var ewrapper = this.WrapObjectResult(resultContext, new {});
                ewrapper.StatusCode = (int) HttpStatusCode.InternalServerError; 
                resultContext.ExceptionHandled = true;
                return;
            }
            else {
                switch(resultContext.Result){
                    case BadRequestObjectResult b :      // 400 with an object
                        var bwrapper=this.WrapObjectResult(resultContext,b.Value);
                        bwrapper.StatusCode = b.StatusCode;
                        break;
                    case NotFoundObjectResult n :        // 404 with an object
                        var nwrapper=this.WrapObjectResult(resultContext,n.Value);
                        nwrapper.StatusCode = n.StatusCode;
                        break;
                    case ObjectResult o :                // plain object
                        this.WrapObjectResult(resultContext,o.Value);
                        break;
                    case JsonResult j :                  // plain json
                        this.WrapObjectResult(resultContext,j.Value);
                        break;
                    case StatusCodeResult s:             // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case 
                        var swrapper = this.WrapObjectResult(resultContext, new {});
                        swrapper.StatusCode = s.StatusCode;
                        break;
                }
            }
        }
    
    }
    

    别忘了将格式化程序注册为服务:

        services.AddScoped<SuperJsonOutputFormatter>();
    

    最后,当您想要启用格式化程序时,只需为控制器或操作添加 [TypeFilter(typeof(SuperJsonOutputFormatterFilter))] 注释即可。

    演示

    让我们为 Test 创建一个动作方法:

    [TypeFilter(typeof(SuperJsonOutputFormatterFilter))]
    public IActionResult Test(int status)
    {
        // test json result(200)
        if(status == 200){ return Json(new { Id = 1, }); }
        // test 400 object result
        else if(status == 400){ return BadRequest( new {}); } 
        // test 404 object result
        else if(status == 404){ return NotFound(new { Id = 1, }); }
        // test exception
        else if(status == 500){ throw new Exception("unexpected exception"); }
        // test status code result
        else if(status == 204){ return new StatusCodeResult(204); }
    
        // test normal object result(200)
        var raw = new ObjectResult(new XModel{
            empId=1999,
            empName = "Conroy, Deborah",
            enrollmentStatus=true,
            primaryFingerprintScore=65,
            secondaryFingerprintScore=60,
            primaryFingerprint = null,
            secondaryFingerprint= null,
            primaryFingerprintType=null,
            secondaryFingerprintType=null
        });
        return raw;
    }
    

    截图:

    【讨论】:

    • 嘿@itminus- 如何处理 post 和 put 请求,因为它们返回状态代码 204 No Content。那么如何在帖子中发送成功消息并提出请求
    • @RahulDev 实际上,我认为 204 不应该被视为错误。在您的场景中,每个响应都应该有内容,NoContentResult 将被转换为StatusCodeResult。我建议您使用方法Created(obj)CreatedAt(...)new CreatedAtActionResult(...) 来创建。
    • 嘿@itminus-上面的代码不适用于.net core 3.1。有没有办法在 newtonsoftjsonoutputformatter 中实现相同的逻辑?
    猜你喜欢
    • 1970-01-01
    • 2019-07-23
    • 1970-01-01
    • 2018-09-24
    • 1970-01-01
    • 2019-12-16
    • 2018-11-30
    • 1970-01-01
    相关资源
    最近更新 更多