C# 服务端篇之实现RestFul Service开发
一、RestFul简介
REST(Representational State Transfer 通常被翻译为“表述性状态传输”或者“表述性状态转移”)是RoyFielding提出的一个描述互联系统架构风格的名词。为什么称为REST?Web本质上由各种各样的资源组成,资源由URI 唯一标识。浏览器(或者任何其它类似于浏览器的应用程序)将展示出该资源的一种表现方式,或者一种表现状态。如果用户在该页面中定向到指向其它资源的链接,则将访问该资源,并表现出它的状态。这意味着客户端应用程序随着每个资源表现状态的不同而发生状态转移,也即所谓REST。
简单地来说REST它是一种使用URL来定位资源,使用HTTP请求描述操作的Web服务规范。REST主要包括以下几方面:
(1) REST是一组架构约束条件和原则,而满足这些约束条件和原则的应用程序就是RESTful。
(2)REST的目标是构建可扩展的Web Service,它是一种更简单的SOAP(Simple Object Access Protocol)协议以及以WSDL为基础的WebService的替代。
(3)REST采用的是HTTP协议并通过HTTP中的GET、POST、PUT、DELETE等动词收发数据。
(4) REST希望通过HTTP来完成对数据的元操作,即传统的CRUD(Create、Read、Update、Delete)分别对应GET、POST、PUT、DELETE,这样就统一了数据操作的接口,实现在不同平台上提供一套相同的服务。
(5) REST是一种面向服务的、分布式的API设计风格。
RESTful API的开发和使用,无非是客户端向服务器发请求(request),以及服务器对客户端请求的响应(response)。所以RESTful架构风格具有统一接口的特点,即:使用不同的http方法表达不同的行为:
- GET(SELECT):从服务器取出资源(一项或多项)
- POST(CREATE):在服务器新建一个资源
- PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)
- PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)
- DELETE(DELETE):从服务器删除资源
二、REST的约束条件和原则
REST本质上是Web服务的一种规范,一种思想。它主要包括以下特性:
1、资源(Resources)
在REST中资源是整个架构或者说整个网络处理的核心,那么什么又是资源呢?在我们传统的观念中,资源是指服务器上的一个文件,而在REST里资源则是指一个URL。URL即统一资源定位,而我们都知道通过URL可以访问互联网上的资源,所以在REST里这种对资源的指向性更加强烈,并且在这里资源的范畴会被无限放大而并非局限在文件本身,例如以下实例:
1 http://api.cnblogs.com/info/source 表示获取某人的成绩
2 http://api.cnblogs.com/info/friends 表示获取某人的好友列表
3 http://api.cnblogs.com/info/profile 表示获取某人的详细信息
由此我们注意到REST在形式上更加趋向API设计,而我们获取的资源则通过一定的形式进行统一而规范化的表达,因此REST实现了让不同的平台共享一套API这样的愿望,这是一件非常美好的事情,这个世界上的技术阵营举不胜数,而它们为了各自的利益建立一套封闭、臃肿的体系框架,很多时候当我们不需要这样的“全家桶”并且希望“跨平台”的时候,REST将会是一个不错的选择。
2、表现形式(Representational)
在REST中表现形式作为我们对资源请求的一个结果的呈现,通过对HTTP协议的学习我们已经知道,服务器会给客户端返回什么形式的信息,这一点取决于服务器响应报文中相关头部字段,而对REST来讲,它通常会采用XML或者JSON来告诉请求者请求的结果,因为JSON相比XML所含的冗余信息较少,所以目前更加倾向于或者说流行使用JSON作为请求结果的表现形式。
3、状态变化(State Transfer)
虽然我们一再强调HTTP协议是无状态,这主要体现在HTTP请求与请求、HTTP响应与响应的上下文无关性上。在REST中,我们所说状态变化更多是指HTTP中的GET、POST、PUT、DELETE等动词实现。具体来讲,看下面的简单示例:
1 GET http://url/info 表示获取全部的info
2 POST http://url/info 表示创建一个新的info
3 GET http://url/info/{id} 表示获取一个指定id的info
4 PUT http://url/info/{id} 表示更新一个指定id的info
5 DELETE http://url/info/{id} 表示删除一个指定id的info
除此之外,我们注意到REST基于HTTP协议,所以HTTP协议中的状态码对它来讲同样适用,例如最常用的200表示成功、500表示服务器内部错误、404表示无法找到请求资源、400表示请求错误等等。
三、如何构建RestFul风格的API
如何构建REST风格的API?我们可以通过以下实例说明
- URLRoot采用下面这样的结构:
1 http://example.com/api/v1/
2 http://api.example.com/v1/
- API版本可以放在URL或者HTTP的Header里
- URL使用名词而非动词:
1 http://example.com/api/v1/getProducts 这是一个糟糕的设计
2 GET http://example.com/api/v1/products 这是一个优雅的设计
- 保证方法时安全的不会对资源状态有所改变。例如:
GET http://example.com/api/v1/deleteProduct?id=1 这是一个危险的信号
- 资源的地址推荐使用嵌套结构
GET http://example.com/api/v1/friends/123456789/profile
- 使用正确的HTTP状态码表示访问状态
- 返回含义明确的结果(一般推荐JSON)
四、Restful 服务端
下面主要讲解如何用C#实现一个Rest 风格的web服务供外部调用,主要包括以下4点:
- 定义service的契约
- 定义URL Routing
- 实现service
- 为服务编写宿主程序
1、定义service的契约和URL Routing
先定义服务契约,这里我介绍最常见的两种方式,分别采用GET和POST方式访问,使用VS2015创建一个新的控制台工程,命名为RestFulService。如图所示:
并为该工程添加引用System.ServiceModel 和System.ServiceModel.Web
创建一个接口类文件,命名为IPersonInfoQuery。为了让.Net FrameWork识别这是一个service接口,我们需要给接口添加上ServiceContract特性,并且给接口定义的方法添加OperationContract特性。这里我介绍最常见的两种方式,分别采用GET和POST方式访问。我们可以看到,与普通WCF服务契约不同的是,需要额外用WebGet或者WebInvoke指定REST访问的方式。另外还要指定消息包装样式和消息格式,默认的消息请求和响应格式为XML,若选择JSON需要显式声明。 UriTemplate用来将方法映射到具体的Uri上,但如果不指定映射,将映射到默认的Uri。比如采用Get访问的GetUser方法,默认映射是:/GetSource?Name={Name}
我们定义两种方法,1、GetScore方法:通过GET请求传入name,返回对应的成绩;2、GetInfo方法:通过POST请求,传入Info对象,查找对应的User并返回给客户端,代码如下
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.ServiceModel;
5 using System.ServiceModel.Web;
6 using System.Text;
7 using System.Threading.Tasks;
8
9 namespace RestFulService
10 {
11 /// <summary>
12 /// 简单定义两种方法,1、GetScore方法:通过GET请求传入name,返回对应的成绩;2、GetInfo方法:通过POST请求,传入Info对象,查找对应的User并返回给客户端
13 /// </summary>
14 [ServiceContract(Name = "PersonInfoQueryServices")]
15 public interface IPersonInfoQuery
16 {
17 /// <summary>
18 /// 说明:GET请求
19 /// WebGet默认请求是GET方式
20 /// UriTemplate(URL Routing)的参数名name必须要方法的参数名必须一致(不区分大小写)
21 /// RequestFormat规定客户端必须是什么数据格式请求的(JSon或者XML),不设置默认为XML
22 /// ResponseFormat规定服务端返回给客户端是以是什么数据格返回的(JSon或者XML)
23 /// </summary>
24 /// <param name="name"></param>
25 /// <returns></returns>
26 [OperationContract]
27 [WebGet(UriTemplate = "PersonInfoQuery/{name}", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
28 User GetScore(string name);
29
30 /// <summary>
31 /// 说明:POS请求
32 /// WebInvoke请求方式有POST、PUT、DELETE等,所以需要明确指定Method是哪种请求的,这里我们设置POST请求。
33 /// 注意:POST情况下,UriTemplate(URL Routing)一般是没有参数(和上面GET的UriTemplate不一样,因为POST参数都通过消息体传送)
34 /// RequestFormat规定客户端必须是什么数据格式请求的(JSon或者XML),不设置默认为XML
35 /// ResponseFormat规定服务端返回给客户端是以是什么数据格返回的(JSon或者XML)
36 /// </summary>
37 /// <param name="info"></param>
38 /// <returns></returns>
39 [OperationContract]
40 [WebInvoke(Method = "POST", UriTemplate = "PersonInfoQuery/Info", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
41 User GetInfo(Info info);
42 }
43 }
定义对象User
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using System.Runtime.Serialization;
7
8 namespace RestFulService
9 {
10 [DataContract]
11 public class User
12 {
13 [DataMember]
14 public int ID { get; set; }
15
16 [DataMember]
17 public string Name { get; set; }
18
19 [DataMember]
20 public int Age { get; set; }
21
22 [DataMember]
23 public int Score { get; set; }
24 }
25 }
定义对象Info
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Runtime.Serialization;
5 using System.Text;
6 using System.Threading.Tasks;
7
8 namespace RestFulService
9 {
10 [DataContract]
11 public class Info
12 {
13 [DataMember]
14 public int ID { get; set; }
15
16 [DataMember]
17 public string Name { get; set; }
18 }
19 }
说明:
UriTemplate就是我们之前提到的URL Routing(可以单独创建一个Routing进行管理)
WebGet默认请求是GET方式。
WebInvoke请求方式有POST、PUT、DELETE等,所以需要明确指定Method是哪种请求的。
UriTemplate(URL Routing)的参数名name必须要方法的参数名必须一致(不区分大小写)
POST情况下,UriTemplate(URL Routing)一般是没有参数(和GET的UriTemplate不一样,因为POST参数都通过消息体传送)
RequestFormat规定客户端必须是什么数据格式请求的(JSon或者XML),不设置默认为XML
ResponseFormat规定服务端返回给客户端是以是什么数据格返回的(JSon或者XML)
2、实现service
上面我们定义了接口,那么我们需要创建一个服务去实现这个接口的方法,我们创建一个类名为PersonInfoQueryServices,我们需要设置一些ServiceBehavior特征属性。代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.ServiceModel;
5 using System.ServiceModel.Activation;
6 using System.Text;
7 using System.Threading.Tasks;
8
9 namespace RestFulService
10 {
11 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, IncludeExceptionDetailInFaults = true)]
12 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
13 public class PersonInfoQueryServices : IPersonInfoQuery
14 {
15 private List<User> UserList = new List<User>();
16 /// <summary>
17 /// 生成一些测试数据
18 /// </summary>
19 public PersonInfoQueryServices()
20 {
21 UserList.Add(new User() { ID = 1, Name = "张三", Age = 18, Score = 98 });
22 UserList.Add(new User() { ID = 2, Name = "李四", Age = 20, Score = 80 });
23 UserList.Add(new User() { ID = 3, Name = "王二麻子", Age = 25, Score = 59 });
24 }
25 /// <summary>
26 /// 实现GetScore方法,返回某人的成绩
27 /// </summary>
28 /// <param name="name"></param>
29 /// <returns></returns>
30 public User GetScore(string name)
31 {
32 return UserList.FirstOrDefault(n => n.Name == name);
33 }
34 /// <summary>
35 /// 实现GetInfo方法,返回某人的User信息
36 /// </summary>
37 /// <param name="info"></param>
38 /// <returns></returns>
39 public User GetInfo(Info info)
40 {
41 return UserList.FirstOrDefault(n => n.ID == info.ID && n.Name == info.Name);
42 }
43
44 }
45 }
3、服务编写宿主程序
上面我们定义了Service接口,并实现了Service方法,现在我们需要将编写宿主程序,以便能够使客户端通过GET或者POST方法进行请求。代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.ServiceModel;
5 using System.ServiceModel.Description;
6 using System.ServiceModel.Web;
7 using System.Text;
8 using System.Threading.Tasks;
9
10 namespace RestFulService
11 {
12 class Program
13 {
14 static void Main(string[] args)
15 {
16 try
17 {
18 PersonInfoQueryServices service = new PersonInfoQueryServices();
19 Uri baseAddress = new Uri("http://127.0.0.1:7788/");
20 using (ServiceHost _serviceHost = new ServiceHost(service, baseAddress))//或者:WebServiceHost _serviceHost = new WebServiceHost(typeof(PersonInfoQueryServices), baseAddress);
21 {
22 //如果不设置MaxBufferSize,当传输的数据特别大的时候,很容易出现“提示:413 Request Entity Too Large”错误信息,最大设置为20M
23 WebHttpBinding binding = new WebHttpBinding
24 {
25 TransferMode = TransferMode.Buffered,
26 MaxBufferSize = 2147483647,
27 MaxReceivedMessageSize = 2147483647,
28 MaxBufferPoolSize = 2147483647,
29 ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max,
30 Security = { Mode = WebHttpSecurityMode.None }
31 };
32 _serviceHost.AddServiceEndpoint(typeof(IPersonInfoQuery), binding, baseAddress);
33 _serviceHost.Opened += delegate
34 {
35 Console.WriteLine("Web服务已开启...");
36 };
37 _serviceHost.Open();
38 Console.WriteLine("输入任意键关闭程序!");
39 Console.ReadKey();
40 _serviceHost.Close();
41 }
42 }
43 catch (Exception)
44 {
45 Console.WriteLine("Web服务开启失败:{0}\r\n{1}", ex.Message, ex.StackTrace);
46 }
47 }
48 }
49
如果上面的ServiceHost服务启动了,但是提示服务却找不到相对应的接口,可以使用下面服务方式(简单明了~)
1 //使用新的方式
2 WebServiceHost _serviceHost = new WebServiceHost(service, baseAddress);
3 _serviceHost.Open();
4 Console.WriteLine("Web服务已开启...");
5 Console.WriteLine("输入任意键关闭程序!");
6 Console.ReadKey();
7 _serviceHost.Close();
开启效果如下:
最后,我们通过浏览器简单的测试一下GET请求的效果是怎样的,如图所示:
PS:更多Restful的C#规范,可以参考微软的文档--->WebGet和WebInvoke传送门 API传送门
五、Restful 客户端
上面我们已经简单的成功实现了Restful Service,下面我们简单的讲解一下,如何实现Restful 客户端来校验上面的Restful 服务器的正确性。PS:如何定义高效便捷的Restful Client帮助类,我们将在下篇文章进行讲解,本文就先介绍一种简单有效的Restful Client做一个Demo测试。
我们定义一个Restful 客户端的帮助类RestClient,用于和Restful 服务端交互,如图所示:
、
帮助类代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Net;
6 using System.Text;
7 using System.Threading.Tasks;
8
9 namespace RestFulClient
10 {
11 /// <summary>
12 /// 请求类型
13 /// </summary>
14 public enum EnumHttpVerb
15 {
16 GET,
17 POST,
18 PUT,
19 DELETE
20 }
21
22 public class RestClient
23 {
24 #region 属性
25 /// <summary>
26 /// 端点路径
27 /// </summary>
28 public string EndPoint { get; set; }
29
30 /// <summary>
31 /// 请求方式
32 /// </summary>
33 public EnumHttpVerb Method { get; set; }
34
35 /// <summary>
36 /// 文本类型(1、application/json 2、txt/html)
37 /// </summary>
38 public string ContentType { get; set; }
39
40 /// <summary>
41 /// 请求的数据(一般为JSon格式)
42 /// </summary>
43 public string PostData { get; set; }
44 #endregion
45
46 #region 初始化
47 public RestClient()
48 {
49 EndPoint = "";
50 Method = EnumHttpVerb.GET;
51 ContentType = "application/json";
52 PostData = "";
53 }
54
55 public RestClient(string endpoint)
56 {
57 EndPoint = endpoint;
58 Method = EnumHttpVerb.GET;
59 ContentType = "application/json";
60 PostData = "";
61 }
62
63 public RestClient(string endpoint, EnumHttpVerb method)
64 {
65 EndPoint = endpoint;
66 Method = method;
67 ContentType = "application/json";
68 PostData = "";
69 }
70
71 public RestClient(string endpoint, EnumHttpVerb method, string postData)
72 {
73 EndPoint = endpoint;
74 Method = method;
75 ContentType = "application/json";
76 PostData = postData;
77 }
78 #endregion
79
80 #region 方法
81 /// <summary>
82 /// http请求(不带参数请求)
83 /// </summary>
84 /// <returns></returns>
85 public string HttpRequest()
86 {
87 return HttpRequest("");
88 }
89
90 /// <summary>