目录
介绍
默认的ASP.NET Core API响应
AutoWrapper.Core的救援
主要特点:
TL,DR 给我看代码
定义自己的自定义消息
定义自己的Api异常
选项
版本1.0.0
1.1.0版添加
支持日志
支持Swagger
总结
GitHub Repo
参考文献
介绍在为“实际”应用程序项目构建API时,大多数开发人员忘记了向其消费者提供有意义的响应的重要性。发生这种情况有几个原因;可能是他们的开发时间有限,他们没有标准的HTTP 响应格式,或者只是只要API 将所需的数据返回给消费者,他们就不在乎响应。嗯,API不仅要通过 HTTP传递JSON ,还应如何向使用它的开发人员提出有意义的响应。
就像有人曾经说过的…
“一个好的API设计是使用它的开发人员的UX。”
作为重视消费者的API 开发者,我们希望对他们做出有意义且一致的API响应。
ASP.NET Core 使我们能够快速创建REST API。但是,对于开箱即用的成功请求和错误,它们不会提供一致的响应。如果你对API采用RESTful方式,那么你将被使用HTTP动词,如GET、POST、PUT和DELETE。根据您的方法/操作的设计方式,每个操作都可能返回不同的类型。你的POST、PUT和DELETE终端可能会返回一个数据,或者根本没有。您的GET终端可能返回一个 string、 List、某种类型的IEnumerable ,甚至是一个object。另一方面,如果您的API 抛出错误,它将返回一个object 或更糟糕的指出错误原因的 HTML 字符串。所有这些响应之间的差异使得使用API变得很困难,因为使用者需要了解每种情况下返回的数据的类型和结构。客户代码和服务代码都变得难以管理。
去年,我创建了两个用于管理异常和响应一致性的Nuget 包,使用一个自定义的用于restful API 的object包装器。
令我惊讶的是,这两个软件包现在都有数百次下载,并且被Blazor Boilerplate等其他开源项目使用。
虽然我认为这两个软件包都成功,但它们仍然存在一些故障,因此我决定创建一个新软件包来重构代码库,应用错误修复并为其添加新功能。
在本文中,我们将研究如何使用AutoWrapper来美化我们的ASP.NET Core API响应。
默认的ASP.NET Core API响应当您创建新的ASP.NET Core API模板时,Visual Studio将构建所有必需的文件和依赖项,以帮助您开始构建RESTful API。生成的模板包含一个“WeatherForecastController”,用于使用静态数据模拟简单的GET请求,如以下代码所示:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger _logger;
public WeatherForecastController(ILogger logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
运行应用程序时,将以JSON格式显示以下输出:
[
{
"date": "2019-09-16T13:08:39.5994786-05:00",
"temperatureC": -8,
"temperatureF": 18,
"summary": "Bracing"
},
{
"date": "2019-09-17T13:08:39.5995153-05:00",
"temperatureC": 54,
"temperatureF": 129,
"summary": "Cool"
},
{
"date": "2019-09-18T13:08:39.5995162-05:00",
"temperatureC": 33,
"temperatureF": 91,
"summary": "Bracing"
},
{
"date": "2019-09-19T13:08:39.5995166-05:00",
"temperatureC": 38,
"temperatureF": 100,
"summary": "Balmy"
},
{
"date": "2019-09-20T13:08:39.599517-05:00",
"temperatureC": -3,
"temperatureF": 27,
"summary": "Sweltering"
}
]
太好了,其API的工作与我们期望的一样,只是它没有给我们带来有意义的回应。我们知道数据是响应中非常重要的部分,但是,随便吐出数据是因为JSON响应实际上并没有太大帮助,尤其是当每个请求之间发生意外行为时。
例如,以下代码模拟了您的代码中可能发生的意外错误:
//This is just an idiot example to trigger an error
int num = Convert.ToInt32("a");
上面的代码尝试将包含非数字值的string转换为integer类型,这将在运行时导致错误。响应输出看起来像这样:
图1:未处理的异常
你能想象那些使用你的API从响应中看到这种格式的开发人员的失望吗?至少堆栈跟踪信息很有用,因为它可以使您了解错误的原因,但是应该进行处理,并且永远不要让您的API使用者看到该信息有安全隐患。堆栈跟踪信息在开发阶段和调试过程中绝对有帮助。在生产中,应处理此类详细错误,将其记录在某处进行分析,然后将有意义的响应返回给消费者。
另一种情况是,当您尝试访问 不存在的API终端时,除了著名的404 (Not Found) Http状态代码以外,没有任何回应。
使用REST API时,重要的是处理异常并为API处理的所有请求返回一致的响应,无论成功或失败。这使得使用API变得容易得多,而无需在客户端上使用复杂的代码。
AutoWrapper.Core的救援AutoWrapper 会处理传入的 HTTP 请求,并通过为成功和错误结果提供一致的响应格式来自动为您包装响应。目的是让您专注于特定于业务的需求,并由包装程序处理HTTP 响应。想象一下在执行HTTP 响应标准时,您在开发API上节省的时间。
AutoWrapper 是基于VMD.RESTApiResponseWrapper.Core的项目分支 ,旨在支持.NET Core 3.x及更高版本。对该程序包的实现进行了重构,以提供更方便的方式来使用中间件,从而增加了灵活性。
主要特点:- 异常处理
- ModelState验证错误处理(同时支持数据注释和FluentValidation)
- 可配置的API异常
- 结果和错误的一致响应格式
- 详细的结果回复
- 详细的错误响应
- 可配置的HTTP状态码和消息
- 添加对Swagger的支持
- 添加对请求、响应和异常的日志支持
- 在中间件中添加选项以设置ApiVersion和IsDebug属性
仅需几步,您就可以将API Controller转换为返回一些有意义的响应,而无需付出很多开发工作。您要做的就是:
1. 从NuGet或通过CLI 下载并安装最新版本的AutoWrapper.Core:
PM> Install-Package AutoWrapper.Core -Version 1.0.1-rc
引用:
注意:目前,这是一个预发行版本,一旦.NET Core 3发布,它将正式发行。
2.在Startup.cs中声明以下名称空间
using AutoWrapper;
3.在UseRouting()中间件“之前”的Startup.cs的Configure()方法中注册下面的中间件:
app.UseApiResponseAndExceptionWrapper();
默认的API版本格式设置为“ 1.0.0.0”。如果希望为API指定其他版本格式,则可以执行以下操作:
app.UseApiResponseAndExceptionWrapper(new ApiResponseOptions { ApiVersion = "2.0" });
很简单!现在,尝试再次构建并运行ASP.NET Core API默认应用程序。根据我们的示例,以下是“WeatherForecastController” API 响应的样子:
{
"message": "Request successful.",
"isError": false,
"result": [
{
"date": "2019-09-16T23:37:51.5544349-05:00",
"temperatureC": 21,
"temperatureF": 69,
"summary": "Mild"
},
{
"date": "2019-09-17T23:37:51.554466-05:00",
"temperatureC": 28,
"temperatureF": 82,
"summary": "Cool"
},
{
"date": "2019-09-18T23:37:51.554467-05:00",
"temperatureC": 21,
"temperatureF": 69,
"summary": "Sweltering"
},
{
"date": "2019-09-19T23:37:51.5544676-05:00",
"temperatureC": 53,
"temperatureF": 127,
"summary": "Chilly"
},
{
"date": "2019-09-20T23:37:51.5544681-05:00",
"temperatureC": 22,
"temperatureF": 71,
"summary": "Bracing"
}
]
}
如果您注意到了,那么输出现在在响应中包含一些属性,例如message,isError以及在result属性中包含的实际数据。
关于AutoWrapper的另一个好处是,日志记录已经预先配置。默认情况下,.NET Core应用程序具有内置的日志记录机制,并且包装程序已拦截的所有请求和响应都将被自动记录。对于此示例,它将在Visual Studio控制台窗口中显示如下内容:
图2:Visual Studio控制台日志
.NET Core支持与各种内置和第三方日志记录提供程序一起使用的日志记录API。根据您使用哪种受支持的.NET Core日志记录提供程序以及如何配置记录数据的位置(例如,文本文件,Cloud等),AutoWrapper会自动为您写入日志。
这是当您尝试指向不存在的URL时的另一个输出示例:
{
"isError": true,
"responseException": {
"exceptionMessage": "Request not found. The specified uri does not exist.",
"details": null,
"referenceErrorCode": null,
"referenceDocumentLink": null,
"validationErrors": null
}
}
现在注意到响应对象是如何更改的。statusCode自动设置为404。当已经发生任何意外错误或异常时,result属性将自动省略,而显示responseException属性以显示错误消息和额外信息。
请记住,任何错误或异常也将被记录。例如,如果我们再次运行以下代码:
//This is just an idiot example to trigger an error
int num = Convert.ToInt32("a");
现在它将给您以下响应:
{
"isError": true,
"responseException": {
"exceptionMessage": "Unhandled Exception occured. Unable to process the request.",
"details": null,
"referenceErrorCode": null,
"referenceDocumentLink": null,
"validationErrors": null
}
}
控制台窗口将显示以下内容:
图3:Visual Studio控制台日志
默认情况下,AutoWrapper禁止显示堆栈跟踪信息。如果要在开发阶段从响应中查看错误的实际详细信息,只需将AutoWrapperOptions IsDebug设置为true:
app.UseApiResponseAndExceptionWrapper( new AutoWrapperOptions { IsDebug = true });
现在,当您再次运行该应用程序以触发异常时,它将显示如下内容:
{
"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 at System.Convert.ToInt32(String value)\r\n at AutoWrapperDemo.Controllers.WeatherForecastController.Get() in . . . . . . .,
"referenceErrorCode": null,
"referenceDocumentLink": null,
"validationErrors": null
}
}
注意,现在显示了真正的异常消息及其详细信息。
要在响应中显示自定义消息,请使用AutoWrapper.Wrappers命名空间中的ApiResponse对象。例如,如果要在成功POST完成后显示一条消息,则可以执行以下操作:
[HttpPost]
public async Task Post([FromBody]CreateBandDTO band)
{
//Call a method to add a new record to the database
try
{
var result = await SampleData.AddNew(band);
return new ApiResponse("New record has been created to the database", result, 201);
}
catch (Exception ex)
{
//TO DO: Log ex
throw;
}
}
成功运行代码将为您提供以下结果:
{
"message": "New record has been created to the database",
"isError": false,
"result": 100
}
ApiResponse对象具有可以设置的以下参数:
ApiResponse(string message, object result = null, int statusCode = 200, string apiVersion = "1.0.0.0")
定义自己的Api异常
AutoWrapper还提供了可用于定义自己的异常的ApiException对象。例如,如果您想抛出自己的异常消息,则可以简单地执行以下操作:
用于捕获ModelState验证错误
throw new ApiException(ModelState.AllErrors());
用于抛出您自己的异常消息
throw new ApiException($"Record with id: {id} does not exist.", 400);
例如,让我们使用ModelState验证来修改POST方法:
[HttpPost]
public async Task Post([FromBody]CreateBandDTO band)
{
if (ModelState.IsValid)
{
//Call a method to add a new record to the database
try
{
var result = await SampleData.AddNew(band);
return new ApiResponse("New record has been created to the database", result, 201);
}
catch (Exception ex)
{
//TO DO: Log ex
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.",
"details": null,
"referenceErrorCode": null,
"referenceDocumentLink": null,
"validationErrors": [
{
"field": "Name",
"message": "The Name field is required."
}
]
}
}
查看如何使用模型中的违规字段自动填充validationErrors属性。
ApiException对象包含以下三个重载constructors,可用于定义异常:
ApiException(string message, int statusCode = 500, string errorCode = "", string refLink = "")
ApiException(IEnumerable errors, int statusCode = 400)
ApiException(System.Exception ex, int statusCode = 500)
选项
以下属性是可以设置的选项:
版本1.0.0- ApiVersion
- ShowApiVersion
- ShowStatusCode
- IsDebug
- IsApiOnly
- WrapWhenApiPathStartsWith
ShowApiVersion
如果要 在响应中显示API版本,则可以执行以下操作:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowApiVersion = true });
默认API 版本格式设置为“ 1.0.0.0”
ApiVersion
如果您希望指定其他版本格式,则可以执行以下操作:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {
ShowApiVersion = true,
ApiVersion = "2.0"
});
ShowStatusCode
如果要在响应中显示StatusCode,则可以执行以下操作:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowStatusCode = true });
IsDebug
默认情况下,AutoWrapper 禁止显示堆栈跟踪信息。如果要在开发阶段从响应中查看错误的实际详细信息,只需将设置AutoWrapperOptions IsDebug 为 true:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsDebug = true });
IsApiOnly
AutoWrapper 仅可用于ASP.NET Core API项目模板。如果要在前端项目(如Angular、MVC、React、Blazor和其他支持.NET Core的SPA框架)中组合API Controllers ,请使用此属性启用它。
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsApiOnly = false} );
WrapWhenApiPathStartsWith
如果将IsApiOnly 选项设置为false,则还可以指定API 路径段以进行验证。默认情况下,它设置为"/api"。如果要将其设置为其他内容,则可以执行以下操作:
app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {
IsApiOnly = false,
WrapWhenApiPathStartsWith = "/myapi"
});
当请求路径包含WrapWhenApiPathStartsWith值时,这将激活AutoWrapper 来拦截HTTP响应。
引用:
请注意,我仍然建议您在一个单独的项目中实现API Controllers,以重视关注点的分离,并避免为您的SPAs和API混合路由配置。
支持日志AutoWrapper 另一个好处是,日志记录已经预先配置。.NET Core应用程序默认情况下具有内置的日志记录机制,并且包装程序已拦截的所有请求和响应都将被自动记录(由于依赖倒置!)。.NET Core支持API与各种内置和第三方日志记录提供程序一起使用的日志记录。根据您使用哪种受支持的.NET Core日志记录提供程序以及如何配置记录数据的位置(例如,文本文件,Cloud等),AutoWrapper会自动为您写入日志。
支持SwaggerSwagger为您的API提供了高级文档,使开发人员可以参考您的API端点的详细信息并在必要时进行测试。这是非常有用的,特别是当您API是公开的并且希望许多开发人员使用它时。
AutoWrapper省略url中带有“/swagger”的任何请求,这样您仍然可以导航到Swagger UI以获得您的API文档。
总结在本文中,我们学习了如何在您的ASP.NET Core应用程序中集成和使用AutoWrapper的核心功能。
我敢肯定,这个项目还有很多地方需要改进,请随时尝试一下,让我知道您的想法。
GitHub Repohttps://github.com/proudmonkey/AutoWrapper
参考文献- ASP.NET Core中间件
- AutoWrapper.Core
- ASP.NET Core 2.1:将VMD.RESTApiResponseWrapper.Core集成到REST API应用程序
- ASP.NET Core和Web API:用于管理异常和一致响应的自定义包装器