欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Core AutoWrapper 自定义响应输出

程序员文章站 2022-07-09 18:44:22
前言 AutoWrapper是一个简单可自定义全局异常处理程序和ASP.NET Core API响应的包装。他使用ASP.NET Core middleware拦截传入的HTTP请求,并将最后的结果使用统一的格式来自动包装起来.目的主要是让我们更多的关注业务特定的代码要求,并让包装器自动处理HTTP ......

前言

autowrapper是一个简单可自定义全局异常处理程序和asp.net core api响应的包装。他使用asp.net core middleware拦截传入的http请求,并将最后的结果使用统一的格式来自动包装起来.目的主要是让我们更多的关注业务特定的代码要求,并让包装器自动处理http响应。这可以在构建api时加快开发时间,同时为http响应试试我们统一的标准。

安装

autowrapper.core从nuget或通过cli下载并安装

pm> install-package autowrapper.core 

在startup.cs configure方法中注册以下内容,但是切记要放在userouting前

app.useapiresponseandexceptionwrapper();  

启动属性映射

默认情况下autowrapper将在成功请求成功时输出以下格式:

{
    "message": "request successful.",
    "iserror": false,
    "result": [
      {
        "id": 7002,
        "firstname": "vianne",
        "lastname": "durano",
        "dateofbirth": "2018-11-01t00:00:00"
      }
    ]
}

如果说不喜欢默认属性命名方式,那么我们可以通过autowrapperpropertymap属性进行映射为我们需要指定的任何名称。例如我么可以将result属性的名称更改为data。如下所示

public class mapresponseobject  
{
    [autowrapperpropertymap(prop.result)]
    public object data { get; set; }
}

然后将mapresponseobject类传递给autpwrapper middleware

app.useapiresponseandexceptionwrapper<mapresponseobject>();  

通过映射重新请求后,现在影响格式如下所示

{
    "message": "request successful.",
    "iserror": false,
    "data": {
        "id": 7002,
        "firstname": "vianne",
        "lastname": "durano",
        "dateofbirth": "2018-11-01t00:00:00"
    }
}

可以从中看出result属性已经更换为data属性了

默认情况下autowrapper发生异常时将吐出以下响应格式

{
    "iserror": true,
    "responseexception": {
        "exceptionmessage": "unhandled exception occurred. unable to process the request."
    }
}

而且如果在autowrapperoptions中设置了isdebug,则将产生带有堆栈跟踪信息的类似信息

{
    "iserror": true,
    "responseexception": {
        "exceptionmessage": " input string was not in a correct format.",
        "details": "   at system.number.throwoverfloworformatexception(parsingstatus status, typecode type)\r\n   at system.number.parseint32(readonlyspan`1 value, numberstyles styles, numberformatinfo info)\r\n …"
    }
}

如果想将某些apierror属性名称更改为其他名称,只需要在以下代码中添加以下映射mapresponseobject

public class mapresponseobject  
{
    [autowrapperpropertymap(prop.responseexception)]
    public object error { get; set; }

    [autowrapperpropertymap(prop.responseexception_exceptionmessage)]
    public string message { get; set; }

    [autowrapperpropertymap(prop.responseexception_details)]
    public string stacktrace { get; set; }
}

通过如下代码来模拟错误

int num = convert.toint32("10s"); 

现在映射后的输出如下所示

{
    "iserror": true,
    "error": {
        "message": " input string was not in a correct format.",
        "stacktrace": "   at system.number.throwoverfloworformatexception(parsingstatus status, typecode type)\r\n   at system.number.parseint32(readonlyspan`1 value, numberstyles styles, numberformatinfo info)\r\n …"
    }
}

请注意apierror现在根据mapresponseobject类中定义的属性更改了模型的默认属性。

我们可以*的选择映射任何属性,下面是映射属性相对应的列表

[autowrapperpropertymap(prop.version)]
[autowrapperpropertymap(prop.statuscode)]
[autowrapperpropertymap(prop.message)]
[autowrapperpropertymap(prop.iserror)]
[autowrapperpropertymap(prop.result)]
[autowrapperpropertymap(prop.responseexception)]
[autowrapperpropertymap(prop.responseexception_exceptionmessage)]
[autowrapperpropertymap(prop.responseexception_details)]
[autowrapperpropertymap(prop.responseexception_referenceerrorcode)]
[autowrapperpropertymap(prop.responseexception_referencedocumentlink)]
[autowrapperpropertymap(prop.responseexception_validationerrors)]
[autowrapperpropertymap(prop.responseexception_validationerrors_field)]
[autowrapperpropertymap(prop.responseexception_validationerrors_message)]

自定义错误架构

autowrapper还提供了一个apiexception可用于定义自己的异常的对象,如果想抛出自己的异常消息,则可以简单地执行以下操作

throw new apiexception("error blah", 400, "511", "http://blah.com/error/511");  

默认输出格式如下所示

{
    "iserror": true,
    "responseexception": {
        "exceptionmessage": "error blah",
        "referenceerrorcode": "511",
        "referencedocumentlink": "http://blah.com/error/511"
    }
}

