構建基於WCF Restful Service的服務

前言html

傳統的Asmx服務,因爲遵循SOAP協議,因此返回內容以xml方式組織。而且客戶端須要添加服務端引用才能使用(雖然看到網絡上已經提供了這方面的Dynamic Proxy,可是沒有這種方式簡便),因此給開發和部署帶來了不小的麻煩。而且當服務過多的時候,生成的引用文件會很大,以前項目的一個引用文件光引用代碼都有5000多行,所有在一個類中。確實不方便維護。json

基於以上幾點,就特別研究了一下基於Restful的服務開發,當時手頭有兩種框架,一個是WCF Restful Service,另外一個是Asp.net Web API。因爲對WCF比較熟悉一些,因此就選擇了前者。設計模式

Restful Service及其相關網絡

說到Restful Service,不得不提到其中的Rest這個關鍵字。它是用於建立分佈式超文本媒體的一種架構方式,咱們能夠經過標準的HTTP(GET,POST,PUT,DELETE)操做來構建基於面向資源的軟件架構方式(Resource-Oriented Architecture (ROA))。它是獨立於任何技術或者平臺的,因此人們常常將符合這種操做規範的服務稱爲「RESTful services」。由於WCF可以構建符合這種規範的服務,因此咱們常常稱之爲 WCF Restful Services。架構

因爲傳統的WCF Service可使用tcp,net.msmq,http等協議進行數據交換,而且採用了RPC(Remote Procedure Call)的工做方式,客戶端須要添加對服務端的引用才能完成。可是WCF Restful Service徹底使用Http協議來進行,而且無需添加客戶端引用,因此方便不少。app

服務端開發一瞥框架

下面以圖書館的例子來作具體的說明。less

打開VS2010,新建一個WCF REST Service Application項目,而後在項目中,添加一個BookService.cs用於處理邏輯操做,再添加一個BookEntity.cs用於提供實體類。異步

打開Global.asax,能夠看到以下代碼:tcp

   1:   void Application_Start(object sender, EventArgs e)
   2:   {
   3:      RegisterRoutes();
   4:   }
   5:   
   6:   private void RegisterRoutes()
   7:   {
   8:      RouteTable.Routes.Add(new ServiceRoute("BookService", new WebServiceHostFactory(), typeof(BookService)));
   9:   }

 

其中RegisterRoutes是設定服務啓動的入口點的。

而後是BookEntity實體類的組織方式:

   1:   public class BookEntity
   2:   {
   3:          public int BookID { get; set; }
   4:   
   5:          public string BookName { get; set; }
   6:   
   7:          public decimal BookPrice { get; set; }
   8:   
   9:          public string BookPublish { get; set; }
  10:   }

這裏我就不用多說了,實體類包含圖書序號,圖書名稱,圖書價格,出版單位四個屬性。

