asp.net-core - 在 .net core web api 中发送自定义错误响应的最佳方法是什么

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

我正在使用 .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   
}

最佳答案

I expect all the error codes to be sent as generic error messages like the JSON below

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

对格式化程序进行一点更改

首先,您的格式化程序没有返回具有与您想要的相同架构的 json。因此,我创建一个虚拟类来保存错误代码错误描述的信息:

public class ErrorDescription{
    public ErrorDescription(HttpStatusCode statusCode)
    {
        this.Code = (int)statusCode;
        this.Description = statusCode.ToString();
    }

    [JsonProperty("error code")]
    public int Code {get;set;}
    [JsonProperty("error description")]
    public string Description {get;set;}
}

And change your WriteResponseBodyAsync() method as below:

    ...

    using (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) {
        var statusCode = context.HttpContext.Response.StatusCode;
        var rewrittenValue = new {
            status = IsSucceeded(statusCode),
            error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
            data  = context.Object,
        };
        this.WriteObject(writer, rewrittenValue);
        await writer.FlushAsync();
    }

Here the IsSucceeded(statusCode) is a simple helper method that you can custom as you need:

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;
}

启用格式化程序

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

这是动态启用自定义格式化程序的过滤器的实现:

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>();

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

演示

让我们为测试创建一个操作方法:

[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;
}

屏幕截图:

enter image description here

关于asp.net-core - 在 .net core web api 中发送自定义错误响应的最佳方法是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57869900/

相关文章:

razor - 如何有条件地禁用标签助手中的 <select>?

c# - 如何找到 Microsoft.AspNetCore.App 共享框架中包含的所有包?

json - Microsoft.Extensions.Configuration 只读?!?!真的吗?

c# - 如何从 .NET 中的异常堆栈跟踪中隐藏当前方法?

java - 这两种方法中哪一种是处理使用 JDBC 的 DAO 类中关闭 ResultSet、PreparedStatement 和 Connection 的 try-catch 的最佳方法?

ASP.NET Core 1.0 - .NET 4.5 项目的 API 引用

angular - 在 Asp.NET Core 之上构建 Angular 4 应用程序的最佳方式是什么?

android - IllegalArgumentException : Invalid int: "OS" with Samsung tts

asp.net-core-mvc - 为什么 Url.RouteUrl 不为具有约束的路由生成地址

.net - 无法实例化实现类型