以下會進行一個非常簡單的連絡人管理。透過以下方法來提供服務。
Action說明 | HTTP方法 | URI關連 |
---|---|---|
取得所有連絡人清單 | GET | /api/contacts |
透過 id 取得連絡人資料 | GET | /api/contacts/id |
新增一位連絡人 | POST | /api/contacts |
更新連絡人資料 | PUT | /api/contacts/id |
刪除連絡人資料 | DELETE | /api/contacts/id |
由上表中,我們可以很清楚看到,有兩種資源類型( resource types):
URI | 說明 |
---|---|
/api/contacts | 列出所有連絡人 |
/api/contacts/id | 一位連絡人 |
HTTP 方法
HTTP 主要方法 (GET, PUT, POST, DELETE) 能對應到 CURD 操作:
- GET 接收及顯示。GET 在伺服器應該沒有副作用。
- PUT 更新。PUT 也能拿來"新增"使用,如果伺服器允許 Client 去指定新的 URI。那範例的連絡人管理將不允許使用 PUT 來新增。
- POST 新增。伺服器會給 URI 分配新物件,然後返回此 URI 作為回應訊息的一部分。
- DELETE 刪除。
ps. 現在開啟前一篇的 HelloWebAPI 專案。相關步驟不再重覆。
新增資源
在 ASP.NET Web API,你能在 Model 使用強型別 CLR 物件,他們將會自動序列化為 XML 或 JSON 給 Client。
在 Model 目錄新增 Contact 類別:
''' <summary> ''' 連絡人資訊欄位 ''' </summary> Public Class Contact Property Id As Integer Property Name As String Property Phone As String Property Email As String End Class
使用倉儲模式( Repository Pattern)
我們的 HTTP Service 需要儲存連絡人清單,此範例,連絡人清單會儲存在記憶體( List(Of T ) )。使用 Repository Pattern 會讓此物件從我們的 Service 實作中切割出來。
在 Model 目錄,新增一個 Interface 檔案,名稱 IContactRepository.vb
''' <summary> ''' 連絡人 Repository Pattern 介面定義 ''' </summary> Public Interface IContactRepository ''' <summary> ''' 取得所有連絡人 ''' </summary> ''' <returns>IQueryable(Of Contact)</returns> Function GetAllContact() As IQueryable(Of Contact) ''' <summary> ''' 透過 id 取得連絡人 ''' </summary> ''' <param name="id">編號</param> ''' <returns>連絡人物件</returns> Function GetContactById(id As Integer) As Contact ''' <summary> ''' 新增連絡人 ''' </summary> ''' <param name="item">連絡人物件</param> ''' <returns>連絡人物件</returns> Function AddContact(item As Contact) As Contact ''' <summary> ''' 刪除連絡人 ''' </summary> ''' <param name="id">編號</param> Sub RemoveContact(id As Integer) ''' <summary> ''' 更新連絡人 ''' </summary> ''' <param name="item">連絡人物件</param> ''' <returns>True 成功更新</returns> Function UpdateContact(item As Contact) As Boolean End Interface
以上定義出我們需要的 CRUD 相關功能介面,然後一樣在 Model 目錄 下新增一個新的類別檔,類別檔名稱 "ContactRepository.vb",此類別將實作 IContactRepository 介面。以下為相關實作:
''' <summary> ''' 實作 IContactRepository 介面 ''' </summary> Public Class ContactRepository Implements IContactRepository ' 存取連絡人清單 Private _contacts As New List(Of Contact)() ' id 指標 Private _nextId As Integer = -1 ''' <summary> ''' 建構式 ''' </summary> ''' <remarks>新增連絡人資料</remarks> Sub New() Me.AddContact(New Contact() With {.Name = "艾利思Alice", .Email = "alice@kkbruce.net"}) Me.AddContact(New Contact() With {.Name = "布魯斯Bruce", .Email = "bruce@kkbruce.net"}) Me.AddContact(New Contact() With {.Name = "查理斯Charles", .Email = "charles@kkbruce.net"}) End Sub ''' <summary> ''' 新增連絡人 ''' </summary> ''' <param name="item">連絡人物件</param> ''' <returns>連絡人物件</returns> Public Function AddContact(item As Contact) As Contact _ Implements IContactRepository.AddContact ' 自動增加 id 編號 item.Id = _nextId + 1 ' 將物件加入至 List 的結尾 _contacts.Add(item) Return item End Function ''' <summary> ''' 取得所有連絡人清單 ''' </summary> ''' <returns>IQueryable(Of Contact)</returns> Public Function GetAllContact() As IQueryable(Of Contact) _ Implements IContactRepository.GetAllContact ' 將 IEnumerable 轉換成 IQueryable 回傳 Return _contacts.AsQueryable() End Function ''' <summary> ''' 透過 id 找尋特定連絡人 ''' </summary> ''' <param name="id">編號</param> ''' <returns>連絡人物件</returns> Public Function GetContactById(id As Integer) As Contact _ Implements IContactRepository.GetContactById ' 並傳回整個 List 內第一個相符的元素 Return _contacts.Find(Function(c) c.Id = id) End Function ''' <summary> ''' 移除連絡人 ''' </summary> ''' <param name="id">編號</param> Public Sub RemoveContact(id As Integer) _ Implements IContactRepository.RemoveContact ' 並傳回整個 List 內第一個相符的元素 Dim contact = _contacts.Find(Function(c) c.Id = id) ' 從 List 移除特定物件的第一個相符項目 _contacts.Remove(contact) End Sub ''' <summary> ''' 更新連絡人 ''' </summary> ''' <param name="item">連絡人物件</param> ''' <returns>Boolean, True 更新成功</returns> Public Function UpdateContact(item As Contact) As Boolean _ Implements IContactRepository.UpdateContact ' 並傳回 List 內或它的一部分中第一個相符元素之以零起始的索引 Dim index As Integer = _contacts.FindIndex(Function(c) c.Id = item.Id) If index = -1 Then Return False End If ' 移除 List 中指定之索引處的項目 _contacts.RemoveAt(index) ' 將物件加入至 List 的結尾 _contacts.Add(item) Return True End Function End Class
實作沒有什麼難處,要處理都是 List(Of Contact) 所提供的 .Add(), .Remove(), .Find() 以進行相關新增、刪除、搜尋動作。另外,還利用 LINQ 的 .AsQueryable() 來將 List 型別轉換,才有辦法以 IQueryable() 來回傳。以上利用 List 來模擬資料庫,或者說,把 List 想像成記憶體裡的資料庫。
新增 Web API Controller
Controllers → New → Controller...
圖一:新增 Controller |
圖二:選擇 Controller Template |
- Controller name: ContactsController
- Template: Empty API controller
接下來,我們就可以撰寫此 HTTP Service 的程式碼,注意命名開始要對應到 HTTP Method。我們先寫下我們的引用等一下會用到的命名空間及第一行程式碼。
Imports System.Web.Http Imports System.Net.Http Imports System.Net Public Class ContactsController Inherits ApiController ' Respository Pattern Shared ReadOnly _repository As IContactRepository = New ContactRepository() End Class
取得資源
取得資源是 Read 與 GET 的對應關係。在連絡人管理中提供了二個 Action,一個是讀取所有連絡人,一個是透過 id 來取得連絡人。這兩個 Action 都定義在 HTTP GET 方法,記得方法必須以 "Get..." 開頭。- GET /api/contacts
- GET /api/contacts/id
' Get /api/contacts ''' <summary> ''' 取得所有連絡人資料 ''' </summary> Function GetAllContacts() As IEnumerable(Of Contact) Return _repository.GetAllContact() End Function
GetAllContacts() 方法會對應至 GET /api/contacts。
' Get /api/contacts/id ''' <summary> ''' 透過 id 取得連絡人資料 ''' </summary> ''' <param name="id">編號</param> Function GetContact(id As Integer) As Contact Dim contact As Contact = _repository.GetContactById(id) If contact Is Nothing Then Throw New HttpResponseException(System.Net.HttpStatusCode.NotFound) End If Return contact End Function
GetContact(id As Integer) 會對應至 GET /api/contacts/id。我們傳入的 id 是字串,但 ASP.NET Web API 會自動轉換傳入的 id 到正確的資料型別 ( Integer )。
我們在 GetContact() 裡還做了錯誤處理,此例外為 System.Web.Http.HttpResponseException 類別,此命名空間必須在 .NET Framework 4.5 Beta 中才會提供。原文解釋:The exception that is thrown to allow for a given HttpResponseMessage to be returned to the client.
新增資源
新增資源是 Create 與 POST 的對應關係。要新增一位連絡人,Client 送出一個 HTTP POST 請求,請求訊息包含新連絡人的相關內容。記得方法必須以 "Post..." 開頭。這裡我們很直覺的寫以下程式碼:
' Add ''' <summary> ''' 新增連絡人 ( 非最後完成實作 ) ''' </summary> ''' <param name="contact">連絡人物件</param> Function PostContact(contact As Contact) As Contact contact = _repository.AddContact(contact) Return contact End Function
預設下,從請求主體(request body)來而的參數解序列化後是複合(complex)型別。因此,我們預期 Client 傳送給我們的是一個經序列化表現的連絡人物件,使用 XML 或 JSON 來序列化。
以上的實作可以運作,但未考慮到兩件事:
- Response code
預設,Web API Framework 設定回傳狀態碼(status code)為 200 (OK)。但按照 HTTP/1.1 協定,當一個 POST 請求會導致資源的建立,伺服器應該回應的狀態碼為 201 (Created)。 - Location當伺服器新增一個資源,它應該在回應的 Location header 中包含新資源的 URI。
ASP.NET Web API 讓你可以很簡易操作 HTTP 回應訊息,我們看改善後的 POST 實作。
' Add ''' <summary> ''' 新增連絡人 ''' </summary> ''' <param name="contact">連絡人物件</param> ''' <returns>System.Net.Http.HttpResponseMessage for .NET Frameowrk 4.5</returns> Function PostContact(contact As Contact) As HttpResponseMessage(Of Contact) contact = _repository.AddContact(contact) ' .NET Frameowrk 4.5 才有提供 ' http://msdn.microsoft.com/en-us/library/system.net.http.httpresponsemessage(v=vs.110).aspx ' Represents a HTTP response message. Dim response = New HttpResponseMessage(Of Contact)(contact, HttpStatusCode.Created) ' 設定路徑的 URL 模式。 Dim uri As String = Url.Route(Nothing, New With {.id = contact.Id}) ' Headers : Gets the collection of HTTP response headers. response.Headers.Location = New Uri(Request.RequestUri, uri) Return response End Function
以上就是做 Response code 與 Location 兩件事。
注意回傳型別是 HttpResponseMessage(Of Contact),HttpResponseMessage(Of T) 型別是一個用強型別表示的 HTTP 回應訊息。泛型參數 T 會取得 CLR 型別然後序列化到訊息主體。
在建構式,我們指定連絡人的執行個體 (contact) 來序列表且傳入 HTTP 狀態碼來回傳:
Dim response = New HttpResponseMessage(Of Contact)(contact, HttpStatusCode.Created)
HttpResponseMessage(Of T) 類別也讓你操作回應標頭(headers),在此範例中,我們設定 Location 標頭在一個新連絡人的 URI。
Dim uri As String = Url.Route(Nothing, New With {.id = contact.Id}) response.Headers.Location = New Uri(Request.RequestUri, uri)
更新資源
更新資源是 Update 與 PUT 的對應關係。更新連絡人作法相當直覺,記得方法必須以 "Put..." 開頭。' Updating ''' <summary> ''' 更新連絡人 ''' </summary> ''' <param name="id">編號</param> ''' <param name="contact">連絡人物件</param> Sub PutContact(id As Integer, contact As Contact) contact.Id = id If Not _repository.UpdateContact(contact) Then Throw New HttpResponseException(HttpStatusCode.NotFound) End If End Sub
UpdateContact() 會回傳 True 或 False,我可以進行判斷是否更新成功。方法中有二個參教,連絡人 id 與 更新的連絡人資料。id 參數會從 URI 路徑取得,連絡人參數是從請求主體解序列化而來。預設,ASP.NET Web API Framework 會從請求主體 (request body)裡的 route 與 複合(complex) 型別來取得簡易的參數型別。
刪除資源
刪除資源是 Delete 與 DELETE 的對應關係。' Deleting ''' <summary> ''' 刪除連絡人 ''' </summary> ''' <param name="id">編號</param> ''' <returns>HttpResponseMessage</returns> Function DeleteContact(id As Integer) As HttpResponseMessage _repository.RemoveContact(id) Return New HttpResponseMessage(HttpStatusCode.NoContent) End Function
依居 HTTP 規範,DELETE 方法必須是 idmpotent(冪等),意味著幾個相同 URI 的刪除請求必須和一個刪除請求有相同效果。因此,如果連絡人已經被刪除,方法應該不能回傳錯誤碼。
如果 DELETE 請求成功,你能回傳狀態 200 (OK) 描述該實體主體(即要刪除的主體)的狀態,或如果刪除持續未處理回傳狀態 202 (Accepted) ,或沒有實體主體回傳狀態 204 (No Content)。我們範例會回傳狀態 204 (No Content)。
高手您好
回覆刪除Function PostContact(contact As Contact) As HttpResponseMessage(Of Contact)
Dim response = New HttpResponseMessage(Of Contact)(contact, HttpStatusCode.Created)
這兩個地方在錯誤清單上會出現
錯誤 1 'System.Net.Http.HttpResponseMessage' 沒有型別參數,因此不可以有型別引數。 C:\Users\it\Documents\Visual Studio 2012\Projects\HelloWebAPI\HelloWebAPI\Controllers\ContactsController.vb 39 72 HelloWebAPI
請指導是哪裡出問題嗎?