65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core with AutoWrapper:自定义默认响应输出

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019年10月18日

CPOL

6分钟阅读

viewsIcon

12070

重点介绍了 AutoWrapper 2.0 版本的新增功能。AutoWrapper 是一个简单但可自定义的 ASP.NET Core API 全局异常处理程序和响应包装器。

引言

上个月,我发布了 AutoWrapper 1.x 版本,令人惊叹的是它已有数百次下载。在一个月内取得如此大的进展,真是令人欣慰!我很高兴它能造福许多开发人员,感谢大家的支持和反馈。我非常感激。

在我之前的帖子中,我介绍了 AutoWrapper 是什么,并演示了如何使用它来美化您的 ASP.NET Core API HTTP 响应,使其具有一致且有意义的信息。如果您还没有看过,我建议您先查看我之前的帖子:AutoWrapper:用有意义的响应美化您的 ASP.NET Core API

昨天,AutoWrapper 版本 2.0.1 发布,并根据社区反馈添加了一些新功能。

什么是 AutoWrapper

简单回顾一下,AutoWrapper 是一个简单但可自定义的 ASP.NET Core API 全局异常处理程序和响应包装器。它使用 ASP.NET Core 中间件 来拦截传入的 HTTP 请求,并通过为成功和错误结果提供一致的响应格式来自动包装响应。目标是让您专注于业务代码要求,让包装器自动处理 HTTP 响应。这可以加快 API 开发速度,同时强制执行您自己的 HTTP 响应标准。

安装

  1. 从 NuGet 或通过 CLI 下载并安装最新的 AutoWrapper.Core
    PM> Install-Package AutoWrapper.Core -Version 2.0.1 
  2. Startup.cs 文件中声明以下命名空间
    using AutoWrapper;
  3. Startup.csConfigure() 方法中,在 UseRouting() 中间件 "之前" 注册以下 中间件
    app.UseApiResponseAndExceptionWrapper();

就这么简单!

1.x 版本

AutoWrapper 的先前版本已经提供了核心功能,并提供了一些属性来控制包装器如何生成输出。但是,它不允许您自定义响应对象本身。根据我从开发人员那里收到的类似反馈和请求,我决定发布新版本的 AutoWrapper 来解决其中的大部分问题。

2.0 版本有哪些新功能?

最新版本的 AutoWrapper 提供了更高的灵活性,可根据您的需求使用。以下是新增功能:

  • 启用默认 ApiResponse 属性的属性名映射
  • 添加了支持来实现您自定义的 ResponseError 架构/对象
  • 添加了 IgnoreNullValueUseCamelCaseNamingStrategy 选项。这两个属性默认都设置为 true
  • netcoreapp2.1netcoreapp.2.2 .NET Core 框架启用向后兼容性支持。
  • 从响应输出中排除值为 Null 的属性

启用属性映射

这项功能是请求最多的。默认情况下,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 类传递给 AutoWrapper 中间件

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

如果您不喜欢默认错误格式的结构,现在可以定义您自己的 Error 对象,并将其传递给 ApiException() 方法。例如,如果您有以下带有映射配置的 Error 模型:

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 响应架构添加其他属性,现在可以通过在 AutoWrapperOptions 中将 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; }
}

要测试结果,您可以创建类似以下的 GET 方法:

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() 方法来抛出用户定义的错误消息。例如,您可以像这样定义您的 PUT 方法:

[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."
            }
        ]
    }
}

如果您不喜欢默认错误响应的结构或名称,您可以将映射对象传递给 AutoWrapper 中间件,或者实现您自己的错误架构,如前一节所示。

对 NetCoreApp2.1 和 NetCoreApp2.2 的支持

AutoWrapper 2.x 版本现在也支持 .NET Core 2.1 和 2.2。您只需要先安装 Nuget 包 Newtonsoft.json,然后再安装 AutoWrapper.Core

摘要

在本文中,我们学习了如何在 ASP.NET Core 应用程序中集成和使用 AutoWrapper 2.0 的新功能。上面的示例基于 ApiBoilerPlate 项目模板。

请留下您的评论和建议,以便我继续为该项目进行未来的改进。您也可以自由贡献,因为这是一个开源项目。:)

参考文献

历史

  • 2019年10月18日:初次发布
© . All rights reserved.