31、实现 BookingController 类与数据反序列化

实现 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 时有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值