【REST WCF】30分鍾理論到實踐


先來點理論知識,來自 http://www.cnblogs.com/simonchen/articles/2220838.html

一.什么是Rest


  REST軟件架構是由Roy Thomas Fielding博士2000年在他的論文《Architectural Styles and the Design of Network- based Software Architectures》首次提出的。他提出的理論對后來的Web技術的發展產生了巨大的影響,他是許多重要Web架構標准的設計者,這些標准就是 HTTP、URI等。

  • Rest的英文全稱是“Representational State Transfer”。中文翻譯為“表述性狀態轉移”。REST本身只是為分布式超媒體系統設計的一種架構風格,而不是標准。
  • 那么如何理解“Representational State Transfer”這句話呢?下面我們來解釋一下:
    1.     Representational :中文直譯:代表的,表像的。如果把WEB 服務器端中所有的東西(數據)都看作是資源(Resource),那么呈現在用戶面前(客戶端)的就是資源的表像(Representation)。每一個資源都有自己的唯一標識(URI)。
    2.     State :中文直譯:狀態。首先這個狀態是客戶端的狀態,而不是服務器端的狀態(在REST 中,服務器端應該是無狀態的)。那么,把State和Representation聯系在一起(Representational State),可以理解成:每一個資源(Resource)在客戶端的表像(Representation)就是客戶端的一個狀態(State)。
    3.     Transfer:中文直譯:轉移。當用戶通過不同的URI訪問不同的資源時,客戶端的表像(Representation)也會隨着變化,也就意味着客戶端的狀態變更(Transfer)了,連起來就是:Representational State Transfer。
  • REST=老的Web規范+3個新的規范:REST實際上也是基於已有的Web規范集合產生的。傳統的Web應用大都是BS系統,這些系統共同遵循一些老的Web規范,這些規范主要包含 3條:
    1.   客戶-服務器:這種規范的提出,改善了用戶接口跨多個平台的可移植性,並且通過簡化服務器組件,改善了系統的可伸縮性。最為關鍵的是通過分離用戶接口和數據存儲這兩個關注點,使得不同用戶終端享受相同數據成為了可能。
    2.   無狀態性:無狀態性是在客戶-服務器約束的基礎上添加的又一層規范。他要求通信必須在本質上是無狀態的,即從客戶到服務器的每個request都 必須包含理解該request所必須的所有信息。這個規范改善了系統的可見性(無狀態性使得客戶端和服務器端不必保存對方的詳細信息,服務器只需要處理當 前request,而不必了解所有的request歷史),可靠性(無狀態性減少了服務器從局部錯誤中恢復的任務量),可伸縮性(無狀態性使得服務器端可 以很容易的釋放資源,因為服務器端不必在多個request中保存狀態)。同時,這種規范的缺點也是顯而易見得,由於不能將狀態數據保存在服務器上的共享 上下文中,因此增加了在一系列request中發送重復數據的開銷,嚴重的降低了效率。
    3.       緩存:為了改善無狀態性帶來的網絡的低效性,我們填加了緩存約束。緩存約束允許隱式或顯式地標記一個response中的數據,這樣就賦予了客戶 端緩存response數據的功能,這樣就可以為以后的request共用緩存的數據,部分或全部的消除一部分交互,增加了網絡的效率。但是用於客戶端緩存了信息,也就同時增加了客戶端與服務器數據不一致的可能,從而降低了可靠性。

 

  • REST在原有的架構上增加了3個新規范:統一接口、分層系統和按需代碼:
    1.       統一接口:REST架構風格的核心特征就是強調組件之間有一個統一的接口,這表現在REST世界里,網絡上所有的事物都被抽象為資源,而REST 就是通過通用的鏈接器接口對資源進行操作。這樣設計的好處是保證系統提供的服務都是解耦的,極大的簡化了系統,從而改善了系統的交互性和可重用性。並且 REST針對Web的常見情況做了優化,使得REST接口被設計為可以高效的轉移大粒度的超媒體數據,這也就導致了REST接口對其它的架構並不是最優的。
    2.       分層系統:分層系統規則的加入提高了各種層次之間的獨立性,為整個系統的復雜性設置了邊界,通過封裝遺留的服務,使新的服務器免受遺留客戶端的影響,這也就提高了系統的可伸縮性。
    3.       按需代碼:REST允許對客戶端功能進行擴展。比如,通過下載並執行 applet或腳本形式的代碼,來擴展客戶端功能。但這在改善系統可擴展性的同時,也降低了可見性。所以它只是REST的一個可選的約束。