当然我们可以自定义错误格式

public class mapresponseobject  
{
    [autowrapperpropertymap(prop.responseexception)]
    public object error { get; set; }
}

public class error  
{
    public string message { get; set; }

    public string code { get; set; }
    public innererror innererror { get; set; }

    public error(string message, string code, innererror inner)
    {
        this.message = message;
        this.code = code;
        this.innererror = inner;
    }

}

public class innererror  
{
    public string requestid { get; set; }
    public string date { get; set; }

    public innererror(string reqid, string reqdate)
    {
        this.requestid = reqid;
        this.date = reqdate;
    }
}

然后我们可以通过如下代码进行引发我们错误

throw new apiexception(  
      new error("an error blah.", "invalidrange",
      new innererror("12345678", datetime.now.toshortdatestring())
));

输出格式如下所示

{
    "iserror": true,
    "error": {
        "message": "an error blah.",
        "code": "invalidrange",
        "innererror": {
            "requestid": "12345678",
            "date": "10/16/2019"
        }
    }
}

使用自定义api响应格式

如果映射满足不了我们的需求。并且我们需要向api响应模型中添加其他属性,那么我们现在可以自定义自己的格式类,通过设置usecustomschema为true来实现,代码如下所示

app.useapiresponseandexceptionwrapper(new autowrapperoptions { usecustomschema = true });  

现在假设我们想在主api中响应中包含一个属性sentdate和pagination对象,我们可能希望将api响应模型定义为以下格式

public class mycustomapiresponse  
{
    public int code { get; set; }
    public string message { get; set; }
    public object payload { get; set; }
    public datetime sentdate { get; set; }
    public pagination pagination { get; set; }

    public mycustomapiresponse(datetime sentdate, object payload = null, string message = "", int statuscode = 200, pagination pagination = null)
    {
        this.code = statuscode;
        this.message = message == string.empty ? "success" : message;
        this.payload = payload;
        this.sentdate = sentdate;
        this.pagination = pagination;
    }

    public mycustomapiresponse(datetime sentdate, object payload = null, pagination pagination = null)
    {
        this.code = 200;
        this.message = "success";
        this.payload = payload;
        this.sentdate = sentdate;
        this.pagination = pagination;
    }

    public mycustomapiresponse(object payload)
    {
        this.code = 200;
        this.payload = payload;
    }

}

public class pagination  
{
    public int totalitemscount { get; set; }
    public int pagesize { get; set; }
    public int currentpage { get; set; }
    public int totalpages { get; set; }
}

通过如下代码片段进行测试结果

public async task<mycustomapiresponse> get()  
{
    var data = await _personmanager.getallasync();

    return new mycustomapiresponse(datetime.utcnow, data,
        new pagination
        {
            currentpage = 1,
            pagesize = 10,
            totalitemscount = 200,
            totalpages = 20
        });

}

运行后会得到如下影响格式

{
    "code": 200,
    "message": "success",
    "payload": [
        {
            "id": 1,
            "firstname": "vianne",
            "lastname": "durano",
            "dateofbirth": "2018-11-01t00:00:00"
        },
        {
            "id": 2,
            "firstname": "vynn",
            "lastname": "durano",
            "dateofbirth": "2018-11-01t00:00:00"
        },
        {
            "id": 3,
            "firstname": "mitch",
            "lastname": "durano",
            "dateofbirth": "2018-11-01t00:00:00"
        }
    ],
    "sentdate": "2019-10-17t02:26:32.5242353z",
    "pagination": {
        "totalitemscount": 200,
        "pagesize": 10,
        "currentpage": 1,
        "totalpages": 20
    }
}

但是从这里要注意一旦我们对api响应进行自定义,那么就代表我们完全控制了要格式化数据的方式,同时丢失了默认api响应的某些选项配置。但是我们仍然可以利用apiexception()方法引发用户定义的错误消息
如下所示

[route("{id:long}")]
[httpput]
public async task<mycustomapiresponse> put(long id, [frombody] persondto dto)  
{
    if (modelstate.isvalid)
    {
        try
        {
            var person = _mapper.map<person>(dto);
            person.id = id;

            if (await _personmanager.updateasync(person))
                return new mycustomapiresponse(datetime.utcnow, true, "update successful.");
            else
                throw new apiexception($"record with id: {id} does not exist.", 400);
        }
        catch (exception ex)
        {
            _logger.log(loglevel.error, ex, "error when trying to update with id:{@id}", id);
            throw;
        }
    }
    else
        throw new apiexception(modelstate.allerrors());
}

现在当进行模型验证时,可以获得默认响应格式

{
    "iserror": true,
    "responseexception": {
        "exceptionmessage": "request responded with validation error(s). please correct the specified validation errors and try again.",
        "validationerrors": [
            {
                "field": "firstname",
                "message": "'first name' must not be empty."
            }
        ]
    }
}

reference

https://github.com/proudmonkey/autowrapper