【ASP.NET Core】深入理解Controller的工作机制
系列文章目录
链接: 【ASP.NET Core】REST与RESTful详解,从理论到实现
文章目录
- 系列文章目录
- 前言
- 一、Controller的泛用职责
- 二、Controller的特性
- 2.1 ApiController
- 2.2 属性路由Route
- 2.3 Action方法HTTP特性
- 2.4 Action方法的异步实现
- 三、Action方法参数
- 3.1 URL占位符
- 3.2 QueryString
- 3.3 请求报文体
- 四、Controller里的结果响应
- 4.1 IActionResult
- 4.2 ActionResult
- 总结
前言
Web开发中,我们时刻都在和HTTP请求打交道,构建路由,解析请求,响应结果。在ASP.NET Core MVC/Web API中,有一个核心的组件,名为Controller。作为处理请求、协调模型和视图/响应的“指挥中心”。本文将深入探讨Controller的角色、功能。
一、Controller的泛用职责
Controller本质上是一个包含了Action方法的类,负责处理客户端HTTP请求,然后调用指定业务逻辑,最后再返回响应给客户端。可以把Controller简单的理解成请求的入口点(实际上Controller是连接着请求管道的末尾),通过路由映射,再匹配到到具体的Action调用逻辑。
我们可以把Controller的职责总结为三部分:
- 处理客户端HTTP请求,映射路由,解析参数
- 匹配具体Action,调用业务逻辑方法
- 生成响应,返回客户端。
请求管道与控制器
实际上,作为一个完整的ASP.NET Core请求管道,严格来说Controller不属于请求管道本身,而Controller是这条管道最后抵达的“终点”。
Controller的目的是执行业务逻辑,它需要通过路由中间件和端点中间件与请求管道集成。在这个请求管道里,还有那些认证授权,记录日志等通用的中间件,抑或是限流等自定义中间件。事实上请求管道也就是由多个中间件组成的一条处理链
ASP.NET Core WebAPI里的Controller继承自ControllerBase;ASP.NET Core MVC里的Controller继承自Controller,其Controller内部也是继承自ControllerBase;:
- ControllerBase是所有控制器的抽象基类,仅包含处理 HTTP 请求的核心功能,如状态码返回、模型验证、路由绑定等。
- Controller继承自ControllerBase,并额外集成了视图引擎相关功能,用于处理需要返回视图。
二、Controller的特性
2.1 ApiController
ApiController是一个用于控制器的特性,主要是当传入的参数不符合数据模型验证,会自动返回400 Bad Request响应。
2.2 属性路由Route
控制器里的方法上通过标注Route自定义特性,来表示这个Controller包含的Action方法如何匹配。路由Route特性里可路由模板自定义路径。
[Route("api/[controller]")]GET方法 https://localhost:7027/api/[controller名称]
像类似"api/[controller]",模板里的[controller]是占位符。通过控制器的名称结合HTTP属性匹配Action方法。
https://localhost:7027/api/[controller名称]/[action名称]
也可也在模板里留下自定义action路径,通过控制器的名称和方法名称来匹配路由。
2.3 Action方法HTTP特性
- [HttpGet]
- [HttpGet(“{id}”)]
- [HttpPost]
- [HttpPut(“{id}”)]
- [HttpPatch(“{id}”)]
- [HttpDelete(“{id}”)]
诸如以上几种方式,通过在Action前指定HTTP特性,便可通过不同的请求方式获取不同的Action。
2.4 Action方法的异步实现
使用async修饰Action,有返回值的通过Task包裹。Action的名称可不用Async尾缀修饰。
[HttpGet("{id}")]
public async Task<ActionResult<Student>> GetStudent(int id)
三、Action方法参数
3.1 URL占位符
在[HttpGet]、[HttpPost]等HTTP特性修饰的Action方法中使用占位符,来解析URL路径中的内容。
比如
[HttpGet("{id}")]
public ActionResult<Student> GetStudent(int id)
{var student = _students.FirstOrDefault(e => e.Id == id);if (student == null)return NotFound("");return Ok(student);
}
这个[HttpGet(“{id}”)]修饰的Action方法,就会解析URL里的匹配{id}占位符的内容,并且赋与GetStudent方法里的(int id)参数。
但是如果占位符的名字和方法里的参数名称不一致,可以用[FromRoute(Name=“名字”)]进行手动映射
[HttpGet("{idStr}")]
public ActionResult<Student> GetStudent([FromRoute(Name = "idStr")]string id)
3.2 QueryString
QueryString在请求路径是以?xxx=yyy&…的形式存在。如果Querystring中的名字和Action方法参数对应,那么框架会自动匹配。
[HttpGet]
public ActionResult<Student> GetStudent(int id)
{var student = _students.FirstOrDefault(e => e.Id == id);if (student == null)return NotFound("");return Ok(student);
}
但是如果占位符的名字和方法里的参数名称不一致,可以用[FromRoute(Name=“名字”)]进行手动映射
[HttpGet]
public ActionResult<Student> GetStudent([FromQuery(Name ="idStr")]string id)
3.3 请求报文体
一般HTTP的POST/PUT/PATCH请求中会用到报文体,一般通过[FromBody]绑定JSON数据(JSON格式的请求体是主流,客户端设定请求头中的Content-Type为application/json)。也有少部分通过表单提交数据[FromForm]。
存在URL占位符,QueryString和报文体嵌套的情况下,厘清Action方法里的数据参数对应的来源,用相应的诸如[FromRoute] [FromQuery] [FromBody]等修饰参数
四、Controller里的结果响应
在 ASP.NET Core WebAPI中,默认响应格式是JSON。当return Ok(data),框架会自动将对象序列化为JSON并设置响应头 Content-Type: application/json。
在返回格式上我们一般常用IActionResult 和 ActionResult。这样的好处是可以方便的调用框架里的各种封装好的方法,如各类状态码的返回。
4.1 IActionResult
IActionResult的使用在ASP.NET Core MVC中较为常见。这里的控制器的主要职责是返回视图,而非直接返回数据。
常见的诸如:
返回模型
return View(model);
重定向
return RedirectToAction("Login", "Auth");
下载excel
return File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
这些都不是直接返回数据。MVC控制器的核心是视图渲染,以及混合这多种类型(视图、重定向、文件等)的返回。
4.2 ActionResult
Web API 通常返回单一类型的数据,并且考虑到格式,我们更加倾向于使用ActionResult的泛型ActionResult< T>返回数据对象,自动处理序列化和状态码。
[HttpGet("{id}")]
public ActionResult<Student> GetStudent(int id)
{var student = _students.FirstOrDefault(e => e.Id == id);if (student == null)return NotFound("");return student;
}
如上代码直接返回student,会被框架自动处理序列化和状态码。
这点我们观察泛型ActionResult< T>内部实现,它包含两个隐式的操作符转换。
- 方法签名返回ActionResult< TValue>,但实际返回 TValue
- 方法签名返回ActionResult< TValue>,但实际返回ActionResult result。
这两者都会触发new ActionResult< TValue>的执行。这样的好处是可以直接返回一个数据对象,或者一个ActionResult的方法。
如果没有隐式操作符
// 必须手动调用Ok()包裹示例
return Ok(classInstance);
// 必须手动包装
return new ActionResult<ClassName>(new NotFoundResult());
隐式操作符转换
/// <summary>
/// Implicitly converts the specified <paramref name="value"/> to an <see cref="ActionResult{TValue}"/>.
/// </summary>
/// <param name="value">The value to convert.</param>
public static implicit operator ActionResult<TValue>(TValue value)
{return new ActionResult<TValue>(value);
}/// <summary>
/// Implicitly converts the specified <paramref name="result"/> to an <see cref="ActionResult{TValue}"/>.
/// </summary>
/// <param name="result">The <see cref="ActionResult"/>.</param>
public static implicit operator ActionResult<TValue>(ActionResult result)
{return new ActionResult<TValue>(result);
}
总结
本文详解ASP.NET Core中Controller的职责、基类差异、特性、Action 参数绑定及响应方式,展现其处理HTTP 请求的核心作用。