二.Rest的特點


     由於Rest遵守的這些規范,因此Rest架構的特點也非常的明顯:

  •   REST是一種架構,而不是一個規范。
  •   REST是一種典型的Client-Server架構,但是強調瘦服務器端,服務器端只應該處理跟數據有關的操作,所有有關顯示的工作都應該放在客戶端。
  •   在REST架構中,服務器是無狀態的,也就是說服務器不會保存任何與客戶端的會話狀態信息。所有的狀態信息只能放在雙方溝通的 Message(消息)中。
  •   REST架構是冪等的,對於相同的請求,服務器返回的結果也是相同的,因此服務器端返回的結果是可以緩存的,既可以存在客戶端也可以存在代理服務器端。
  •   在REST架構中,所有的操作都是基於統一的方式進行的:
    1.     每個Resource都有一個唯一的ID。
    2.     通過Representation(客戶端)來處理Resource(服務器端)。也就是說,客戶端不能直接操作服務器端的Resource,只能通過對相應的Representation的操作,並發送相應的請求,最后由服務器端來處理Resource並返回結果。
    3.     客戶端和服務器端傳送的任何一個Message(消息),都應該是自描述的。也就是說處理這個 Message所需要的上下文環境都應該包含在這個Message當中。
    4.     多媒體的交互系統,客戶端和服務器端傳送的內容可以是文檔,圖片,聲音等等多媒體數據,這也是一個Resource能夠對應不同的Representation(例如文檔,圖片等)的基礎。 

 

  •   分層結構,像TCP/IP的分層結構一樣,第n層使用第n-1層提供的服務並為第n+1層提供服務。在REST中,Client- Server之間加入了Proxy層和Gateway層。在這些中間層可以加入一些業務處理以外的功能,譬如:負載均衡,安全控制等等。
  •   Code-On-Demand,客戶端可以訪問服務器端的Resource,但並不知道如何處理服務器端返回的結果,這個處理過程的代碼應該是從服務器端發送過來,然后在客戶端執行,也就是說客戶端的功能是根據需要動態從服務器端獲得的。一個很簡單的例子,Applet就是從服務器端下載然后在客戶端執行的。注意,這個特性是可選的(Optional),也就是說在你的REST實現當中,可以不考慮這個特性。

三.Rest的優點


      既然Rest風格有這些特點,那么也就具備了許多優點:

  •   緩存使用 HTTP 向 RESTful 端點申請數據時,用到的 HTTP 動詞是 GET。對於 GET 請求響應中返回的資源,可以用多種不同的方式進行緩存。Conditional GET 就是可供選擇的一種實現細節,客戶端可以向服務驗證他的數據是否為最新版本;RESTful 端點可以通過它進一步提高速度和可伸縮性。
  •   擴展 REST 鼓勵每項資源包含處理特殊請求所需的所有必要狀態。滿足這一約束時,RESTful 服務更易於擴展且可以沒有狀態。
  •   副作用如您使用 GET 請求資源,RESTful 服務應該沒有副作用(遺憾的是,與其他一些 REST 約束相比,這一約束更容易被打破)。
  •   冪等統一接口另外兩個常用到的主要 HTTP 動詞是 PUT 和 DELETE。用戶代理想要修改資源時最常使用 PUT,DELETE 可以自我描述。要點(也就是“冪等”一詞所強調的)是您可以對特殊資源多次使用這兩個動詞,效果與首次使用一樣——至少不會有任何其他影響。構建可靠的分布式系統時(即錯誤、網絡故障或延遲可能導致多次執行代碼),這一優點可提供保障。
  •   互操作性許多人將 SOAP 捧為建立客戶端-服務器程序最具互操作性的方法。但一些語言和環境至今仍沒有 SOAP 工具包。有一些雖然有工具包,但采用的是舊標准,不能保證與使用更新標准的工具包可靠溝通。對於大多數操作,REST 僅要求有 HTTP 庫(當然,XML 庫通常也很有幫助),它的互操作性肯定強過任何 RCP 技術(包括 SOAP)。
  •   簡易性與其他優點相比,這一優點更主觀一些,不同的人可能有不同的感受。對我而言,使用 REST 的簡易性涉及到代表資源的 URI 和統一接口。作為一名 Web 沖浪高手,我理解在瀏覽器中輸入不同的 URI 可以得到不同的資源(有時也被稱為 URI 或 URL 黑客,但絕無惡意)。由於有多年使用 URI 的經驗,所以為資源設計 URI 對我來說得心應手。使用統一接口簡化了開發過程,因為我不必為每個需要建立的服務構建接口、約定或 API。接口(客戶端與我的服務交互的方式)由體系結構約束設置。

