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

AutoWrapper:使用有意义的响应美化您的 ASP.NET Core API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (13投票s)

2019年9月17日

CPOL

11分钟阅读

viewsIcon

32011

downloadIcon

201

在本帖中,我们将了解如何使用 AutoWrapper 美化我们的 ASP.NET Core API 响应。

引言

引用

更新:  

  • 2019/10/17:AutoWrapper 版本 2.0.1 - 添加了新功能。
  • 2019/10/06:AutoWrapper 版本 1.2.0 - 重构、清理和错误修复,以支持 SPA。
  • 2019/10/04:AutoWrapper 版本 1.1.0,新增了选项。
  • 2019/09/23:AutoWrapper 版本 1.0.0正式发布,并添加了一些选项配置。

在为“实际”应用程序项目构建 API 时,大多数开发人员都忽略了为消费者提供有意义响应的重要性。这可能是因为他们的开发时间有限、没有标准的 HTTP 响应格式,或者仅仅是因为他们不关心响应,只要 API 能向消费者返回所需数据即可。然而,API 不仅仅是来回通过 HTTP 传递 JSON,更重要的是您如何向使用它的开发人员呈现有意义的响应。

正如有人曾经说过的……

一个好的 API 设计就是为使用它的开发人员提供良好的用户体验。

作为一名重视消费者的 API 开发人员,我们希望为他们提供有意义且一致的 API 响应。

ASP.NET Core 使我们能够轻松创建 RESTful API;然而,它们默认情况下不为成功的请求和错误提供一致的响应。如果您采用 RESTful 方法来构建 API,那么您将利用 HTTP 动词,如 GET、POST、PUT 和 DELETE。这些操作中的每一个都可能根据端点的设计返回不同的类型。您的 POST、PUT 和 DELETE 端点可能会返回数据,也可能不返回数据。您的 GET 端点可能会返回一个 string、一个 List、一个某种类型的 IEnumerable,甚至是一个 object。另一方面,如果您的 API 抛出错误,它将返回一个 object,最糟糕的情况是返回一个说明错误原因的 HTML 字符串。所有这些响应之间的差异使得 API 难以使用,因为消费者需要了解每种情况下返回的数据的类型和结构。客户端代码和服务器端代码都会变得难以管理。

去年,我创建了几个 Nuget ,用于使用自定义 object 包装器来管理 RESTful API 的异常和响应一致性。

令我惊讶的是,这两个包现在已有数百次下载,并被其他开源项目使用,例如 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<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> 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");

上面的代码试图将一个包含非数字值的字符串转换为 integer 类型,这将在运行时导致错误。响应输出将如下所示:

图 1:未处理的异常

真糟糕!

您能想象那些使用您的 API 的开发人员看到这种响应格式时的失望吗?至少堆栈跟踪信息有点用,因为它让您了解了错误的原因,但出于安全风险的考虑,这应该被处理,绝不能让您的 API 消费者看到这些信息。堆栈跟踪信息在开发和调试阶段非常有用。在生产环境中,应处理此类详细错误,将其记录在某处进行分析,并向消费者返回有意义的响应。

另一种情况是,当您尝试访问一个不存在的 API 端点时,除了著名的 404 (Not Found) HTTP 状态码之外,您将一无所获。

在处理 RESTful API 时,处理异常并为 API 处理的所有请求返回一致的响应(无论成功还是失败)非常重要。这使得 API 更容易使用,而无需在客户端编写复杂的代码。

AutoWrapper.Core 来救援

AutoWrapper 会处理传入的 HTTP 请求,并通过为成功和错误结果提供一致的响应格式来自动包装响应。其目标是让您专注于业务特定需求,而让包装器处理 HTTP 响应。想象一下,在强制执行 HTTP 响应标准的同时,您可以节省多少开发 API 的时间。

AutoWrapper 是基于 VMD.RESTApiResponseWrapper.Core 的一个分支项目,该项目旨在支持 .NET Core 3.x 及更高版本。对该包的实现进行了重构,以便提供一种更便捷的方式来使用中间件,并增加了灵活性。

