目录
介绍
开始
模型
模拟数据
创建一个API控制器
测试输出
只需3个简单步骤即可集成VMD.RESTApiResponseWrapper.Core库!
第1步
第2步
第三步
启用自定义响应
GET
POST
PUT
DELETE
实现模型验证
使用数据注释验证
使用Fluent 验证
处理自定义错误和异常
启用Swagger
总结
几个月前,我写了一篇关于如何为ASP.NET Core和Web API应用程序创建一致的对象响应包装的文章。我还为包装程序制作了两个版本的NuGet软件包,可以在下面找到它们:
令我惊讶的是,两个NuGet软件包现在都有数百个下载,并且我收到开发人员的一些评论和电子邮件,要求提供有关如何在项目中实际使用它们的教程。本文旨在回答我收到的那些常见问题,因此您可以开始。
在开始之前,我要感谢那些正在研究这个库并且可能已经尝试过它的人们。我非常感谢您对这个库的所有反馈,希望对您的项目有所帮助。
事不宜迟,让我们看看如何在应用程序中使用它。让我们继续并启动Visual Studio2017。通过浏览File > New Project创建一个新项目。在“新建项目”对话框的左窗格上,选择“已安装” >“ Visual C#” >“ Web” >“.NET Core” >“ ASP.NET Core Web应用程序”。将项目命名为任意名称,但对于本演示,我们将其命名为“MyWebApp.API”,如下图所示:
图1:新建项目对话框
单击确定,它将带您进入下一个屏幕,如下图所示:
图2:ASP.NET Core模板对话框
选择“Empty”,然后单击“确定”,以使Visual Studio为您生成默认项目文件和依赖项。这是默认生成的文件:
图3:默认生成的文件
让我们快速概述一下生成的每个文件。
如果您已经知道ASP.NET Core的核心重大更改,则可以跳过此部分,但是如果您不熟悉ASP.NET Core,那么我想重点介绍其中的一些更改。如果您以前使用过ASP.NET的早期版本,那么您会注意到新的项目结构完全不同。该项目现在包括以下文件:
让我们创建几个简单的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只是普通类,其中包含一些用于保存信息的属性。
现在,让我们重新开始工作。假设我们有下面的类定义了模拟数据:
public class SampleData
{
public static List GetBands()
{
return new List()
{
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方法。该方法定义了一个List类型的Band,并向集合中添加了一些默认记录。
让我们创建一个新的ASP.NET Core API Controller并定义一些可用于测试的终端。这是我的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 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
图4:HTTP Get请求
GET: api/v1/bands/{id}
图5:HTTP Get请求
在这一点上,API可以正常工作,但问题是它没有给开发人员有意义的响应。我们知道数据是响应中非常重要的一部分。但是,仅将数据作为JSON响应吐出并没有真正的帮助,尤其是当每个请求之间发生意外行为时。
快速回顾一下,如果你对API采用RESTful方式,那么你将被使用HTTP动词,如GET、POST、PUT和DELETE。根据您的方法/操作的设计方式,每个操作都可能返回不同的类型。你的POST、PUT和DELETE终端可能会返回一个数据,或者根本没有。您的GET终端可能返回一个 string、 List
这就是为什么我想出一个为成功和错误结果提供响应格式一致性的库。
仅需几个步骤,您就可以使API控制器返回有意义的响应,而无需您付出很多开发工作。您要做的就是:
从NuGet下载并安装该库。
图6: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;
在Startup.cs的Configure()方法中注册UseAPIResponseWrapperMiddleware,如下所示:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAPIResponseWrapperMiddleware();
app.UseMvc();
}
引用:
注意:确保在“MVC中间件”之前注册它。
很简单!现在尝试再次构建并运行该应用程序。根据我们的示例,响应如下所示:
图7:HTTP Get请求
您会注意到响应对象现在包含一些属性,如Version,StatusCode,Message和实际数据被存储在该Result属性。
当我们尝试指向不存在的URL时,这是另一个示例输出:
图8:HTTP Get请求
可能发生的任何意外错误将自动处理,无需您做任何事情。您注意到响应输出是动态的。动态是指没有包括Result属性,而是省略了ResponseException属性,而是将该属性用于错误和异常信息。
让我们继续修改现有的API终端,以返回其他HTTP动词的消息。
// 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;
}
结果
图9:HTTP Get请求
// 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.");
}
结果
图10:HTTP发布请求
// 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);
}
结果
图11:HTTP Put请求
// 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);
}
结果
图12:HTTP删除请求
请注意,响应对象对于每个HTTP操作请求都是一致的。这无疑为您的API使用者提供了更好,更有意义的信息。
通过模型验证,您可以在class/property级别强制执行预定义的验证规则。通常,您将使用这种验证技术来保持关注点的清晰分离,因此,验证代码的编写、维护和测试变得更加简单。
如您所知,ASP.NET Core 2.1 引入了APIController特性,该属性针对400 Bad Request错误执行自动模型状态验证。当Controller装饰有APIController特性,框架会自动注册一个运行于OnActionExecuting事件上的ModelStateInvalidFilter。这将检查Model State有效性并相应地返回响应。这是一个很棒的功能,但是由于我们要返回一个自定义响应对象而不是400 Bad Request错误,因此我们将禁用此功能。
要禁用自动模型状态验证,只需在Startup.cs文件中的ConfigureServices()方法中添加以下代码:
public void ConfigureServices(IServiceCollection services) {
services.Configure(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属性留空时,它应导致如下图所示:
图13:HTTP发布请求
请注意,包装器捕获了验证错误,并将其放入ValidationErrors属性中以便于跟踪。有关模型验证的更多信息,请参见ASP.NET Core MVC中的模型验证。
如果由于某种原因,您不想使用System.ComponentModel.DataAnnotations来验证您Models而想要使用FluentValidation,则也可以这样做。让我们看一个简单的示例,说明如何集成FluentValidation。
首先,下载并安装NuGet软件包,如下图所示:
图14: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
{
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(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.AddMvc().AddFluentValidation();
services.AddTransient, CreateBandValidator>();
}
有关更多信息,请参见此链接。
这是Model验证失败时响应的示例屏幕截图:
图15:HTTP发布请求
这样的信息量,一致和有意义的响应应有助于开发人员轻松使用您的API并解决问题。
您可以使用ApiException对象返回错误和异常消息。例如,以下代码使用try-catch块处理并模拟了您的代码可能发生的意外错误:
[HttpGet]
public IEnumerable 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,在运行时会导致错误的类型。响应输出将如下所示:
图16:HTTP Get请求
当自定义代码验证失败时,您还可以使用ApiException引发自己的消息。例如,如果您的代码验证了用户凭据但失败了,则可以执行以下操作:
throw new ApiException($"The userid {id} does not exist.", 400);
Swagger为您的API提供了高级文档,使开发人员可以参考您API终端的详细信息并在必要时进行测试。这非常有用,特别是当您的API是public并且希望许多开发人员使用它时。
要为您的API应用程序启用Swagger,请继续并通过NPM 下载并安装Swashbuckle软件包,如下图所示:
图17:NuGet软件包管理器
在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文档和SwaggerUI。为此,请在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");
});
现在运行您的应用程序,并将“/swagger ” 附加到URL上,它应显示SwaggerUI,如下图所示:
图18:Swagger UI
这是SwaggerUI 发出的示例POST请求/响应:
图19:Swagger UI HTTP Post请求
像查看更多信息, 可参见 此链接。
在本文中,我们学习了如何将VMD.RESTApiResponseWrapper.Core
程序包库合并到ASP.NET Core 2.1应用程序中。希望对您有所帮助。