而後就是咱們的核心內容:

   1:      [ServiceContract]
   2:      [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   3:      [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
   4:      public class BookService
   5:      {
   6:          public BookService()
   7:          {
   8:              bookList = new List<BookEntity>();
   9:              BookEntity book = new BookEntity();
  10:              book.BookID = 1;
  11:              book.BookName = "大話設計模式";
  12:              book.BookPrice = (decimal)45.2;
  13:              book.BookPublish = "中國郵電出版社";
  14:              bookList.Add(book);
  15:   
  16:              BookEntity book1 = new BookEntity();
  17:              book1.BookID = 2;
  18:              book1.BookName = "測試用例";
  19:              book1.BookPrice = (decimal)21.0;
  20:              book1.BookPublish = "清華大學出版社";
  21:              bookList.Add(book1);
  22:   
  23:              BookEntity book2 = new BookEntity();
  24:              book2.BookID = 3;
  25:              book2.BookName = "Rework";
  26:              book2.BookPrice = (decimal)15.4;
  27:              book2.BookPublish = "Wrox pulishment";
  28:              bookList.Add(book2);
  29:          }
  30:   
  31:          private static List<BookEntity> bookList;
  32:   
  33:          [WebInvoke(Method = "GET"
  34:              , ResponseFormat = WebMessageFormat.Json
  35:              , BodyStyle = WebMessageBodyStyle.Bare   //不須要任何修飾,不然生成的json沒法解析
  36:              , UriTemplate = "/?bookID={bookID}")]  //只接收string類型,若是是其餘類型,須要按照 /?para={parameter}的方式來組織。
  37:          public BookEntity Get(int bookID)
  38:          {
  39:              return bookList.Where(p => p.BookID == bookID).FirstOrDefault();
  40:          }
  41:   
  42:          [WebInvoke(Method = "GET"
  43:              , ResponseFormat = WebMessageFormat.Json
  44:              , BodyStyle = WebMessageBodyStyle.Bare
  45:              , UriTemplate = "/")]
  46:          public List<BookEntity> GetALL()
  47:          {
  48:              return bookList;
  49:          }
  50:   
  51:            [WebInvoke(Method = "POST"
  52:              , ResponseFormat = WebMessageFormat.Json
  53:              , BodyStyle = WebMessageBodyStyle.Bare
  54:              , UriTemplate = "/")]
  55:          public bool Update(BookEntity book)
  56:          {
  57:              BookEntity query = (from p in bookList where p.BookID == book.BookID select p).FirstOrDefault();
  58:              bookList.Remove(query);
  59:              bookList.Add(book);
  60:              return true;
  61:            }
  62:   
  63:           [WebInvoke(Method = "PUT"
  64:              , ResponseFormat = WebMessageFormat.Json
  65:              , BodyStyle = WebMessageBodyStyle.Bare
  66:              , UriTemplate = "/")]
  67:          public bool Add(BookEntity book)
  68:          {
  69:              bookList.Add(book);
  70:              return true;
  71:          }
  72:   
  73:          [WebInvoke(Method = "DELETE"
  74:              , ResponseFormat = WebMessageFormat.Json
  75:              , BodyStyle = WebMessageBodyStyle.Bare
  76:              , UriTemplate = "/")]
  77:          public bool Delete(BookEntity book)
  78:          {
  79:              BookEntity bookCurrent = (from p in bookList where p.BookID == book.BookID select p).FirstOrDefault();
  80:              return bookList.Remove(bookCurrent);
  81:          }
  82:      }

其中,Method 方法主要是代表能夠接受客戶端的請求類型,這裏有四種:GET,POST,PUT,DELETE,其中GET爲請求數據,POST爲更新數據,PUT爲新增數據,DELETE表明着刪除數據。

而後ResponseFormat 則表明着返回的數據組織,若是是Json則代表客戶端會接收到Json數據,若是是XML則代表客戶端會接收到XML組織的數據。BodyStyle 表明返回數據的包裝對象,若是是Bare則代表數據無任何包裝,原生數據返回;若是是Wrapped則代表數據會在最外層包裝一個當前函數名稱加上Result的套。好比對於Delete對象,則會返回 DeleteResult:{******},會形成DataContractJsonSerializer沒法進行反序列化。

UriTemplate 主要用於指定操做的URI路徑,只要用戶輸入了合法路徑並採用了正確的請求方式,就會觸發該函數。

最後說到的就是URI後面跟的參數的問題,因爲函數只能接受string類型的,因此若是傳入參數是string類型,則可使用UriTemplate = "{bookID}"的路徑,反之,則須要加上/?param1={paramname}的方式,好比我代碼中使用的是:UriTemplate = "/?bookID={bookID}"。

當一切都弄好之後,讓咱們運行一下,訪問以下路徑,就能夠獲得結果:

http://localhost:45345/BookService/

獲得的結果以下:

[{"BookID":1,"BookName":"大話設計模式","BookPrice":45.2,"BookPublish":"中國郵電出版社"},{"BookID":2,"BookName":"測試用例","BookPrice":21,"BookPublish":"清華大學出版社"},{"BookID":3,"BookName":"Rework","BookPrice":15.4,"BookPublish":"Wrox pulishment"}]

 若是訪問http://localhost:45345/BookService/?bookID=1,則會獲得以下的結果:

{"BookID":1,"BookName":"大話設計模式","BookPrice":45.2,"BookPublish":"中國郵電出版社"}

客戶端開發一瞥

初步測試成功後,讓咱們來進行一下全面的測試。

首先,在項目中,咱們新建一個Asp.net WebForm Application,用於作測試工做。

而後,在Default.aspx.cs中,針對GET操做,咱們添加以下代碼:

   1:   private void GetBookByID(string id)
   2:          {
   3:              WebClient proxy = new WebClient();
   4:              string serviceURL = string.Empty;
   5:              DataContractJsonSerializer obj ;
   6:              if (string.IsNullOrEmpty(id))
   7:              {
   8:                  serviceURL = string.Format("http://localhost:45345/BookService/");
   9:                  obj = new DataContractJsonSerializer(typeof(List<BookEntity>));
  10:              }
  11:              else
  12:              {
  13:                  serviceURL = string.Format("http://localhost:45345/BookService/?bookID=" + id);
  14:                  obj = new DataContractJsonSerializer(typeof(BookEntity));
  15:              }
  16:              byte[] data = proxy.DownloadData(serviceURL);
  17:              Stream stream = new MemoryStream(data);
  18:              var result = obj.ReadObject(stream);
  19:              List<BookEntity> list=new List<BookEntity>();
  20:              if (result is BookEntity)
  21:                  list.Add(result as BookEntity);
  22:              else if (result is List<BookEntity>)
  23:                  list = result as List<BookEntity>;
  24:              GridView1.DataSource = list;
  25:              GridView1.DataBind();
  26:          }

在以上代碼中,DataContractJsonSerializer 是WCF提供的一個序列化類,用於將對象序列化或者反序列化。

寫好以後,咱們點擊界面按鈕,出現瞭如下的結果:

QQ截圖20140225141523QQ截圖20140225141545

針對PUT操做,也就是添加操做,咱們添加以下代碼:

   1:  BookEntity bookEntity = new BookEntity();
   2:              bookEntity.BookID = Int32.Parse(txtBookID.Text);
   3:              bookEntity.BookName = txtBookName.Text;
   4:              bookEntity.BookPrice = decimal.Parse(txtBookPrice.Text);
   5:              bookEntity.BookPublish = txtBookPublish.Text;
   6:   
   7:              DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(BookEntity));
   8:              MemoryStream ms = new MemoryStream();
   9:              obj.WriteObject(ms, bookEntity);
  10:              byte[] byteSend = ms.ToArray();
  11:              ms.Close();
  12:   
  13:              string serviceURL = string.Format("http://localhost:45345/BookService");
  14:   
  15:              WebClient test = new WebClient();
  16:              test.Headers.Add("Content-Type", "application/json");
  17:              test.Headers.Add("ContentLength", byteSend.Length.ToString());
  18:              
  19:   
  20:              byte[] responseData = test.UploadData(serviceURL, "PUT", byteSend);
  21:   
  22:              string result = Encoding.GetEncoding("UTF-8").GetString(responseData);
  23:              lblLog.Text = result;

在作這步的時候,須要注意,test.Headers.Add("Content-Type", "application/json") 和test.Headers.Add("ContentLength", byteSend.Length.ToString())須要添加,不然會形成Http 400 返回的錯誤。而且,向服務端傳遞實體的時候,能夠經過使用UploadData的方式來進行,若是數據量過大,能夠考慮使用異步方式傳送。

接下來的POST和DELETE方法和上面相似,我都貼一下:

POST方法:

   1:   BookEntity bookEntity = new BookEntity();
   2:              bookEntity.BookID = Int32.Parse(txtBookID.Text);
   3:              bookEntity.BookName = txtBookName.Text;
   4:              bookEntity.BookPrice = decimal.Parse(txtBookPrice.Text);
   5:              bookEntity.BookPublish = txtBookPublish.Text;
   6:   
   7:              DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(BookEntity));
   8:              MemoryStream ms = new MemoryStream();
   9:              obj.WriteObject(ms, bookEntity);
  10:              byte[] byteSend = ms.ToArray();
  11:              ms.Close();
  12:   
  13:              string serviceURL = string.Format("http://localhost:45345/BookService");
  14:   
  15:              WebClient test = new WebClient();
  16:              test.Headers.Add("Content-Type", "application/json");
  17:              test.Headers.Add("ContentLength", byteSend.Length.ToString());
  18:              
  19:              byte[] responseData = test.UploadData(serviceURL, "POST", byteSend);
  20:   
  21:              string result = Encoding.GetEncoding("UTF-8").GetString(responseData);
  22:              lblLog.Text = result;

