实现 BookingController 类与数据反序列化
在之前的工作中,我们已经完成了数据库访问、存储库、服务层以及
FlightController
类的实现。接下来,我们要完成最后一个控制器
BookingController
类的实现,之后还会针对
FlyTomorrow
的 OpenAPI 规范进行手动测试和验收测试,并且可以选择设置 Swagger 中间件来动态生成 OpenAPI 规范,这对验收测试很有帮助。
1. 实现 BookingController 类
在之前的学习中,我们了解了如何实现
FlightController
并添加了一些 HTTP GET 方法,如
GET /Flight
和
GET Flight/{FlightNumber}
。现在,我们要基于这些知识来实现
BookingController
。
BookingController
是
FlyTomorrow
与
Flying Dutchman Airlines
创建预订的入口和网关,完成这个控制器的实现后,我们就能为公司带来实际的收入。
1.1 端点分析
我们需要实现三个端点:
1.
GET /Flight
2.
GET /Flight/{FlightNumber}
3.
POST /Booking/{FlightNumber}
前两个端点在之前的章节已经实现,现在只剩下第三个端点需要实现。前两个端点在
FlightController
类中,而第三个端点需要我们实现
BookingController
类。前两个端点不需要处理 JSON 主体,而
POST
请求需要接受发送到端点的数据。
在实现具体的端点处理逻辑之前,我们先创建
BookingController
类的骨架:
[Route("{controller}")]
public class BookingController : Controller {}
2. 数据反序列化介绍
对于
POST /Booking/{flightNumber}
端点,它结合了两种向控制器提供数据的方式:路径参数
flightNumber
和包含两个字符串(名字和姓氏)的 JSON 主体。例如:
{
"firstName": "Frank",
"lastName": "Turner"
}
但用户可能会错误填写字段。我们假设一个简单的验证规则:
firstName
和
lastName
都必须有值。
由于 HTTP 请求的主体是以序列化形式(这里是 JSON 字符串)传输的,我们需要将其反序列化为可理解的数据结构。反序列化是将数据流(通常是字节或 JSON 字符串)转换为内存或磁盘上可消费的数据结构的过程,反之则是序列化。
为了反序列化数据,我们使用两个概念:
- 一个具有适当结构的数据结构(通常是类)来反序列化数据。
- 使用
[FromBody]
参数属性进行模型绑定(也称为数据绑定)。
我们创建一个
BookingData
类来存储反序列化后的数据,将其放在
ControllerLayer/JsonData
文件夹下的
BookingData.cs
文件中:
public class BookingData
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
为了对属性进行验证,我们可以添加一些逻辑:
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
if (IsValidName(value))
_firstName = value;
}
}
private string _lastName;
public string LastName
{
get => _lastName;
set
{
if (IsValidName(value))
_lastName = value;
}
}
private bool IsValidName(string name) => !string.IsNullOrEmpty(name);
同时,我们还可以为
BookingData
类编写一些单元测试来验证其功能:
[TestClass]
public class BookingDataTests
{
[TestMethod]
public void BookingData_ValidData()
{
BookingData bookingData = new BookingData { FirstName = "Marina", LastName = "Michaels" };
Assert.AreEqual("Marina", bookingData.FirstName);
Assert.AreEqual("Michaels", bookingData.LastName);
}
[TestMethod]
[DataRow("Mike", null)]
[DataRow(null, "Morand")]
public void BookingData_InvalidData_NullPointers(string firstName, string lastName)
{
BookingData bookingData = new BookingData { FirstName = firstName, LastName = lastName };
Assert.AreEqual(firstName, bookingData.FirstName);
Assert.AreEqual(lastName, bookingData.LastName);
}
[TestMethod]
[DataRow("Eleonor", "")]
[DataRow("", "Wilke")]
public void BookingData_InvalidData_EmptyStrings(string firstName, string lastName)
{
BookingData bookingData = new BookingData { FirstName = firstName, LastName = lastName };
Assert.AreEqual(firstName, bookingData.FirstName ?? "");
Assert.AreEqual(lastName, bookingData.LastName ?? "");
}
}
3. 使用 [FromBody] 属性反序列化传入的 HTTP 数据
在前面我们创建了
BookingData
类来存储反序列化后的信息。在 HTTP POST 请求的上下文中,有效的 JSON 数据需要被解析(反序列化)到
BookingData
类的
firstName
和
lastName
属性中。如果请求不完整(
BookingClass
的属性为 null),我们将返回 HTTP 状态码 500。
要将数据反序列化到
BookingData
类,我们可以使用 ASP.NET 的
[FromBody]
属性。在
BookingController
类中创建一个新方法
CreateBooking
并添加
[HttpPost]
属性:
[HttpPost]
public async Task<IActionResult> CreateBooking([FromBody] BookingData body)
通过添加
[FromBody]
属性,我们就可以使用 HTTP 请求中的数据了。默认情况下,ASP.NET 使用
DataContractSerializer
类进行序列化,适用于 JSON 数据。如果要使用 XML,需要在
global.asax.cs
文件中添加以下两行代码:
XmlFormatter xmlFormatter = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xmlFormatter.UseXmlSerializer = true;
4. 使用自定义模型绑定器和方法属性进行模型绑定
有些人不喜欢代码中的“魔法”,我们可以实现自己的模型绑定器。自定义模型绑定器
BookingModelBinder
包含了如何将给定数据绑定到类的信息,虽然实现起来有些繁琐,但可以提供更好的控制和可扩展性。
BookingModelBinder
类需要实现
IModelBinder
接口,其实现主要有四个部分:
1. 验证
bindingContext
输入参数。
2. 将 HTTP 主体读取为可解析的格式。
3. 将 HTTP 主体数据绑定到
BookingData
类的属性。
4. 返回绑定的模型。
以下是
BookingModelBinder
类的完整实现:
class BookingModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentException();
}
ReadResult result = await bindingContext.HttpContext.Request.BodyReader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
string bodyJson = Encoding.UTF8.GetString(buffer.FirstSpan);
JObject bodyJsonObject = JObject.Parse(bodyJson);
BookingData boundData = new BookingData
{
FirstName = (string)bodyJsonObject["FirstName"],
LastName = (string)bodyJsonObject["LastName"]
};
bindingContext.Result = ModelBindingResult.Success(boundData);
}
}
在端点方法中,由于不能使用
[FromBody]
属性,我们可以添加
[ModelBinder(typeof([custom binder]))]
属性:
[HttpPost]
public async Task<IActionResult> CreateBooking([ModelBinder(typeof(BookingModelBinder))] BookingData body)
通过以上步骤,我们完成了
BookingController
类的实现,以及对
POST
请求数据的反序列化处理,并且介绍了两种不同的数据绑定方式。
下面是
BookingModelBinder
类处理数据的流程图:
graph TD;
A[开始] --> B[验证 bindingContext];
B --> C{bindingContext 是否为空};
C -- 是 --> D[抛出异常];
C -- 否 --> E[读取 HTTP 主体数据];
E --> F[获取数据缓冲区];
F --> G[将缓冲区数据转换为 JSON 字符串];
G --> H[解析 JSON 字符串到 JObject];
H --> I[创建 BookingData 实例并赋值];
I --> J[设置绑定结果];
J --> K[结束];
D --> K;
通过这个流程图,我们可以更清晰地看到
BookingModelBinder
类处理数据的整个过程。在实际开发中,我们可以根据具体需求选择使用
[FromBody]
属性或自定义模型绑定器来完成数据绑定。
实现 BookingController 类与数据反序列化
5. 两种数据绑定方式对比
为了更清晰地了解
[FromBody]
属性和自定义模型绑定器的差异,我们可以通过以下表格进行对比:
| 对比项 | [FromBody] 属性 | 自定义模型绑定器 |
| — | — | — |
| 实现难度 | 简单,只需添加属性到参数 | 复杂,需要实现
IModelBinder
接口 |
| 代码量 | 少,代码简洁 | 多,需要编写较多逻辑 |
| 可定制性 | 低,依赖默认序列化器 | 高,可以自定义数据绑定逻辑 |
| 可读性 | 高,代码直观 | 低,逻辑复杂时不易理解 |
从表格中可以看出,
[FromBody]
属性适合快速开发和简单场景,而自定义模型绑定器适合对数据绑定有特殊需求的复杂场景。
6. 手动测试和验收测试
完成
BookingController
类的实现后,我们需要进行手动测试和验收测试,以确保其符合
FlyTomorrow
的 OpenAPI 规范。
6.1 手动测试
手动测试可以使用工具如 Postman 或 cURL 来发送请求。以下是使用 cURL 测试
POST /Booking/{flightNumber}
端点的示例:
curl -X POST -H "Content-Type: application/json" -d '{"firstName": "John", "lastName": "Doe"}' http://localhost:5000/Booking/123
在这个示例中,我们向
http://localhost:5000/Booking/123
端点发送了一个
POST
请求,请求体包含 JSON 数据。根据响应的 HTTP 状态码和返回内容,我们可以判断请求是否成功。
6.2 验收测试
验收测试可以使用 Swagger 中间件来动态生成 OpenAPI 规范。Swagger 可以帮助我们自动生成 API 文档,并提供交互式的测试界面。
要设置 Swagger 中间件,我们需要在项目中添加相关的 NuGet 包,并在
Startup.cs
文件中进行配置。以下是一个简单的配置示例:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Swashbuckle.AspNetCore.SwaggerUI;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.DocExpansion(DocExpansion.None);
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
配置完成后,启动应用程序,访问
http://localhost:5000/swagger
即可看到 Swagger 生成的 API 文档和测试界面。
7. 总结与建议
通过本文的介绍,我们完成了
BookingController
类的实现,掌握了数据反序列化的方法,包括使用
[FromBody]
属性和自定义模型绑定器。同时,我们还了解了手动测试和验收测试的方法,以及如何使用 Swagger 中间件来辅助测试。
以下是一些建议:
- 在开发过程中,根据项目的复杂度和需求选择合适的数据绑定方式。如果需求简单,优先使用
[FromBody]
属性;如果需要更多的控制和定制,使用自定义模型绑定器。
- 编写单元测试和集成测试,确保代码的正确性和稳定性。
- 使用 Swagger 中间件生成 API 文档,方便团队成员和外部开发者了解和使用 API。
下面是整个开发流程的流程图:
graph LR;
A[分析端点需求] --> B[创建 BookingController 类骨架];
B --> C[实现数据反序列化];
C --> D{选择数据绑定方式};
D -- [FromBody] 属性 --> E[使用 [FromBody] 实现方法];
D -- 自定义模型绑定器 --> F[实现 BookingModelBinder 类];
E --> G[编写单元测试];
F --> G;
G --> H[手动测试];
H --> I[验收测试];
I --> J[使用 Swagger 生成文档];
通过这个流程图,我们可以看到整个开发过程的步骤和顺序。希望这些内容对你在开发 Web API 时有所帮助。
超级会员免费看

被折叠的 条评论
为什么被折叠?