主要特点

  • 异常处理
  • ModelState 验证错误处理(支持 Data Annotation 和 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. 在 Startup.cs 的 Configure() 方法中,在 UseRouting() 中间件“之前”注册以下中间件:

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,该 API 可与各种内置和第三方日志记录提供程序配合使用。根据您使用的支持的 .NET Core 日志记录提供程序以及如何配置日志记录数据的位置(例如文本文件、云等),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 属性会被自动省略,取而代之的是 display 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<ApiResponse> 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<ApiResponse> 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 对象包含以下三个重载构造函数,您可以使用它们来定义异常:

ApiException(string message, int statusCode = 500, string errorCode = "", string refLink = "")
ApiException(IEnumerable<ValidationError> errors, int statusCode = 400)
ApiException(System.Exception ex, int statusCode = 500)

选项

以下属性是您可以设置的选项:

版本 1.0.0

  • ApiVersion
  • ShowApiVersion
  • ShowStatusCode
  • IsDebug

版本 1.1.0 新增内容

  • 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

如果您想在响应中显示状态码,可以这样做:

app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { ShowStatusCode = true });

IsDebug

默认情况下,AutoWrapper 会隐藏堆栈跟踪信息。如果您想在开发阶段从响应中查看实际的错误详细信息,只需将 AutoWrapperOptions 的 IsDebug 设置为 true 即可。

app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsDebug = true }); 

IsApiOnly

AutoWrapper 旨在仅用于 ASP.NET Core API 项目模板。如果您将 API Controller 与前端项目(如 Angular、MVC、React、Blazor 以及其他支持 .NET Core 的 SPA 框架)结合使用,请使用此属性启用它。

app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { IsApiOnly = false} );

WrapWhenApiPathStartsWith

如果将 IsApiOnly 选项设置为 false,您还可以指定 API 路径段进行验证。默认设置为 "/api"。如果您想将其设置为其他值,可以这样做:

app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { 
    IsApiOnly = false, 
    WrapWhenApiPathStartsWith = "/myapi" 
});

这将激活 AutoWrapper,在请求路径包含 WrapWhenApiPathStartsWith 值时拦截 HTTP 响应。

引用

请注意,我仍然建议您将 API Controller 实现为单独的项目,以实现关注点分离,并避免将 SPA 和 API 的路由配置混合在一起。

日志支持

AutoWrapper 的另一个优点是日志记录已预先配置好。.NET Core 应用默认具有内置的日志记录机制,并且包装器拦截的任何请求和响应都将被自动记录(感谢依赖注入!)。.NET Core 支持一个日志记录 API,该 API 可与各种内置和第三方日志记录提供程序配合使用。根据您使用的支持的 .NET Core 日志记录提供程序以及如何配置日志记录数据的位置(例如文本文件、云等),AutoWrapper 会自动将日志写入其中。

Swagger 支持

Swagger 为您的 API 提供高级文档,允许开发人员参考您的 API 端点的详细信息并在需要时进行测试。当您的 API 是公开的,并且您期望许多开发人员使用它时,这非常有帮助。

AutoWrapper 会忽略 URL 中包含“/swagger”的请求,因此您仍然可以导航到 API 文档的 Swagger UI。

摘要

在本文中,我们学习了如何在 ASP.NET Core 应用程序中集成和使用 AutoWrapper 的核心功能。

我确信这个项目还有很多可以改进的地方,所以请随意尝试并告诉我您的想法。欢迎评论和建议,请留下信息,我很乐意尽我所能回答任何问题。

查看版本 2 的新功能:ASP.NET Core with AutoWrapper:自定义默认响应输出

GitHub 仓库

https://github.com/proudmonkey/AutoWrapper

参考文献

发布历史

  • 2019/10/17:AutoWrapper 版本 2.0.1 - 添加了新功能。
  • 2019/10/06:AutoWrapper 版本 1.2.0 - 重构、清理和错误修复,以支持 SPA。
  • 2019/10/04:AutoWrapper 版本 1.1.0,新增了选项。
  • 2019/09/23:AutoWrapper 版本 1.0.0 正式发布。
  • 2019/09/14:AutoWrapper 版本 1.0.0-rc 预发布。
© . All rights reserved.