DELETE方法:

   1:   BookEntity bookEntity = new BookEntity();
   2:              bookEntity.BookID = Int32.Parse(txtBookID.Text);
   3:   
   4:              DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(BookEntity));
   5:              MemoryStream ms = new MemoryStream();
   6:              obj.WriteObject(ms, bookEntity);
   7:              byte[] byteSend = ms.ToArray();
   8:              ms.Close();
   9:   
  10:              string serviceURL = string.Format("http://localhost:45345/BookService");
  11:   
  12:              WebClient test = new WebClient();
  13:              test.Headers.Add("Content-Type", "application/json");
  14:              test.Headers.Add("ContentLength", byteSend.Length.ToString());
  15:              
  16:   
  17:              byte[] responseData = test.UploadData(serviceURL, "DELETE", byteSend);
  18:   
  19:              string result = Encoding.GetEncoding("UTF-8").GetString(responseData);
  20:              lblLog.Text = result;

最後獲得的效果圖以下:

QQ截圖20140225142104

(新增記錄)

QQ截圖20140225142135

(更新記錄)

QQ截圖20140225142154

(刪除記錄)

 成文倉促,不免有誤,還請指出,在此謝過。

源代碼下載

點擊下載源代碼  

 

Edit:基於本方法構建的Android服務已經在使用中。後續繼續跟進各類使用信息。

在StackOverFlow問答以下:點擊這裏查看

 看評論中提到了IContract問題,因爲這是Restful Service,不是基於RPC模式,因此不必使用的。不過用上去也沒錯。

相關文章
相關標籤/搜索