四.Rest的設計原則


  REST架構是針對Web應用而設計的,其目的是為了降低開發的復雜性,提高系統的可伸縮性。REST提出了如下設計准則:

  1. 網絡上的所有事物都被抽象為資源(resource),比如圖片、音樂、視頻、文字、以及服務等等;
  2. 每個資源有唯一的資源標識符(resource identifier),URI定位資源;
  3. 通過通用的連接器接口(generic connector interface)對資源進行操作,比如使用 HTTP 標准動詞(GET、POST、PUT 和 DELETE)的統一接口完成操作;
  4. 對資源的各種操作不會改變資源標識符,URI不變;
  5. 所有的操作都是無狀態的(stateless)。

五.wcf3.5到wcf4.0 Rest的新增特性

 

  1. 增加對路由的支持
  2. 對緩存的支持。
  3. 幫助(help)頁面的改進。
  4. 消息錯誤處理
  5. 消息格式的多樣性如(XML\JSON\ATOM\TEXT\BINARY)
  6. 簡化操作。

甩過一遍理論,那么就趁熱實踐一番吧!

六.實踐出真知

 

按正常步驟新建一個WCF應用,常見的CRUD操作

 [ServiceContract]
    public interface IExampleService
    {

        [OperationContract]
        string GetData(string value);

        [OperationContract]
        string AddData(string value);

        [OperationContract ]
        string UpdateData(string value);

        [OperationContract ]
        string DeleteData(string value);

    }

