ASP.NET MVC Web API - 利用jQuery進行CRUD! (二) Controller篇

18 May 2012 — Written by Sky Chang
#ASP.NET MVC#Design Patten#快速使用

支援版本

  • 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嚕,也就是這篇的主題。

image

接下來,先把CustomerController名稱打好,但是這次的樣板則是選擇API controller with empty read/write actions這種樣板了喔!

image

完成後,我們會發現,系統自動幫我們準備好了這些程式碼,建立好的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 IEnumerable Get()
        {
            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 IEnumerable Get()
        {
            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 IEnumerable Get()
{
    return _repository.GetAll();
}

如果使用IE進行分析,就可以看到對這個網址進行了GET請求。( 底下的圖,是整個專案寫完後,執行並截圖下來的,因為View的地方還沒有講到,所以這邊就只截分析的片段,等講道View的部分,會再帶大家看一次。 )

image

會回應JSON格式,沒錯,不用懷疑,ASP.NET MVC 預設就是回應JSON的格式,基本上JSON的格式還滿簡單的,如果對JSON不了解的人,可以參考一下這篇的中間,或是這篇的最後面有參考連結。

image

還是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 )。

image

這裡只回應單筆了。

image

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了。

image

然後我們可以看一下,送出去了那些東西 ( 好啦,我承認我懶惰,把名字和電話都打"2" )

image

接下來,我們可以看到,回應的部分,的確如預期是回應201 Created了,並且在Location裡面有附上URI,因為是第四筆,所以後面多了4。

image

接下來,我們來看看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,表示我們更改第一筆。

image

這次稍微多打了幾個字,我們把第一筆資料的name改為MS,phone也改為MS。( 由此可知,我們的UI和後面的Server端是完全沒檢查的XDDD,未來在教大家如何使用Model進行驗證,方法和ASP.NET MVC的方法其實是一樣的喔! )

image

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命令。

image

回應的是204 No Contant。

image

後記

這篇又打了超久,但至少未來如果自己懶得打程式,也可以直接拷貝和貼上使用XDD;我們從這邊可以感受到Repository Patten所帶來的一些好處,也深深的了解到ASP.NET MVC Web API的寫法,更加知道了Web Service應該回應的status,下一篇,我們將進入View的世界,使用JavaScript和jQuery來撰寫AJAX的應用程式,來呼叫這裡寫好的Web Service!!

( 先去準備打Diablo 3了.. )

參考資料

Sky & Study4.TW