ASP.NET Core Web API各种技术及选择

在ASPNET Core 中进行 Web API开发的时候,为达成同一个目标有多种技术可以选择,这些不同的技术选择可以满足各种不同的需求。但是在项目开发中,如果不对技术选择进行统,就会出现不同技术混用的情况。这不仅会导致项目代码编程风格不统一,影响代码的可读性和可维护性,而且会延长新入职员工培训的周期。

一、控制器父类用哪个

之前的中的控制器类都是继承自 ControllerBase,而ASPNET Core MVC 项目中的控制器类默认继承自 Controller。 Controller 类继承自 CntrollerBase, Controller 类在ControllerBase 的基础上增加了和视图相关的方法,而 Web API的接口不涉及视图,因此除非读者需要在同一个控制器中同时提供 Web API和MVC 的功能,否则 Web API 的控制器类继承自ControllerBase 即可。

其实,控制器类不显式设置父类,照样是可以正常工作的,我们可以要求依赖注入容器为我们注入IHtpContextAccessor 对象,然后我们通过HtpContextAccessor 获取 HttpContext 对象。不过这样的控制器类就无法访问 ControllerBase 中的 Response、Request、HttpContext 等成员,更无法通过 BadRequest、Ok 等方法来快速设置响应报文。因此,一般情况下,我们编写的 WebAPI控制器类继承自 ControllerBase 即可。

二、操作方法的异步、返回值、状态码

ASP.NET Core Web API中的操作方法既可以是同步方法也可以是异步方法。因为异步方法能提升系统的并发吞吐量,所以如果一个操作方法调用的代码有异步调用,那么作者建议扣操作方法声明为异步。因为操作方法一般不会被我们的代码直接调用,所以异步方法的名字一般不需要以Async结尾。

ASP.NET Core Web API 中的操作方法的返回值如果是通数据类型,那么返回值就会默认被序列化为JSON格式的响应报文体返回。ASPNET Core Web API 中的作方法的返回值同祥支持 IActionResult 类型,但是 AtionResult 类型中不包含返回值的类型信息,Swagger无法从操作方法的声明中推断出返回数据的类型。因此 ASPNET Core 中提供了一个泛型的ActionResult<T>类型,它的用法和 IActionResult 类似,如下代码所示。