那么rest模式該是如何呢?

 [ServiceContract]
    public interface IExampleService
    {

        [OperationContract]
        [WebGet(UriTemplate = "/Rest/Get/{value}", ResponseFormat = WebMessageFormat.Json)]
        string GetData(string value);

        [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/Add/{value}", Method = "POST")]
        string AddData(string value);

        [OperationContract ]
        [WebInvoke(UriTemplate = "/Rest/Update/{value}", Method = "PUT")]
        string UpdateData(string value);

        [OperationContract ]
        [WebInvoke (UriTemplate="/Rest/Delete/{value}",Method="DELETE")]
        string DeleteData(string value); 
       
    }

比較下就很容易看出多加了些標簽,並且也從方法的使用上可以對應出GET、POST、PUT、DELETE的使用。

wcf可以看元數據,那么rest也有對應的方式,在web.config中添加如下配置就可以查看help頁面

 <services>
      <service name="RestWCFTest.ExampleService">
        <endpoint address="" behaviorConfiguration="HelpBehavior" binding="webHttpBinding"
          bindingConfiguration="" contract="RestWCFTest.IExampleService" />
      </service>
    </services>

  <behaviors>
      <endpointBehaviors>
        <behavior name="HelpBehavior">
          <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
</behaviors>

help頁面如下

點擊方法進去可以看見調用方式

我們的接口實現

   public string GetData(string value)
        {
            return string.Format("You entered: {0}", value);
        }

        public string AddData(string value)
        {
            return string.Format("You added: {0}", value);
        }

        public string UpdateData(string value)
        {
            return string.Format("You updated: {0}", value);
        }

        public string DeleteData(string value)
        {
            return string.Format("You deleted: {0}", value);
        }

現在我們用fiddler來模擬請求測試下

在composer選項里有模擬請求的功能,very good!我們先來調用GetData操作,根據參數獲取數據,根據設置的URI模板,“123456”為匹配

的參數,執行它!

看請求的信息

GET http://localhost/REST4/ExampleService.svc/Rest/Get/123456 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

看響應的數據

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 21
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:16:52 GMT

"You entered: 123456"

200 OK 調用正常,content-type是json,因為我們指定的,IIS是7.5,對,我的確是部署在7.5上。。。看結果也是和預期一模一樣,so easy~

可能有同學會問,這是返回的json數據么?我也覺得不是,如果在方法標簽上修改為如下

 [OperationContract]
        [WebGet(UriTemplate = "/Rest/Get/{value}",BodyStyle=WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
        string GetData(string value);

多加了個修飾bodystyle,它的功能是對結果進行包裝,包裝后再看返回的結果

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 39
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:34:24 GMT

{"GetDataResult":"You entered: 123456"}

果然,被包裝了,它是一個json格式的數據了。

POST

請求

POST http://localhost/REST4/ExampleService.svc/Rest/Add/1234 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 92
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:06:41 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You added: 1234</string>

這個時候我們沒有指定返回的格式,默認為XML。

PUT

請求

PUT http://localhost/REST4/ExampleService.svc/Rest/Update/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:23:04 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You updated: 123</string>

DELETE

請求

DELETE http://localhost/REST4/ExampleService.svc/Rest/Delete/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:14:56 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You deleted: 123</string>

有同學可能DELETE請求發出去沒反應(IIS 7.5),請在web.config里加上以下節點

 <system.webServer>
<modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
    </modules>
<handlers>
      <remove name="WebDAV" />
    </handlers>
 </system.webServer>

至此一般的傳參情況就是如此了,下面列舉一些其它傳參情況

   [OperationContract]
        [WebGet(UriTemplate = "/Rest/GetList2/", ResponseFormat = WebMessageFormat.Json)]
        List<ExampleData> GetDataLs2();

   [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/AddLs3", BodyStyle = WebMessageBodyStyle.Wrapped,
            ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
        List<ExampleData> AddDataLs3(List<ExampleData> datas);

        [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/AddLs4", BodyStyle = WebMessageBodyStyle.Wrapped,
            ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
        List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2);

實體

  public class ExampleData
    {
        public string Name
        {
            get;
            set;
        }

        public string Age
        {
            get;
            set;
        }
    }

接口實現

  public List<ExampleData> GetDataLs2()
        {
            List<ExampleData> result = new List<ExampleData> { 
                new ExampleData{ Name="張三", Age="20"}
                ,new ExampleData {Name="李四",Age="21"}
                 ,new ExampleData {Name="王五",Age="30"}
            };

            return result;
        }

  public List<ExampleData> AddDataLs3(List<ExampleData> datas)
        {
            return datas;
        }

  public List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2)
        {
            List<ExampleData> result = new List<ExampleData>();
            result.AddRange(datas1);
            result.AddRange(datas2);

            return result;
        }

 

我們看到有獲取實體集合了,還有傳參的時候也是實體集合了

首先看看獲取集合的情況

請求

GET http://localhost/REST4/ExampleService.svc/Rest/GetList2/ HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 88
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:21:52 GMT

[{"Age":"20","Name":"張三"},{"Age":"21","Name":"李四"},{"Age":"30","Name":"王五"}]

嗯,返回的格式不錯。

看看怎樣做新增操作的

AddDataLs3

請求

POST http://localhost/REST4/ExampleService.svc/Rest/AddLs3 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 41

{"datas":[{"Name":"xiaohua","Age":"13"}]}

這時候我們會注意到,多了request body了,並且datas就是參數名

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 52
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:59:55 GMT

{"AddDataLs3Result":[{"Age":"13","Name":"xiaohua"}]}

被包裝了的數據。

AddDataLs4

請求

POST http://localhost/REST4/ExampleService.svc/Rest/AddLs4 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 78

{"datas1":[{"Name":"xiaohua","Age":"13"}],"datas2":[{"Name":"li","Age":"13"}]}

響應

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 77
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 07:02:58 GMT

{"AddDataLs4Result":[{"Age":"13","Name":"xiaohua"},{"Age":"13","Name":"li"}]}

 

面對茫茫多的CRUD的時候,我們也許會顯得不耐煩,因為每個操作都去寫方法,真是煩躁,不妨可以試下如下的方式

   [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/*", Method = "*", ResponseFormat = WebMessageFormat.Json)]
        string ExecuteData();

用星號來匹配所有的請求,讓程序區識別請求到底是GET、POST、PUT還是DELETE

實現

   public string ExecuteData()
        {
            var request = WebOperationContext.Current.IncomingRequest;

            var method = request.Method;
            var args = request.UriTemplateMatch.WildcardPathSegments;

            switch (method)
            {
                case "POST":
                    return "POST...";
                case "DELETE":
                    return "DELETE...";
                case "PUT":
                    return "UPDATE...";
                default:
                    return "GET...";
            }
        }

嗯,不知不覺就貼了這么多代碼了,其實我字沒寫多少,今天就到這吧,算是入門了吧。

以上例子所有源碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM