ASP.NET Core 2.1:将 VMD.RESTApiResponseWrapper.Core 集成到你的 REST API 应用程序






3.32/5 (4投票s)
一个关于如何将 VMD.RESTApiResponseWrapper.Core 库集成到你的 ASP.NET Core 2.1 REST API 应用程序的快速教程
引言
几个月前,我写了一篇文章,介绍了如何为你的 ASP.NET Core 和 Web API 应用程序创建一个统一的对象响应包装器。我还为该包装器制作了两个版本的 NuGet 包,可以在下面找到
令我惊讶的是,这两个 NuGet 包现在都有了数百次下载,并且我收到了一些开发者的评论和电子邮件,询问如何实际在项目中它们。本文旨在回答我收到的这些常见问题,所以在这里就给出来。
入门
在我们开始之前,我想感谢那些关注这个库并可能已经试用过的人。我非常感谢你们对这个库的所有反馈,并希望它能在你的项目中有所帮助。
废话不多说,让我们看看如何在应用程序中实际使用它。让我们打开 Visual Studio 2017。通过导航到 **文件** > **新建项目** 来创建一个新项目。在“新建项目”对话框的左侧窗格中,选择 **已安装** > **Visual C#** > **Web** > **.NET Core** > **ASP.NET Core Web 应用程序**。项目名称可以随意命名,但在此演示中,我们将其命名为“MyWebApp.API
”,如下图所示
点击 **确定**,它应该会带你到下一个屏幕,如下图所示
选择 **空**,然后点击 **确定**,让 Visual Studio 为你生成默认项目文件和依赖项。这是默认生成的项目文件
让我们快速了解一下每个生成的文件。
如果你已经了解 ASP.NET Core 的核心重要更改,那么你可以跳过这部分,但如果你是 ASP.NET Core 的新手,那么我想强调其中一些更改。如果你之前使用过 ASP.NET 的早期版本,你会注意到新的项目结构完全不同。项目现在包含以下文件
- 依赖项:包含应用程序所需的分析器、NuGet 和 SDK 依赖项
- 属性:包含 `launchSettings.json` 文件,该文件管理与每个调试配置文件(如 IIS、IIS Express 和应用程序本身)关联的应用程序配置设置。在这里,你可以为应用程序使用的框架定义特定于配置文件的配置(编译和调试配置文件)。
- **wwwroot***:一个文件夹,其中将放置所有静态文件。这些是 Web 应用程序将直接提供给客户端的资源,包括 HTML、CSS、图像和 JavaScript 文件。
- **Startup.cs***:这是你放置启动和配置代码的地方。
- **Program.cs***:这是你初始化应用程序所需的所有服务的地方。
模型
让我们创建一些简单的 `Model` 来用于此演示,这些 `Model` 具有以下属性
namespace MyWebApp.API.Model
{
public class Band
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
}
public class CreateBandDTO
{
public string Name { get; set; }
public string Genre { get; set; }
}
public class UpdateBandDTO
{
public string Name { get; set; }
public string Genre { get; set; }
}
}
`Model` 只是简单的类,用于容纳一些属性来保存信息。
模拟数据
我将尽量让这个演示尽可能的简单,所以我不会在这里使用任何数据库,因为这并不是本文的真正意图。如果你想了解如何处理来自数据库的真实数据,请查看我的其他文章
- 开始使用 Entity Framework Core:
数据库优先开发 - Entity Framework Core 入门:使用 Web API 和 Code First 开发构建 ASP.NET Core 应用程序
现在,让我们回到工作。假设我们有以下定义模拟数据的类
public class SampleData
{
public static List<Band> GetBands()
{
return new List<Band>()
{
new Band { Id = 1, Name="Alice in Chains", Genre= "Heavy Metal"},
new Band { Id = 2, Name="Soundgarden", Genre= "Grunge"},
new Band { Id = 3, Name="Pearl Jam", Genre= "Grunge"},
new Band { Id = 4, Name="Audioslave", Genre= "Alternative Metal"},
new Band { Id = 5, Name="Stone Temple Pilots", Genre= "Hard Rock"},
new Band { Id = 6, Name="Nirvana", Genre= "Grunge"},
new Band { Id = 7, Name="Third Eye Blind", Genre= "Alternative Rock"},
new Band { Id = 8, Name="Led Zeppelin", Genre= "Blues Rock"},
new Band { Id = 9, Name="The Beatles", Genre= "Rock and Roll"},
new Band { Id = 10, Name="The Rolling Stones", Genre= "Blues Rock"}
};
}
}
上面的 `class` 只是一个普通的类,带有一个名为 `GetBands()` 的 `public static` 方法。该方法定义了一个 `Band` 类型的 `List`,并向集合中添加了一些默认记录。
创建 API 控制器
让我们创建一个新的 ASP.NET Core API 控制器,并定义一些可用于测试的终结点。我的 `Controller` 类看起来是这样的
using Microsoft.AspNetCore.Mvc;
using MyWebApp.API.Model;
using System.Collections.Generic;
using System.Linq;
namespace MyWebApp.API.v1
{
[Route("api/v1/[controller]")]
[ApiController]
public class BandsController : ControllerBase
{
// GET: api/v1/bands
[HttpGet]
public IEnumerable<Band> Get()
{
return SampleData.GetBands();
}
// GET api/v1/bands/7
[HttpGet("{id}")]
public Band Get(int id)
{
var data = SampleData.GetBands().Where(o => o.Id.Equals(id));
if (data.Any())
return data.First();
return null;
}
// POST api/v1/bands
[HttpPost]
public IActionResult Post([FromBody]CreateBandDTO band)
{
//Call a method to add a new record to the entity
return Ok();
}
// PUT api/v1/bands/7
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody]UpdateBandDTO band)
{
//Call a method to update the entity
return Ok();
}
// DELETE api/bands/7
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
//Call a method to delete an entity
return Ok();
}
}
}
上面的 `Controller` 类包含四个基本的 **HTTP** 方法,如 `GET`、`POST`、`PUT` 和 `DELETE`。典型的 RESTful API 就是这样的。请注意,你找不到 `POST`、`PUT` 和 `DELETE` 的任何代码实现。这是因为我们没有在这里处理数据库或内存中的数据存储。我只是把它们放在那里,以便你可以直观地看到终结点是什么样的。
测试输出
让我们构建并运行应用程序。这是我从 POSTMAN 进行的一些测试的示例屏幕截图
GET:/api/v1/bands
GET:api/v1/bands/{id}
此时,API 可以工作,但问题在于它没有给开发人员有意义的响应。我们知道数据是响应中非常重要的一部分。然而,仅仅将数据作为 **JSON** 响应输出并不是特别有用,尤其是在每次请求之间发生意外行为时。
快速回顾一下,如果你正在采用 RESTful 方法来构建 API,那么你将利用 **HTTP** 动词,如 `GET`、`POST`、`PUT` 和 `DELETE`。这些操作中的每一个都可能根据你的方法/操作的设计返回不同的类型。你的 `POST`、`PUT` 和 `DELETE` 终结点可能返回数据,也可能不返回数据。你的 `GET` 终结点可能返回一个字符串、一个 `List<T>`、一个 `IEnumerable`、一个自定义 `class` 或一个 `object`。另一方面,如果你的 API 抛出错误,它将返回一个 `object`,或者更糟的是一个 **HTML** `string`,说明错误的发生原因。所有这些响应之间的差异使得 API 难以使用,因为消费者需要知道每次返回的数据的 `type` 和结构。客户端代码和服务端代码都变得难以管理。
这就是我提出一个库的原因,该库为成功和错误结果提供了响应格式的一致性。
3 个简单步骤集成 VMD.RESTApiResponseWrapper.Core 库!
只需几个步骤,你就可以将 API 控制器转换为返回有意义的响应,而无需付出太多开发努力。你所要做的就是
步骤 1
从 NuGet 下载并安装库。
你可以按照上图所示通过 NPM 安装该包,或使用 NPM 控制台中的以下命令
PM> Install-Package VMD.RESTApiResponseWrapper.Core -Version 1.0.4
引用**注意**:截至本文撰写之时,最新版本是 `v1.0.4`,它针对的是 ASP.NET Core 2.1 版本。
第二步
在 `Startup.cs` 中声明下面的命名空间
using VMD.RESTApiResponseWrapper.Core.Extensions;
步骤 3
在 `Startup.cs` 的 `Configure()` 方法中注册 `UseAPIResponseWrapperMiddleware`,如下所示
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAPIResponseWrapperMiddleware();
app.UseMvc();
}
引用**注意**:请务必在 MVC 中间件“之前”注册它。
很简单!现在再次尝试构建并运行应用程序。根据我们的示例,响应将如下所示
你会注意到响应对象现在包含几个属性,如 `Version`、`StatusCode`、`Message`,并且实际数据存储在 `Result` 属性中。
这是我们尝试指向一个不存在的 URL 时的另一个示例输出
任何可能发生的意外错误都将被自动处理,而无需你做任何事情。你会注意到响应输出是动态的。动态的意思是,我们没有包含 `Result` 属性,而是使用 `ResponseException` 属性来存储错误和异常信息。
启用自定义响应
让我们继续修改现有的 API 终结点,为其他 **HTTP** 动词返回消息。
GET
// GET api/bands/7
[HttpGet("{id}")]
public APIResponse Get(int id) {
var data = SampleData.GetBands().Where(o => o.Id.Equals(id));
if (data.Any())
return new APIResponse(200, "Data found", data.First());
return null;
}
结果
POST
// POST api/bands
[HttpPost]
public APIResponse Post([FromBody]CreateBandDTO band) {
long bandID = 10; //10 is just an example here
//Call a method to add a new record to the entity
//bandID = YourRepo.Add(band);
return new APIResponse(201, $"New record with ID {bandID} successfully added.");
}
结果
PUT
// PUT api/bands/6
[HttpPut("{id}")]
public APIResponse Put(long id, [FromBody]UpdateBandDTO band) {
//Call a method to update the entity
//YourRepo.Update(band);
return new APIResponse(200, $"The record with ID {id} has been successfully updated.", id);
}
结果
删除
// DELETE api/bands/7
[HttpDelete("{id}")]
public APIResponse Delete(int id) {
//Call a method to delete an entity
//YourRepo.Delete(id);
return new APIResponse(200, $"The record with ID {id} has been successfully deleted.", id);
}
结果
你会注意到响应对象对于每个 HTTP 操作请求都是一致的。这无疑为你的 API 消费者提供了更好、更有意义的信息。
实现模型验证
模型验证允许你在 `class`/`property` 级别强制执行预定义的验证规则。通常使用这种验证技术来保持关注点清晰分离,因此你的验证代码编写、维护和测试起来会更简单。
正如你已经知道的,ASP.NET Core 2.1 引入了 `APIController` 属性,该属性会自动执行模型状态验证,用于 **400 Bad Request** 错误。当 `Controller` 被 `APIController` 属性修饰时,框架会自动注册一个 `ModelStateInvalidFilter`,该过滤器在 `OnActionExecuting` 事件上运行。它检查 `Model State` 的有效性并相应地返回响应。这是一个很棒的功能,但由于我们希望返回自定义响应对象而不是 **400 Bad Request** 错误,因此在这种情况下我们将禁用此功能。
要禁用自动模型状态验证,只需在 `Startup.cs` 文件的 `ConfigureServices()` 方法中添加以下代码
public void ConfigureServices(IServiceCollection services) {
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.AddMvc();
}
使用数据注解验证
数据注解是位于 `System.ComponentModel.DataAnnotations` 命名空间下的类属性,你可以使用它们来修饰类或属性以强制执行预定义的验证规则。要启用数据注解模型验证,我们需要注册以下命名空间
using System.ComponentModel.DataAnnotations;
让我们修改 `CreateBandDTO` 类,使用数据注解实现基本模型验证。下面是修改后的代码
public class CreateBandDTO
{
[Required]
[MaxLength(20)]
public string Name { get; set; }
[Required]
public string Genre { get; set; }
}
现在,当我们再次运行应用程序并发出 `POST` 请求时,如果 `Name` 属性留空,响应应该如下图所示
你会注意到包装器捕获了验证错误,并将其放入 `ValidationErrors` 属性中,以便于跟踪。有关模型验证的更多信息,请参阅 ASP.NET Core MVC 中的模型验证。
使用 Fluent Validation
如果出于某种原因,你不想使用 `System.ComponentModel.DataAnnotations` 来验证你的 `Models`,而是想使用 `FluentValidation`,你也可以这样做。让我们通过一个简单的例子来看看如何集成 `FluentValidation`。
首先,下载并安装如下图所示的 NuGet 包
你也可以通过运行以下命令在 NPM 控制台中安装它
Install-Package FluentValidation.AspNetCore
安装完成后,我们就可以开始使用 `FluentValidation` API 了。你应该在你声明 `Models` 的地方声明以下命名空间
using FluentValidation;
让我们将 `CreateBandDTO` 类恢复到其原始状态,并添加一个名为 `CreateBandValidator` 的新类。下面是修改后的代码
public class CreateBandDTO
{
public string Name { get; set; }
public string Genre { get; set; }
}
public class CreateBandValidator : AbstractValidator<CreateBandDTO>
{
public CreateBandValidator() {
RuleFor(o => o.Name).NotEmpty().MaximumLength(20);
RuleFor(o => o.Genre).NotEmpty();
}
}
你会注意到我们不再使用 `Required` 和 `MaxLength` 属性来强制对 `Model` 应用预定义的验证规则。相反,我们保持它们简单明了。我喜欢 `FluentValidation` 的地方在于,我们可以通过为每个我们想实现一些约束和其他验证规则的 `Model` 创建一个 `Validator` 类来分离验证逻辑。
让这一切生效的最后一步是按照下面的代码在 `Startup.cs` 文件中配置 `FluentValidation`
public void ConfigureServices(IServiceCollection services) {
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.AddMvc().AddFluentValidation();
services.AddTransient<IValidator<CreateBandDTO>, CreateBandValidator>();
}
更多信息,请参阅 此链接。
以下是模型验证失败时的响应示例屏幕截图
拥有信息丰富、一致且有意义的响应,应该有助于开发人员轻松地使用你的 API 并进行故障排除。
处理自定义错误和异常
你可以使用 `ApiException` 对象来返回错误和异常消息。例如,以下代码使用 `try-catch` 块来处理和模拟你的代码中可能发生的意外错误
[HttpGet]
public IEnumerable<Band> Get() {
try
{
//This is just an example to trigger an error
int number = Convert.ToInt32("100abc");
}
catch (NullReferenceException ex)
{
throw new ApiException(ex);
}
catch (FormatException ex)
{
throw new ApiException(ex);
}
catch (ArithmeticException ex)
{
throw new ApiException(ex);
}
catch (Exception ex)
{
throw new ApiException(ex);
}
return SampleData.GetBands();
}
上面的代码尝试将包含非数字值的 `string` 转换为 `integer` 类型,这将在运行时导致错误。响应输出将如下所示
你还可以使用 `ApiException` 来在自定义代码验证失败时抛出自己的消息。例如,如果你的代码验证用户凭据失败,你可以这样做
throw new ApiException($"The userid {id} does not exist.", 400);
启用 Swagger
Swagger 为你的 API 提供了高级文档,它允许开发人员引用你的 API 终结点的详细信息并在需要时进行测试。这尤其有用,当你的 API 是 `public` 并且你期望许多开发人员使用它时。
要为你的 API 应用程序启用 Swagger,请继续通过 NPM 下载并安装 `Swashbuckle` 包,如下图所示
在 `Startup.cs` 文件的 `ConfigureServices()` 方法中添加以下代码
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{ Title = "My REST API with VMD.RESTApiResponseWrapper.Core", Version = "v1" });
});
接下来,我们需要启用用于提供生成的 `JSON` 文档和 `Swagger` UI 的中间件。为此,请在 `Startup.cs` 文件的 `Configure()` 方法中添加以下代码
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
现在运行你的应用程序,并在 URL 后面加上“`/swagger`”,它应该会显示 `Swagger` UI,如下图所示
这是从 `Swagger` UI 发出的示例 `POST` 请求/响应
太棒了!
更多信息,请参阅 此链接。
摘要
在本文中,我们学习了如何将 `VMD.RESTApiResponseWrapper.Core` 包库集成到你的 ASP.NET Core 2.1 应用程序中。
欢迎随时尝试。评论和建议都受欢迎,所以请随时留言,我很乐意尽我所能回答任何疑问。