[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult<Person> GetPerson(int id)
{
if (id <=0)
return BadRequest("id 必须是正数”);
else if (id == 1)
return newPerson(1,“曾露瑶”,18)
else if (id == 2)
return new Person(2,"Zack",8);
else
return NotFound("人员不存在");
}
]

ActionResul<T>通过泛型约束返回数据的类型,这样 Swagger 可以推断出返回值的类型而且如果返回的数据类型错误的话,我们也能在编译时发现问题。ActionResult<T>通过隐式换重载来完成 BadRequest、NotFound 等方法返回的非泛型的 ActionResut 到 ActionResulkT的转换,它也会完成 Person 这样的具体值到ActionResult<T>的隐式转换。因此在操作方法中我们不需要显式地创建ActionResult<T>类型的对象,我们可以直接执行 return new Person0.return NotFound0等。

在项目开发中,对于处理失败的请求,我们一般要统一响应报文体的格式,以便于在客户端更方便地进行错误处理。比如我们可以声明一个 ErrorInfo 类表示错误的详细信息,如下代码所示。

public record ErrorInfo(int Code, string? Message);

ErrorInfo 类的 Code 代表错误码,这个错误码是业务错误码,具体取值范围及代表的含义由接口来定义。一般建议留一个码段作为整个项目的通用错误码,比如1代表“请求的资源找不到”2 代表“当前用户没有操作权限”3 代表“当前用户和当前分公司不匹配”然后我们再把其他码段供不同操作方法自定义错误码,比如在“新增用户”这个操作中1001 代表“用户名已经存在”、1002 代表“允许创建的用户数量已超限”,而在“把商品入购物车”这个操作中,1001 代表“商品库存不足”、1002 代表“商品不能在当前地区购买”。

ErrorInfo 类的 Message代表错误相关的信息,可以用来对错误给出更易懂的错误说明,如“允许创建的用户数量已超限”“用户名已经存在”。这样客户端开发人员遇到接口错误的候不需要查询文档就能知道接口出现了什么问题。作者不建议直接把 Message 的值显示到客户端,客户端要根据状态码来给用户更友好的报错信息。

我们下面把 ActionResult 和 Errorlnfo 一起使用,从而改造 GetPerson 方法,如下代码所示。

[HttpGet("{id}")]
public ActionResult<Person> GetPerson(int id)
{
if (id<=0)
return BadRequest(new ErrorInfo(l,"id 必须是正数"));
else if (id == 1)
return new Person(1,"曾露瑶”,18);
else if (id == 2)
return new Person(2,“Zack",8);
else
return NotFound(new ErrorInfo(2,"人员不存在"));
}

如果我们向/Persons/GetPerson/1 这个路径发送请求,服务器端就会返回200响应。

如果我们向/Persons/GetPerson/8这个路径发送请求,服务器端就会返回404响应。

这样的设计就既能兼顾Restful 风格,又能方便客户端开发人员排查问题并进行错误处理。

值得注意的是,ActionResult<T>也可以作为异步方法的返回值,格式如下:

public async Task<ActionResult<Person>>GetCountryCode(string countryName)

综上所述,在ASPNET Core Web API中,我们应该使用ActionResult<T>来作为操作方法的返回值;如果操作方法可以声明为异步方法,那么我们就用 async Task<ActionResul<T>>XXX0这样的声明方式;如果服务器端能正确地处理请求,操作方法就直接返回数据,如果操作方法在执行过程中遇到了业务错误,则服务器端创建 ErrorInfo 类型的对象来描述错误的详细信息,并且服务器端通过NotFound、BadRequest 等方法来设置响应状态码及响应报文体

三、操作方法的参数从哪里来

我们在给服务器端传递参数的时候,有 URL、QueryString、请求报文体 3种方式。我们来看一下如何在ASPNET Core Web API 中读取这3 种参值,并介绍其他读取参数值的方式。

我们可以在[HttpGet]、[HttpPost]等 Attribute 中使用占位符(比如schoolName))来捕捉路径中的内容,从而供操作方法的参数匹配时使用。假如请求的路径为/Students/GetAll/schoolMIT/class/A001,而GetAl1方法上添加了[HttpGet("school/schoolName}/class/classNo)”],那么ASPNET Core 就会把 schoolName=MIT 和 classNo=A001 提取出来;如果GetAll方法的参数中有同名的参数,那么这个参数就会被自动赋值。如果捕捉的占位符的名字和参数名一致那么我们就不需要为参数添加[FromRoute]; 如果占位符的名字和参数名不一致,我们就需要为参数添加[FromRoute],并且通过[FromRoute]的 Name 属性来设置匹配的占位符的名字,比如一个名字为 classNum的参数要想获得占位符中{classNo)的值,那么我们就要为 classNum参数添加[FromRoute(Name="classNo")],如下代码所示。

[HttpGet("school/{schoolName}/class/{classNo}")]
public ActionResult<Student[]> GetAll(string schoolName,
[FromRoute(Name ="classNo")]string classNum)

对于通过QueryString传递的参数,我们使用[FromQuery]来获取值。如果操作方法的参数的名字和我们要获取的QueryString 的名字一致,我们只要为参数添加[FromQuery]即可;如操作方法的参数的名字和要获取的QueryString 的名字不一致,我们就要为设定[FromQuery]的Name属性指定和QueryString 中一样的名字。比如一个分页获取数据的URL的QueryStrin为pageNum=8&pSize=10 ,而我们想用名字为 pageSize 的参数获取pSize 这个变量,只要用如下代码所示的方式声明即可。

public ActionResult<Student[]> GetAl([FromQuery]string pageNum,
[FromQuery(Name ="pSize")]int pageSize)

当然,我们可以把这些方式混用,比如可以如下代码所示声明一个操作方法

[HttpGet("schoolschoolName)/class/(classNo)")]
public ActionResult<Student[]> GetAll(string schoolName,
[FromRoute(Name ="classNo")]string classNum,
[FromQuery]string pageNum,[FromQuery(Name = pSize")]int pageSize)

这个操作方法就可以处理/Students/GetAll/school/MIT/class/A001?pageNum-8&pSize=10这样的请求。

上面讲解的都是从 URL中取值的方法。POST、PUT 等请求除了可以通过 URL传递据,也可以通过报文体传递数据。HTTP 请求中通过 Content-Type可以支持不同格式的报文体其中 application/x-www-form-urlencoded、multipar/form-data 属于传统 MVC 开发模式中用得较多的格式,而在 WebAPI的开发模式下,JSON 式的请求报文体是主流,因此这里主要解如何在WebAPI中获取请求报文体中的JSON格式报文体。

比如,要开发一个“新增学生”的接口,我们就可以要求客户端提交如下格式的JSON请求报文体:{"name":"yzk","age":"18"}。

接下来,我们再声明一个 Student 类,这个类中定义了 NameAge 两个属性,然后我们按照如下代码所示声明操作方法。

[HttpPost]
public ActionResult AddNew(Student s)

这样,客户端只要向/Students/AddNew 提交POST 请求即可。

当然,我们也可以把从 URL 获取参数、从请求报文体获取数据等混合使用。比如我们在[HtpPost]这个 Attribute 中把路径设置为[HttpPost("classId/fclassId;")],这样它就能从 URI中获取参数值了,如下代码所示。

[HttpPost("classId/{classId)")]
public ActionResult<long> AddNew(long classId,NewStudentModel s)

客户端只要向/Students/AddNew/classId/8这个路径提交请求报文体为{"name":"yzk”,age""18”的POST请求即可,这样AddNew 操作方法中就能通过classId得到提交的班级主键8的值,并且Student 类的s 变量的值就是报文体代表的数据。

必须注意的是,我们发送上面的请求时一定要设定请求报文头中的 Content-Type 为application/JSON,而且请求报文体必须是合法的JSON 格式,否则服务器会报错。比如,我们使用Postman 发送一个Content-Type为text/plain 的请求,服务器端就会出现错误。

除了前面讲到的从 URL 中获取匹配值的[FromRoute]、从 QueryString 中获取值的[FromQuery]之外,ASPNET Core 中还提供了从 Content-Type为 multipart/form-data 的请求中获取数据的[FromForm]以及从请求报文头中获取值的[FromHeader]等 Attribute。

综上所述,对于 GET、DELETE 等请求,我们尽量从URL或者 QueryString 中获取数据;对于PUT、DELETE 等请求,我们尽量通过JSON 格式的报文体获取数据,当然我们一定要设定请求报文头中的 Content-Type 的值为 application/json。

猜你喜欢

转载自blog.csdn.net/xxxcAxx/article/details/128553949