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





5.00/5 (2投票s)
重点介绍了 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
响应标准。
安装
- 从 NuGet 或通过 CLI 下载并安装最新的
AutoWrapper.Core
PM> Install-Package AutoWrapper.Core -Version 2.0.1
- 在 Startup.cs 文件中声明以下命名空间
using AutoWrapper;
- 在 Startup.cs 的
Configure()
方法中,在UseRouting()
中间件
"之前" 注册以下中间件
。app.UseApiResponseAndExceptionWrapper();
就这么简单!
1.x 版本
AutoWrapper
的先前版本已经提供了核心功能,并提供了一些属性来控制包装器如何生成输出。但是,它不允许您自定义响应对象本身。根据我从开发人员那里收到的类似反馈和请求,我决定发布新版本的 AutoWrapper
来解决其中的大部分问题。
2.0 版本有哪些新功能?
最新版本的 AutoWrapper
提供了更高的灵活性,可根据您的需求使用。以下是新增功能:
- 启用默认
ApiResponse
属性的属性名映射 - 添加了支持来实现您自定义的
Response
和Error
架构/对象 - 添加了
IgnoreNullValue
和UseCamelCaseNamingStrategy
选项。这两个属性默认都设置为true
。 - 为
netcoreapp2.1
和netcoreapp.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日:初次发布