C# 服務端篇之實現RestFul Service開發(簡單實用)

1、RestFul簡介

  REST(Representational State Transfer 一般被翻譯爲「表述性狀態傳輸」或者「表述性狀態轉移」)是RoyFielding提出的一個描述互聯繫統架構風格的名詞。爲何稱爲REST?Web本質上由各類各樣的資源組成,資源由URI 惟一標識。瀏覽器(或者任何其它相似於瀏覽器的應用程序)將展現出該資源的一種表現方式,或者一種表現狀態。若是用戶在該頁面中定向到指向其它資源的連接,則將訪問該資源,並表現出它的狀態。這意味着客戶端應用程序隨着每一個資源表現狀態的不一樣而發生狀態轉移,也即所謂REST。html

  簡單地來講REST它是一種使用URL來定位資源,使用HTTP請求描述操做的Web服務規範。REST主要包括如下幾方面:git

  (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設計風格。github

  RESTful API的開發和使用,無非是客戶端向服務器發請求(request),以及服務器對客戶端請求的響應(response)。因此RESTful架構風格具備統一接口的特色,即:使用不一樣的http方法表達不一樣的行爲:web

  • GET(SELECT):從服務器取出資源(一項或多項)
  • POST(CREATE):在服務器新建一個資源
  • PUT(UPDATE):在服務器更新資源(客戶端提供完整資源數據)
  • PATCH(UPDATE):在服務器更新資源(客戶端提供須要修改的資源數據)
  • DELETE(DELETE):從服務器刪除資源

2、REST的約束條件和原則

  REST本質上是Web服務的一種規範,一種思想。它主要包括如下特性:json

一、資源(Resources)

  在REST中資源是整個架構或者說整個網絡處理的核心,那麼什麼又是資源呢?在咱們傳統的觀念中,資源是指服務器上的一個文件,而在REST裏資源則是指一個URL。URL即統一資源定位,而咱們都知道經過URL能夠訪問互聯網上的資源,因此在REST裏這種對資源的指向性更增強烈,而且在這裏資源的範疇會被無限放大而並不是侷限在文件自己,例如如下實例:api

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將會是一個不錯的選擇。瀏覽器

二、表現形式(Representational)

  在REST中表現形式做爲咱們對資源請求的一個結果的呈現,經過對HTTP協議的學習咱們已經知道,服務器會給客戶端返回什麼形式的信息,這一點取決於服務器響應報文中相關頭部字段,而對REST來說,它一般會採用XML或者JSON來告訴請求者請求的結果,由於JSON相比XML所含的冗餘信息較少,因此目前更加傾向於或者說流行使用JSON做爲請求結果的表現形式安全

三、狀態變化(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表示請求錯誤等等。網絡

3、如何構建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)

4、Restful 服務端

  下面主要講解如何用C#實現一個Rest 風格的web服務供外部調用,主要包括如下4點:

  • 定義service的契約
  • 定義URL Routing
  • 實現service
  • 爲服務編寫宿主程序

  一、定義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}

  咱們定義兩種方法,一、GetScore方法:經過GET請求傳入name,返回對應的成績;二、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     /// 簡單定義兩種方法,一、GetScore方法:經過GET請求傳入name,返回對應的成績;二、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)

二、實現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 }

三、服務編寫宿主程序

  上面咱們定義了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 }

  開啓效果以下:

   最後,咱們經過瀏覽器簡單的測試一下GET請求的效果是怎樣的,如圖所示:

PS:更多Restful的C#規範,能夠參考微軟的文檔--->WebGet和WebInvoke傳送門    API傳送門


 

5、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         /// 文本類型(一、application/json 二、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>
 91         /// http請求(帶參數)
 92         /// </summary>
 93         /// <param name="parameters">parameters例如:?name=LiLei</param>
 94         /// <returns></returns>
 95         public string HttpRequest(string parameters)
 96         {
 97             var request = (HttpWebRequest)WebRequest.Create(EndPoint + parameters);
 98 
 99             request.Method = Method.ToString();
100             request.ContentLength = 0;
101             request.ContentType = ContentType;
102 
103             if (!string.IsNullOrEmpty(PostData) && Method == EnumHttpVerb.POST)
104             {
105                 var bytes = Encoding.UTF8.GetBytes(PostData);
106                 request.ContentLength = bytes.Length;
107 
108                 using (var writeStream = request.GetRequestStream())
109                 {
110                     writeStream.Write(bytes, 0, bytes.Length);
111                 }
112             }
113 
114             using (var response = (HttpWebResponse)request.GetResponse())
115             {
116                 var responseValue = string.Empty;
117 
118                 if (response.StatusCode != HttpStatusCode.OK)
119                 {
120                     var message = string.Format("請求數據失敗. 返回的 HTTP 狀態碼:{0}", response.StatusCode);
121                     throw new ApplicationException(message);
122                 }
123 
124                 using (var responseStream = response.GetResponseStream())
125                 {
126                     if (responseStream != null)
127                         using (var reader = new StreamReader(responseStream))
128                         {
129                             responseValue = reader.ReadToEnd();
130                         }
131                 }
132                 return responseValue;
133             }
134         }
135         #endregion
136     }
137 }

  接下來咱們驗證上面resful 服務端當中的兩個實例,代碼以下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using Newtonsoft.Json;
 7 
 8 namespace RestFulClient
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             Console.Title = "Restful客戶端Demo測試";
15 
16             RestClient client = new RestClient();
17             client.EndPoint = @"http://127.0.0.1:7788/";
18 
19             client.Method = EnumHttpVerb.GET;
20             string resultGet = client.HttpRequest("PersonInfoQuery/王二麻子");
21             Console.WriteLine("GET方式獲取結果:" + resultGet);
22 
23             client.Method = EnumHttpVerb.POST;
24             Info info = new Info();
25             info.ID = 1;
26             info.Name = "張三";
27             client.PostData = JsonConvert.SerializeObject(info);//JSon序列化咱們用到第三方Newtonsoft.Json.dll
28             var resultPost = client.HttpRequest("PersonInfoQuery/Info");
29             Console.WriteLine("POST方式獲取結果:" + resultPost);
30             Console.Read();
31         }
32     }
33 
34     [Serializable]
35     public class Info
36     {
37         public int ID { get; set; }
38         public string Name { get; set; }
39     }
40 }

  結果如圖所示:


  至此、一個簡單的Restful 服務端和Restful 客戶端的就這樣實現了,下篇文章講解若是設計一個高效可擴展的Restful完整幫助類,這樣咱們就能夠跨平臺、跨語言訪問其餘平臺提供的Restful Service了。

  文章代碼已上傳Github--->下載地址

PS:若有疑問,請留言,未經容許,不得私自轉載,轉載請註明出處:http://www.cnblogs.com/xuliangxing/p/8735552.html

 

 

相關文章
相關標籤/搜索