飞行荷兰人航空公司服务控制器层实现指南
1. 引言
在之前的工作中,我们已经完成了数据库访问层、仓库层和服务层的开发。然而,目前的服务还无法被外部系统使用,因为缺少能够接收 HTTP 请求并进行相应处理的控制器。本文将详细介绍如何实现控制器层,特别是
FlightController
,并探讨相关的技术细节。
2. 控制器在 Repository/Service 模式中的位置
控制器通常是 Repository/Service 模式中面向公众的最顶层。在这个模式中,控制器是与外部系统交互的主要方式,除了通过数据库访问层与数据库进行通信外,它是服务与外部世界连接的桥梁。外部系统可以是网站、微服务或桌面应用程序等。
一个典型的 HTTP 响应通常包含以下三个关键部分:
-
HTTP 状态码
:如 200(OK)、404(Not Found)或 500(Internal Server Error),用于表示请求处理的结果状态。
-
头部信息
:通常包含返回数据的类型和跨域资源共享(CORS)指令等。在大多数情况下,ASP.NET 可以自动处理这些信息。
-
响应体
:在适当的情况下,返回给客户端的数据通常以 JSON 字符串的形式存在,通常与 200(OK)状态码一起返回。某些 HTTP 状态码不允许返回数据,如 201 状态码表示 “No Content”。
3. 确定需要实现的控制器
我们需要根据与 FlyTomorrow 签订的合同中规定的端点来确定需要实现的控制器。这些端点包括:
-
GET /Flight
-
GET /Flight/{FlightNumber}
-
POST /Booking/{FlightNumber}
通过分析这些端点中出现的实体,我们可以确定需要实现的控制器:
| 端点 | 所需控制器 |
| — | — |
|
GET /Flight
|
FlightController
|
|
GET /Flight/{FlightNumber}
|
FlightController
|
|
POST /Booking/{FlightNumber}
|
BookingController
|
由于合同中没有要求为机场和客户实体设置控制器端点,根据 “You ain’t gonna need it”(YAGNI)原则,我们不需要为这些实体实现控制器。
4. 实现 FlightController
首先,我们需要创建
FlightController
和
FlightControllerTests
两个类。为了使
FlightController
能够返回 HTTP 数据并设置路由,它需要继承自
Controller
类。
public class FlightController : Controller
{
// 后续代码将添加在这里
}
为了使外部系统能够访问我们的端点,我们需要实现以下三个部分:
-
IActionResult
接口
- 中间件中的依赖注入
- 端点路由
4.1 使用 IActionResult 接口返回 HTTP 响应(GetFlights 方法)
在处理 HTTP 响应时,由于没有原始数据类型可以直接存储 HTTP 响应的所有信息,我们可以使用 ASP.NET 的
IActionResult
接口。这个接口是
ActionResult
和
ContentResult
等类的起点,在实际使用中,我们可以将具体类的选择交给 CLR 处理,这体现了多态性和 “面向接口编程” 的原则。
下面是一个简单的示例,展示了如何使用
IActionResult
接口返回一个 HTTP 状态码为 200 和一个字符串 “Hello, World!” 的响应:
public IActionResult GetFlights()
{
return StatusCode(200, "Hello, World!");
}
为了避免使用硬编码的状态码,我们可以使用
HttpStatusCode
枚举:
public IActionResult GetFlights()
{
return StatusCode((int) HttpStatusCode.OK, "Hello, World!");
}
然而,我们的需求是返回数据库中所有航班的信息。为此,我们需要调用服务层的方法,并处理可能出现的异常。以下是更新后的
GetFlights
方法:
public async Task<IActionResult> GetFlights()
{
try
{
Queue<FlightView> flights = new Queue<FlightView>();
await foreach (FlightView flight in _service.GetFlights())
{
flights.Enqueue(flight);
}
return StatusCode((int)HttpStatusCode.OK, flights);
}
catch(FlightNotFoundException)
{
return StatusCode((int) HttpStatusCode.NotFound, "No flights were found in the database");
}
catch (Exception)
{
return StatusCode((int) HttpStatusCode.InternalServerError, "An error occurred");
}
}
4.2 使用中间件将依赖项注入到控制器中
在之前的开发中,我们使用依赖注入来延迟实例化依赖项。现在,我们需要在中间件中实际实例化这些依赖项。中间件是用于处理 HTTP 请求的代码,可以将其看作是一系列按顺序执行的组件。
在 ASP.NET 服务中,中间件代码通常位于
Startup
类中。以下是一个简单的
Startup
类示例:
class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{ endpoints.MapControllers(); });
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
}
在
ConfigureServices
方法中,我们可以添加需要注入的依赖项。有三种类型的注入方式:
-
单例(Singleton)
:在服务的整个生命周期内只有一个实例。
-
作用域(Scoped)
:在每个请求的生命周期内只有一个实例。
-
瞬态(Transient)
:每次使用依赖项时都会创建一个新的实例。
由于瞬态依赖项是最常见且易于使用的,我们将使用这种方式为
FlightController
添加
FlightService
依赖项:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient(typeof(FlightService), typeof(FlightService));
services.AddTransient(typeof(FlightRepository), typeof(FlightRepository));
services.AddTransient(typeof(AirportRepository), typeof(AirportRepository));
services.AddTransient(typeof(FlyingDutchmanAirlinesContext), typeof(FlyingDutchmanAirlinesContext));
}
现在,我们可以将注入的依赖项添加到
FlightController
中,并调用
FlightService
:
public class FlightController : Controller
{
private readonly FlightService _service;
public FlightController(FlightService service)
{
_service = service;
}
// 其他方法...
}
4.3 单元测试
为了确保
GetFlights
方法的正确性,我们需要编写单元测试。以下是一个完整的单元测试示例:
[TestClass]
public class FlightControllerTests
{
[TestMethod]
public async Task GetFlights_Success()
{
Mock<FlightService> service = new Mock<FlightService>();
List<FlightView> returnFlightViews = new List<FlightView>(2)
{
new FlightView("1932", ("Groningen", "GRQ"), ("Phoenix", "PHX")),
new FlightView("841", ("New York City", "JFK"), ("London", "LHR"))
};
service.Setup(s => s.GetFlights()).Returns(FlightViewAsyncGenerator(returnFlightViews));
FlightController controller = new FlightController(service.Object);
ObjectResult response = await controller.GetFlights() as ObjectResult;
Assert.IsNotNull(response);
Assert.AreEqual((int)HttpStatusCode.OK, response.StatusCode);
Queue<FlightView> content = response.Value as Queue<FlightView>;
Assert.IsNotNull(content);
Assert.IsTrue(returnFlightViews.All(flight => content.Contains(flight)));
}
private async IAsyncEnumerable<FlightView> FlightViewAsyncGenerator(IEnumerable<FlightView> views)
{
foreach (FlightView flightView in views)
{
yield return flightView;
}
}
}
此外,我们还需要编写处理异常情况的单元测试,如
FlightNotFoundException
和
ArgumentException
:
[TestMethod]
public async Task GetFlights_Failure_FlightNotFoundException_404()
{
Mock<FlightService> service = new Mock<FlightService>();
service.Setup(s => s.GetFlights()).Throws(new FlightNotFoundException());
FlightController controller = new FlightController(service.Object);
ObjectResult response = await controller.GetFlights() as ObjectResult;
Assert.IsNotNull(response);
Assert.AreEqual((int)HttpStatusCode.NotFound, response.StatusCode);
Assert.AreEqual("No flights were found in the database", response.Value);
}
[TestMethod]
public async Task GetFlights_Failure_ArgumentException_500()
{
Mock<FlightService> service = new Mock<FlightService>();
service.Setup(s => s.GetFlights()).Throws(new ArgumentException());
FlightController controller = new FlightController(service.Object);
ObjectResult response = await controller.GetFlights() as ObjectResult;
Assert.IsNotNull(response);
Assert.AreEqual((int)HttpStatusCode.InternalServerError, response.StatusCode);
Assert.AreEqual("An error occurred", response.Value);
}
通过以上步骤,我们完成了
FlightController
的实现和单元测试。但目前还无法从外部系统调用这个端点,后续将介绍如何设置路由。
总结
本文详细介绍了如何实现飞行荷兰人航空公司服务的控制器层,特别是
FlightController
。我们首先探讨了控制器在 Repository/Service 模式中的位置,然后确定了需要实现的控制器,接着详细介绍了
FlightController
的实现过程,包括使用
IActionResult
接口返回 HTTP 响应、使用中间件进行依赖注入以及编写单元测试。通过这些步骤,我们为服务的可用性迈出了重要的一步。后续将继续完成
BookingController
的实现,并进行路由设置和验收测试。
飞行荷兰人航空公司服务控制器层实现指南
5. 路由端点
现在我们已经完成了
FlightController
的实现和单元测试,但还不能从外部系统调用端点,接下来需要设置路由。
在 ASP.NET 中,路由是将 HTTP 请求映射到控制器和操作方法的过程。我们可以使用
HttpAttribute
方法属性来声明 HTTP 路由。
以下是如何为
GetFlights
方法设置路由的示例:
[HttpGet("Flight")]
public async Task<IActionResult> GetFlights()
{
// 方法实现
}
在这个示例中,
[HttpGet("Flight")]
属性指定了该方法将处理
GET /Flight
请求。
对于带有参数的请求,例如
GET /Flight/{FlightNumber}
,可以这样设置路由:
[HttpGet("Flight/{FlightNumber}")]
public async Task<IActionResult> GetFlightByNumber([FromRoute] string FlightNumber)
{
// 方法实现
}
这里,
[HttpGet("Flight/{FlightNumber}")]
指定了处理
GET /Flight/{FlightNumber}
请求,
[FromRoute]
属性用于从路由中获取
FlightNumber
参数。
路由的执行流程可以用以下 mermaid 流程图表示:
graph LR
A[HTTP 请求] --> B[路由匹配]
B -->|匹配成功| C[调用控制器方法]
B -->|匹配失败| D[返回 404 错误]
C --> E[处理请求]
E --> F[返回 HTTP 响应]
6. 总结与展望
通过前面的步骤,我们已经完成了
FlightController
的完整实现,包括返回 HTTP 响应、依赖注入、单元测试和路由设置。下面总结一下实现
FlightController
的关键步骤:
1.
创建控制器类
:继承自
Controller
类。
2.
使用
IActionResult
接口
:返回 HTTP 响应。
3.
依赖注入
:在中间件中配置依赖项。
4.
编写单元测试
:确保方法的正确性。
5.
设置路由
:将 HTTP 请求映射到控制器方法。
接下来,我们将实现
BookingController
以处理
POST /Booking/{FlightNumber}
请求。以下是实现
BookingController
的大致步骤列表:
1.
确定控制器
:根据端点确定需要实现
BookingController
。
2.
创建类和测试类
:创建
BookingController
和
BookingControllerTests
类。
3.
依赖注入
:注入
BookingService
等依赖项。
4.
实现方法
:实现处理
POST /Booking/{FlightNumber}
请求的方法。
5.
编写单元测试
:确保方法的正确性。
6.
设置路由
:为方法设置合适的路由。
最后,我们将进行验收测试,使用 Swagger 来验证服务是否符合 API 规范。通过这些步骤,我们将完成飞行荷兰人航空公司服务的完整实现,使其能够与外部系统进行交互。
| 步骤 | 描述 |
|---|---|
| 1 |
创建
BookingController
和
BookingControllerTests
类
|
| 2 |
在中间件中注入
BookingService
等依赖项
|
| 3 |
实现处理
POST /Booking/{FlightNumber}
请求的方法
|
| 4 | 编写单元测试确保方法正确性 |
| 5 | 为方法设置路由 |
| 6 | 使用 Swagger 进行验收测试 |
通过本文的介绍,希望你对控制器层的实现有了更深入的理解,能够顺利完成飞行荷兰人航空公司服务的开发。
超级会员免费看
1670

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



