ASP.NET Web API 2系列(二):灵活多样的路由配置

1. 导言

路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的在于利用注册的路由对请求的URL进行解析以肯定目标HTTPController和Action的名称,以及与目标Action方法某个参数进行绑定的路由变量。html

WebService和WCF的协议都是soap协议,数据的序列化和反序列化都是soap的格式。而WebAPI是基于Http协议,请求和返回格式结果默认是 json格式,所以,比WCF更简单、更通用,比 WebService 更节省流量、更简洁。 Web API是在.NET Framework上构建RESTful应用程序的理想平台,为了更清楚弄懂WebAPI的路由配置,咱们首先要了解HTTP协议和RESTful架构风格。web

2. HTTP协议

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为普遍的一种网络传输协议,全部的WWW文件都必须遵照这个标准。咱们在这里紧列举和本文关系密切的HTTP请求方法和HTTP状态码。数据库

2.1 HTTP请求方法

根据HTTP标准,HTTP请求可使用多种请求方法。json

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法,HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法,以下表所示:api

序号 方法 描述
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的设计初衷,又经过实际的例子(文中的代码及结果均经过了测试),让咱们掌握了路由配置的各类方法。文中如有不足之处,还望海涵,博文写做不易但愿多多支持,后续会更新更多内容,感兴趣的朋友能够加关注,欢迎留言交流!

相关文章
相关标签/搜索