1. 导言
路由系统是请求消息进入 ASP.NET web API 消息处理管道的第一道屏障, 其根本目的在于利用注册的路由对请求的 URL 进行解析以确定目标 HTTPController 和 Action 的名称, 以及与目标 Action 方法某个参数进行绑定的路由变量.
WebService 和 WCF 的协议都是 SOAP 协议, 数据的序列化和反序列化都是 SOAP 的格式. 而 WebAPI 是基于 Http 协议, 请求和返回格式结果默认是 JSON 格式, 因此, 比 WCF 更简单, 更通用, 比 WebService 更节省流量, 更简洁. Web API 是在. NET Framework 上构建 RESTful 应用程序的理想平台, 为了更清楚弄懂 WebAPI 的路由配置, 我们首先要了解 HTTP 协议和 RESTful 架构风格.
2. HTTP 协议
HTTP 协议 (HyperText Transfer Protocol, 超文本传输协议) 是因特网上应用最为广泛的一种网络传输协议, 所有的 WWW 文件都必须遵守这个标准. 我们在这里紧列举和本文关系密切的 HTTP 请求方法和 HTTP 状态码.
2.1 HTTP 请求方法
根据 HTTP 标准, HTTP 请求可以使用多种请求方法.
HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法, HTTP1.1 新增了五种请求方法: OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法, 如下表所示:
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和 / 或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
2.2 HTTP 状态码
当浏览者访问一个网页时, 浏览者的浏览器会向网页所在服务器发出请求. 当浏览器接收并显示网页前, 此网页所在的服务器会返回一个包含 HTTP 状态码 (HTTP Status Code) 的信息头 (server header) 用以响应浏览器的请求.
常见的 HTTP 状态码如下表:
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
200 | OK | 请求成功。一般用于 GET 与 POST 请求 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置 "您所请求的资源无法找到" 的个性页面 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
3. RESTful 介绍
在介绍 RESTful 之前, 我们需了解什么 REST, 他有那些特征, 以及 REST 成熟度模型.
3.1 REST 介绍
REST 是 Representational State Transfer 的缩写, 翻译为表象化状态转变或表述性状态转变, 是 Roy Fielding 博士在 2000 年他的博士论文中提出来的一种软件架构风格, 它包含了一个分布式超文本系统中对于组件, 连接器和数据的约束. REST 是作为互联网自身架构的抽象而出现的, 其关键在于所定义的架构上的各种约束. 只有满足这些约束, 才能称之为符合 REST 架构风格.
3.2 REST 系统的特征
RESR 系统包括 6 个特征, 如下所示:
(1)客户端 - 服务器结构(Client-Server)
通过一个统一的接口来分开客户端和服务器, 使得两者可以独立开发和演化. 客户端的实现可以简化, 而服务器可以更容易的满足可伸缩性的要求;
(2)无状态(Stateless)
在不同的客户端请求之间, 服务器并不保存客户端相关的上下文状态信息. 任何客户端发出的每个请求都包含了服务器处理该请求所需的全部信息;
(3)可缓存(Cachable)
客户端可以缓存服务器返回的响应结果. 服务器可以定义响应结果的缓存设置.
(4)分层的系统(Layered System)
在分层的系统中, 可能有中间服务器来处理安全策略和缓存等相关问题, 以提高系统的可伸缩性. 客户端并不需要了解中间的这些层次的细节.
(5)按需代码(Code-On-Demand, 可选)
服务器可以通过传输可执行代码的方式来扩展或自定义客户端的行为. 这是一个可选的约束.
(6)统一接口(Uniform Interface)
该约束是 REST 服务的基础, 是客户端和服务器之间的桥梁. 该约束又包含下面 4 个子约束.
资源标识符: 每个资源都有各自的标识符. 客户端在请求时需要指定该标识符. 在 REST 服务中, 该标识符通常是 URI. 客户端所获取的是资源的表达(representation), 通常使用 xml 或 JSON 格式.
通过资源的表达来操纵资源: 客户端根据所得到的资源的表达中包含的信息来了解如何操纵资源, 比如对资源进行修改或删除.
自描述的消息: 每条消息都包含足够的信息来描述如何处理该消息.
超媒体作为应用状态的引擎(HATEOAS): 客户端通过服务器提供的超媒体内容中动态提供的动作来进行状态转换.
3.3 REST 成熟度模型
Richardson 提出的 REST 成熟度模型. 该模型把 REST 服务按照成熟度划分成 4 个层次, 我们常用到的就是 Level1 和 Level2, 如下图所:
具体说明如下:
Level 0:Web 服务只是使用 HTTP 作为传输方式, 实际上只是远程方法调用 (RPC) 的一种具体形式. SOAP 和 xml-RPC 都属于此类.
Level 1:Web 服务引入了资源的概念. 每个资源有对应的标识符和表达.
Level 2:Web 服务使用不同的 HTTP 方法来进行不同的操作, 并且使用 HTTP 状态码来表示不同的结果. 如 HTTP GET 方法来获取资源, HTTP DELETE 方法来删除资源.
Level 3:Web 服务使用 HATEOAS. 在资源的表达中包含了链接信息. 客户端可以根据链接来发现可以执行的动作. 例如, 客户端通过订单资源中包含的链接取消某一订单, GET 请求被发送去获取该订单. HATEOAS 的优点包括无需在客户端代码中写入硬链接的 URL. 此外, 由于资源信息中包含可允许操作的链接, 客户端无需猜测在资源的当前状态下执行何种操作.
3.4 RESTful
RESTful 是一种常见的 REST 应用, 是遵循 REST 风格的 Web 服务, REST 式的 Web 服务是一种 ROA(面向资源的架构).
RESTful 资源操作如下表:
http 方法 | 资源操作 | 幂等 | 安全 |
---|---|---|---|
GET | SELECT | 是 | 是 |
POST | INSERT | 否 | 否 |
PUT | UPDATE | 是 | 否 |
DELETE | DELETE | 是 | 否 |
注: 幂等性: 对同一 REST 接口的多次访问, 得到的资源状态是相同的; 安全性: 对该 REST 接口访问, 不会使服务器端资源的状态发生改变.
RESTful URL 请求格式与传统请求格式比较如下表所示:
传统 URL 请求格式 | RESTFul 请求格式 | 描述 |
---|---|---|
http:/localhost/user/query/1 GET | http:/localhost/user/1 GET | 根据用户 id 查询用户数据 |
http:/localhost/user/save POST | http:/localhost/user POST | 新增用户 |
http:/localhost/user/update POST | http:/localhost/user PUT | 修改用户信息 |
http:/localhost/user/delete GET/POST | http:/localhost/user DELETE | 删除用户信息 |
4.Web API 路由
路由的目的是用于解析请求的 URL 来确定 Controller 和 Action.Web API 默认路由是通过 http 的方法 (get/post/put/delete) 去匹配对应的 action, 也就是说 webapi 的默认路由并不需要指定 action 的名称, 当然, WebApi 也支持 MVC 里面的路由机制, 但 RestFul 风格的服务要求请求的 url 里面不能包含 action, 所以, 在 WebApi 里面是并不提倡使用 MVC 路由机制的. 下边通过例子介绍 Web API 路由原理以及使用.
4.1 新建空的 Web API 应用程序
上篇博客介绍了手动搭建基本框架, 这次我们通过 VS2017 提供的模板, 新建空的 WebAPI 应用程序 MyWebAPI2, 构建过程中仅勾选 Web API, 如下图所示.
创建完成, 应用程序的目录结构如下图所示.
4.2 默认路由
App_Start 文件夹下 WebApiConfig.cs 类用于注册 Web API 路由, 默认路由代码如下:
- public static void Register(HttpConfiguration config)
- {
- // 默认路由
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new { id = RouteParameter.Optional }
- );
- }
在 Models 文件夹增加一个 Student 类, 代码如下:
public class Student { public string Id { get; set; } public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } public string Dept { get; set; } }
在 Controllers 文件夹中添加一个 Web API 控制类(v2.1), 命名为 StudentController, 并修改相关代码, 如下所示:
public class StudentController : ApiController { private static List<Student> studentList = new List<Student>() { new Student() {Id = "001", Name = "张三", Sex = "男", Age = 19, Dept = "软件学院"}, new Student() {Id = "002", Name = "李丽", Sex = "女", Age = 19, Dept = "资环学院"} }; [HttpGet] public IEnumerable<Student> Get() { return studentList; } [HttpGet] public Student Get(string id) { List<Student> tempList = studentList.Where(p => p.Id == id).ToList(); return tempList.Count==1?tempList.First():null ; } [HttpPost] public bool Post([FromBody]Student student) { if (student == null) return false; if (studentList.Where(p=>p.Id==student.Id).ToList().Count>0) return false; studentList.Add(student); return true; } [HttpPut] public bool Put(string id, [FromBody]Student student) { if (student == null) return false; List<Student> tempList = studentList.Where(p => p.Id == id).ToList(); if (tempList.Count == 0) return false; Student originStudent = tempList[0]; originStudent.Name = student.Name; originStudent.Sex = student.Sex; originStudent.Age = student.Age; originStudent.Dept = student.Dept; return true; } [HttpDelete] public bool Delete(string id) { List<Student> tempList = studentList.Where(p => p.Id == id).ToList(); if (tempList.Count == 0) return false; studentList.Remove(tempList[0]); return true; } }
在实际项目中, 增删改查这些操作都是和数据库打交道的, 这里为了演示具体实现, 用一个静态数组存储数据. 运行程序, 我们采用 Fiddler 工具进行测试调用.
测试一: 调用 Get()方法
功能说明: 通过路由解析 StudentController 中的 Get()Action, 获取所有学生列表.
URL:http://localhost:52317/API/student;HTTP 方法: GET. 如下图所示:
测试结果: HTTP 状态码为 200, 获取的 JSON 数据如下所示:
测试二: 调用 Get(string id)方法
功能说明: 通过路由解析 StudentController 中的 Get(string id)Action, 根据学号获取某个学生信息.
URL:http://localhost:52317/API/student/002;HTTP 方法: GET.
测试结果: HTTP 状态码为 200, 获取的 JSON 数据如下所示:
测试三: 调用 Post([FromBody]Student student)方法
功能说明: 通过路由解析 StudentController 中的 Post([FromBody]Student student)Action, 插入一条数据, 其中参数前添加 [FromBody] 是指该参数不是从 URL 中获取, 而是在 RequestBody 中获取.
URL:http://localhost:52317/API/student;HTTP 方法: POST. 在 RequestBody 输入 JSON 格式的数据, 如下图所示:
测试结果: HTTP 状态码为 200, 返回的数据 TRUE. 重复测试一, 获取所有学生列表如下图所示.
测试四: 调用 Put(string id, [FromBody]Student student)方法
功能说明: 通过路由解析 StudentController 中的 Put(string id, [FromBody]Student student)Action, 实现通过学号更新信息, 其中学号 id 是在 Url 中获取.
URL:http://localhost:52317/API/student/002;HTTP 方法: PUT.
测试结果: HTTP 状态码为 200, 返回的数据 TRUE.
测试五: 调用 Delete(string id)方法
功能说明: 通过路由解析 StudentController 中的 Delete(string id)Action, 删除某学生信息, 其中学号 id 是在 Url 中获取.
URL:http://localhost:52317/API/student/002;HTTP 方法: DELETE.
测试结果: HTTP 状态码为 200, 返回的数据 TRUE.
总结: 通过上边测试, 我们可以到 URL 中没有用到 Action, 默认路由就是通过参数和 HTTP 方法匹配 Controller 中的 Action.
4.3 自定义路由
在实际项目中, 如果 http 请求的类型相同, 且请求参数相同(如 Get), 这个时候按照默认路由肯定会出问题, 具体出现什么问题, 我们在这做个测试:
在 StudentController 中添加一个 Action, 用于查询某个学院的所有同学, 代码如下:
[HttpGet] public IEnumerable<Student> GetByDept(string id) { List<Student> tempList = studentList.Where(p => p.Dept == id).ToList(); return tempList; }
这时, 运行程序, 输入 URL:http://localhost:52317/API/student / 资环学院; HTTP 方法: GET. 测试结果发现返回的结果 null, 跟踪测试代码发现该 URL 调用的 Get(string id)这个 Action.
那么问题就来了, 怎么才能调用这个 Action 呢? 其中有两种方法一种是自定义路由, 就像 MVC 那样在模板中增加 Action, 另一种为通过 Web API 路由特性实现.
在 App_Start 文件夹下 WebApiConfig.cs 类中添加 ActionAPI 路由模板, 代码如下:
public static void Register(HttpConfiguration config) { // 默认路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 自定义路由 config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); }
运行程序, 输入 URL:http://localhost:52317/API/student/GetByDept / 资环学院; HTTP 方法: GET. 运行结果如下:
注: 若出现中文乱码, 解决途径请参考 Fiddler 抓包中文乱码问题.
由此可知通过自定义路由可以解决该问题, 但是不符合 RESTful 风格, 所有不提倡使用该方法.
4.4 特性路由
默认路由解决不了的访问, 可以通过 Web API 的路由特性解决.
启动路由特性: 在 App_Start 文件夹下 WebApiConfig.cs 类中启用特性路由, 代码如下:
public static void Register(HttpConfiguration config) { // 启用 Web API 特性路由 config.MapHttpAttributeRoutes(); // 默认路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 自定义路由 config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); }
修改 StudentController 中 GetByDept 代码如下:
[Route("student/GetByDept/{dept}")] [HttpGet] public IEnumerable<Student> GetByDept(string dept) { List<Student> tempList = studentList.Where(p => p.Dept == dept).ToList(); return tempList; }
运行程序, 输入 URL:http://localhost:52317/student/GetByDept / 软件学院; HTTP 方法: GET. 运行结果如下:
通过此例, 可以看到 Web API 特性路由非常灵活方便, 具体使用要结合实际项目灵活运用.
5. 总结
本文在开始部分介绍了 HTTP 协议及 RESTful 架构风格, 让我们更深入的了解 Web API 的设计初衷, 又通过实际的例子(文中的代码及结果均通过了测试), 让我们掌握了路由配置的各种方法. 文中若有不足之处, 还望海涵, 博文写作不易希望多多支持, 后续会更新更多内容, 感兴趣的朋友可以加关注, 欢迎留言交流!
来源: https://www.cnblogs.com/aizai846/p/11027633.html