ASP.NET MVC Web API - 利用jQuery進行CRUD! (二) Controller篇
支援版本
- ASP.NET MVC 4 Beta
上一篇寫完後,沒想到喝個淡定紅茶,就淡定到現在;前篇介紹了Model的實作,現在我們準備要進入Controller了喔,也就是開始撰寫Web Service!
REST
在開始寫Controller之前,我們要稍微回憶一下REST風格,所謂的REST風格,就是同一個URI下,利用HTTP的四大命令"Get、Post、Put、Delete"來配合CRUD,而不像以前取得資料是一個網址,刪除資料又是一個網址,我們可以看看下面的表。
URI | Get | Post | Put | Delete |
`http://xxx/api/customer` | 取得整組資料 | 新增整組資料 | 更新整組資料 | 刪除整組資料 |
`http://xxx/api/customer/12` | 取得單筆資料 | 新增單筆資料 | 更新單筆資料 | 刪除單筆資料 |
從這邊我們可以看到,URI ( 資源,其實就是一個網址 )都是同一個,但是我們針對不同的HTTP命令,就會有不同的效果,而這些效果剛好符合CRUD,而這邊在強調一下,REST並不是一種規範,而是一種風格,利用HTTP的四大命令來對應資料庫的CRUD,但實際是你也可以使用Get命令來執行DB的Delete,但這樣就不符合REST風格了。
建立Web API吧
了解了這些規則後,接下來,我們要做的就是,如何使用ASP.NET MVC 建立符合REST風格的Web API;首先,第一步當然是先建立Controller嚕,也就是這篇的主題。
接下來,先把CustomerController名稱打好,但是這次的樣板則是選擇API controller with empty read/write actions這種樣板了喔!
完成後,我們會發現,系統自動幫我們準備好了這些程式碼,建立好的Class裡面的Function,也剛好就是上頭提到的Get、Post、Put、和Delete!!剛好就對應到HTTP的四個命令!
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http; namespace MvcWebApiCRUDDemo.Controllers { public class CustomerController : ApiController { // GET /api/customerontroller public IEnumerableGet() { return new string[] { "value1", "value2" }; } // GET /api/customerontroller/5 public string Get(int id) { return "value"; } // POST /api/customerontroller public void Post(string value) { } // PUT /api/customerontroller/5 public void Put(int id, string value) { } // DELETE /api/customerontroller/5 public void Delete(int id) { } } }
修改開始
不過這畢竟不是我們要的,如果能自動產生出我們想要的東西,就好了,但現實總是殘酷的;所以我們要重新修改一下程式碼,完成後的程式碼會長這樣,大家可以先看一下,細節我們下面會再仔細介紹。
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http; using MvcWebApiCRUDDemo.Models; using System.Net; namespace MvcWebApiCRUDDemo.Controllers { public class CustomerController : ApiController { //建立一個靜態的倉儲,這裡使用靜態的目的是為了讓資料可以被CRUD, //而不會因,重新建立此倉儲物件,而造成重塞資料的問題。 private static readonly ICustomerRepository _repository = new CustomerRepository(); // GET /api/customer public IEnumerableGet() { return _repository.GetAll(); } // GET /api/customer/5 public Customer Get(int id) { Customer customer = _repository.Get(id); if (customer == null) { //如果找不到,就拋出HTTP的Response例外,內容是尋找不到,也就是404 throw new HttpResponseException(HttpStatusCode.NotFound); } return customer; } // POST /api/customer public HttpResponseMessage Post(Customer customer) { customer = _repository.Add(customer); var response = new HttpResponseMessage (customer, HttpStatusCode.Created); string uri = Url.Route(null, new { id = customer.Id }); response.Headers.Location = new Uri(Request.RequestUri, uri); return response; } // PUT /api/customer/5 public void Put(int id, Customer customer) { customer.Id = id; if (!_repository.Update(customer)) { //如果找不到,就拋出HTTP的Response例外,內容是尋找不到,也就是404 throw new HttpResponseException(HttpStatusCode.NotFound); } } // DELETE /api/customer/5 public HttpResponseMessage Delete(int id) { _repository.Delete(id); return new HttpResponseMessage(HttpStatusCode.NoContent); } } }
Repository
首先我們在這個Controller先建立一個static的Repository類別,這個類別就是我們上一章準備好的倉儲物件,也就是會提供給我們新增修改Collection方法的地方 ( 別忘了,我們這個範例使用Collection來代替資料庫 ),這邊使用靜態物件的原因,是因為,如果使用非靜態物件,每次產生這個倉儲物件時,都會自動塞資料到Collection,所以我們利用靜態物件來處理,這樣子,這個倉儲物件,就只會產生一次,淡定的在那邊=v=。
( 備註一下,並不是Repository Patten都要使用靜態物件!這裡使用靜態的原因就如上面所說,但如果今天我們是針對資料庫,我們就可以不需要使用靜態物件,另外,有人可能會疑惑,為什麼要使用介面,這其實是為了測試阿!!,當我們希望針對這個Controller進行Unit Test的時候,我們就可以Mock一個假的倉儲物件,來"模擬"新增修改刪除,所以這邊才要使用介面,這也就是使用Repository Patten的精隨。 )
private static readonly ICustomerRepository _repository = new CustomerRepository();
當有了倉儲物件,我們就可以輕易地去做新增修改等方法,如下,這就是很典型的取得所有資料的方法。
_repository.GetAll();
就是這麼簡單。
Get
接下來,我們看看第一個Get方法,這個方法其實就會對應到HTTP的Get命令,簡單的說,當我們對/api/customer這個URI,使用HTTP的Get命令,就會執行到這個Function,然後就會取得一堆JSON的資料,這就是ASP.NET MVC Web API的特色;當然,如果熟悉ASP.NET MVC的人,可能會想說,Get這個Action ( 對MVC來說Action其實就是個Function )的網址應該是xxx/Get阿!?以前不都是這樣搞的嗎!?,但不要懷疑,的確也可以像以前一樣的網址,但這部分後續章節才會講到。回頭看Web API,Web API 裡面的Function名稱,如果取名為Get,就會對應到HTTP的Get命令,反正只要記住,在ASP.NET MVC Web API底下( 也就是繼承了ApiController的Controller ),預設的Function名稱,只要對應到HTTP四大命令的名稱,就是會去對應到HTTP的四大命令。另外,如果有人看過ASP.NET MVC官網的範例,會發現,人家的範例是寫GetAllContacts(),不用懷疑,其實Function的名稱,只要前面有符合HTTP四大命令的名稱,就會對應到了。
// GET /api/customer public IEnumerableGet() { return _repository.GetAll(); }
如果使用IE進行分析,就可以看到對這個網址進行了GET請求。( 底下的圖,是整個專案寫完後,執行並截圖下來的,因為View的地方還沒有講到,所以這邊就只截分析的片段,等講道View的部分,會再帶大家看一次。 )
會回應JSON格式,沒錯,不用懷疑,ASP.NET MVC 預設就是回應JSON的格式,基本上JSON的格式還滿簡單的,如果對JSON不了解的人,可以參考一下這篇的中間,或是這篇的最後面有參考連結。
還是Get
接下來,我們繼續往下看,接下來,還是Get,但這次的Get帶了參數,沒錯,這個Get的用途就是取得單筆的資訊,我們可以透過/api/customer/5,來取得id為5的資料回來;而如果找不到,我們就會拋出一個例外,這個例外大家應該很熟習,就是傳說中的404。
// GET /api/customer/5 public Customer Get(int id) { Customer customer = _repository.Get(id); if (customer == null) { //如果找不到,就拋出HTTP的Response例外,內容是尋找不到,也就是404 throw new HttpResponseException(HttpStatusCode.NotFound); } return customer; }
我們會發現,還是使用Get( 廢話XDD )。
這裡只回應單筆了。
Post
然後接著的是Post,也就是準備要做新增的處理,但這裡的新增有比較不一樣的地方,首先,我們會將Customer這個Model傳進去來處理,然後當然也是用倉儲Class來進行新增的動作,但比較特別的返回型別,這裡返回的是一個HttpReponseMessage,那是因為,Web API框架預設的回應狀態為200(OK),但在HTTP/1.1的協定裏面,如果使用POST創立一個資源時,因該是要返回201( Created ),這部分未來不知道正式版會不會修正,但現階段,我們只能自己手寫的方式( 或是直接拷貝貼上的方式XDD ),來處理201的狀態,這也就是為什麼需要返回一個HttpResponseMessage型別的原因;那這樣就ok了嗎?當然不,根據協定,除了要返回201以外,還必須在回應的Headers裡面的Location加上新資源的URI,這樣才是完整的!!反正結論就是必須加上下面這些東西,還是衷心的期望,正式版可以符合懶人小弟我的需求XDDD
// POST /api/customer public HttpResponseMessage Post(Customer customer) { customer = _repository.Add(customer); var response = new HttpResponseMessage(customer, HttpStatusCode.Created); string uri = Url.Route(null, new { id = customer.Id }); response.Headers.Location = new Uri(Request.RequestUri, uri); return response; }
依照慣例,我們還是要貼一下圖,這次我們使用了POST了。
然後我們可以看一下,送出去了那些東西 ( 好啦,我承認我懶惰,把名字和電話都打"2" )
接下來,我們可以看到,回應的部分,的確如預期是回應201 Created了,並且在Location裡面有附上URI,因為是第四筆,所以後面多了4。
接下來,我們來看看Update。
Update
Update對應的其實就是HTTP的Put,這大家應該就比較少見了,但是其實還是依樣簡單,基本上帶入兩個參數,第一個參數id,是透過網址的方式傳進來的,第二個參數Customer,未來則是會用JSON格式傳遞過來,那內容其實也很簡單,畢竟我們將更新的方法都封裝到Repository Class裡面去了,我們只要簡單的呼叫_repository.Update就可以了,如果回傳false,就跳出錯誤訊息,代表找不到。
// PUT /api/customer/5 public void Put(int id, Customer customer) { customer.Id = id; if (!_repository.Update(customer)) { //如果找不到,就拋出HTTP的Response例外,內容是尋找不到,也就是404 throw new HttpResponseException(HttpStatusCode.NotFound); } }
這次使用PUT了,所以會看到,請求使用PUT命令,而且可以看到網址最後有1,表示我們更改第一筆。
這次稍微多打了幾個字,我們把第一筆資料的name改為MS,phone也改為MS。( 由此可知,我們的UI和後面的Server端是完全沒檢查的XDDD,未來在教大家如何使用Model進行驗證,方法和ASP.NET MVC的方法其實是一樣的喔! )
Delete
終於寫到最後一個了,寫到快斷氣了= =,Delete通常都是號稱最簡單的XDD,( 破壞果然比建設容易 ),但這邊比較特別的是,這裡會返回一個204,也就是NoContent,根據HTTP/1.1,如果刪除了,可以返回200,或是202 ( 表示已經接受,但還沒刪除 ),或是204 ( 代表的是完成了請求,且不需要返回任何東西 ),這裡204是最適用於Delete,詳細可以參考這裡。
// DELETE /api/customer/5 public HttpResponseMessage Delete(int id) { _repository.Delete(id); return new HttpResponseMessage(HttpStatusCode.NoContent); }
然後,我們依樣看一下圖,這次使用了DELETE命令。
回應的是204 No Contant。
後記
這篇又打了超久,但至少未來如果自己懶得打程式,也可以直接拷貝和貼上使用XDD;我們從這邊可以感受到Repository Patten所帶來的一些好處,也深深的了解到ASP.NET MVC Web API的寫法,更加知道了Web Service應該回應的status,下一篇,我們將進入View的世界,使用JavaScript和jQuery來撰寫AJAX的應用程式,來呼叫這裡寫好的Web Service!!
( 先去準備打Diablo 3了.. )