ASP.NET MVC 4 Web API 開發 (全攻略) [上]

  • 62304
  • 0
  • MVC
  • 2013-02-16

什麼 Web API? 它是基於 WCF 所開發出來的輕量級資料存取技術,在現今,掌上型裝置開始蓬勃發展的現在,如果您是開發人員,您可能不能不了解什麼是 Web API

什麼 Web API? 它是基於 WCF 所開發出來的輕量級資料存取技術,在現今,掌上型裝置開始蓬勃發展的現在,如果您是開發人員,您可能不能不了解什麼是 Web API。它使用了基礎的 HTTP 協定,不過當然,Web API 不見得一定要使用 ASP.NET MVC Web API 來實作,網路上有人使用 PHP 實作 Web API 可參考這個連結:石頭閒語 REST and RESTfull web service-樂多日誌,許多現有的服務如:Amazon、Facebook、Twitter  服務 等等,幾乎是已行之有年了。

如果您走 Microsoft Solution ,那麼您一定要了解 ASP.NET MVC Web API ,這對您以後若要開發 Windows Store App 或是 Windows Phone 8 的應用程式的服務端,這會非常的有用。

首先我們再來複習一下 ASP.NET MVC Web API 的基本特性:

  • 使用 HTTP 就具備的 GET / POST / DELETE / PUT 的 4 個Method 來區別要叫用的方法。

  • 由於建構在 MVC 上,所以完整支援 REST.

  • 支援 OData 什麼是 OData (Open Data Prototol)?詳細說明可參考官方網站 http://www.odata.org/

  • 預設支援 XML 與 JSON 兩種資料格式的輸出.

  • 使用強行別的 HttpResponseMessage 物件.

 

何謂 REST ?

REST 是英文 (Representational State Transfer) 的簡稱,一般簡稱為 REST ,Roy Fielding博士在2000年他的博士論文中提出來的一種軟體架構的開發的方法。目前在三種主流的網頁、Web Service 、實現方案中,因為REST模式的與複雜的SOAPXML-RPC對比來講明顯的更加輕薄,比較適用於使用 3G 頻寬不太大的掌上型裝置、手機等應用來使用。

在 ASP.NET MVC Web API 中會增加屬於 Web API 使用的 Route 定義:

image

與原本的 ASP.NET MVC Web API 事不是都一樣呢!只是他沒有 Action,因為  Web API 的引擎會自動以HTTP 的(GET / POST / DELETE / PUT ) 與傳遞的參數個數來判別要叫用的 "Action" 或說 "方法"。

相關更詳細的關於 REST 的參考可以查看維基百科

 

注意:

這裡要觀念澄清一下,不是使用 Web API 就一定得在 REST 上,只要有一個網路位址可以讓我以 HTTP 的(GET / POST / DELETE / PUT )加以存取都可以一種 Web API,只是微軟也將  Web API 實作於 ASP.NET MVC 上,而 ASP.NET MVC 本來就是走 REST Routing 的架構,所以當然 Web API 使用 REST,而會這麼做的原因也是因為 REST 的優點就是我只要使用 URL 即可清楚地描述我要存取的網路資源,這與輕量級資料存取相符相承,才因此一拍即合。

 

何謂  ASP.NET MVC Web API 路由、基本架構、叫用、取得資料的方式。

在建立一個新的 ASP.NET MVC Web API 專案之中便提供了一個非常基本的範例,在這個範例中提供了 (Get / Post/ Put / Delete ) 這幾個方法,分別對應到 HTTP 裡的在(GET / POST / DELETE / PUT ) 這四個動詞。其中 Get 方法使用了 REST 的參數識別,不傳遞 id 就會回傳所有資料。

這裡筆者將範例稍作修改以方便介紹如何叫用。修改後如下:

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:  using System.Linq;
   5:  using System.Net;
   6:  using System.Net.Http;
   7:  using System.Web.Http;
   8:  
   9:  namespace Mvc4TestApplication1.Controllers
  10:  {
  11:      public class ValuesController : ApiController
  12:      {
  13:          // GET api/values
  14:          public IQueryable<string> Get()
  15:          {
  16:              return new string[] { "value1", "value2" }.AsQueryable();
  17:          }
  18:  
  19:          // GET api/values/5
  20:          public string Get(int id)
  21:          {
  22:              return "value";
  23:          }
  24:  
  25:          // POST api/values
  26:          public void Post([FromBody]string value)
  27:          {
  28:              //寫入資料
  29:              Hashtable db = new Hashtable();
  30:              db.Add(value, value); //Sample for Test
  31:          }
  32:  
  33:          // PUT api/values/5
  34:          public void Put(int id, [FromBody]string value)
  35:          {
  36:              //測試抓取資料
  37:              int Id = id;
  38:              string Value = value;
  39:          }
  40:  
  41:          // DELETE api/values/5
  42:          public void Delete(int id)
  43:          {
  44:              //測試抓取資料
  45:              int Id = id;
  46:          }
  47:      }
  48:  }

 

 

 

 

如上程式,其實筆者只修改了 Post、Put、Delete 增加幾行測試是否能夠接到資料而已,

1. 呼叫 Get() 的方式

預設使用這樣的 URL 即可取得所有資料

http://localhost:14591/api/values

image

如果是使用 jQuery 來取得資料的話我們可能會使用下面類似的方式:

   1:            var WebAPI_Url = "http://localhost:14591/api/values/";
   2:  
   3:          function getAllData() {
   4:              $.getJSON(WebAPI_Url,
   5:              function (data) {
   6:                 //處理資料
   7:                  }
   8:                  
   9:              })
  10:          .fail(
  11:              function (xhr, textStatus, err) {
  12:                  $('#status').html('錯誤: ' + err);
  13:              });
  14:          }
  15:  
  16:          $(document).ready(function () {
  17:              getAllData();
  18:          });

 

 

 

 

 

當然 jQuery 的方式不是只有這一種,但筆者今天想介紹Windows Phone & Store App 等手持裝置如何透過 ASP.NET Web API 來存取資料。

2. 呼叫 Get(int id) 方式

還記得最上面的 WebApiConfig 中的config.Routes.MapHttpRoute 所定義的 REST 格式最後一碼為 id ,因此要叫用 Get(int id) 的方式就是在上面第一個方式的 URL 中後面再加上 id

http://localhost:14591/api/Values/3

程式就會叫用 方法,並傳遞 id = 3 的值進來,如下:

image

 

3. 呼叫 Post([FromBody]string value) 方式

各位會發現,在 Post 方法裡面的 value 參數前面多了 [FromBody] 的屬性,這是表示將該參數設定為必須由表單內傳送進來。要呼叫 Post 方法嚴格來說寫一個簡單的 Form 表單也可以叫用這個 Post 方法,只要 action 指定http://localhost:14591/api/Values 這個 URL.

image

那麼只要你送過去的 HTTP Request Header 為 POST ,那麼就會叫用 Post 方法,如下筆者使用 Fiddler 查看這個 Html Form 進行 Submit 的封包:

image

註:

但是要注意的是 Web API 的動作處理的 Filter 是直接將 Body 內的內容個數當作 C# 方法內的一個參數,也就說他並無法將 Body 內的 input 個數當作參數個數來辦別是要叫用哪一個方法。

 

4. 呼叫 Put(int id, [FromBody]string value) 方式

同上,只是HTTP 的 Method 改為 PUT 方法,且URL 改為 http://localhost:14591/api/Values/5?id=3 ,但這在上面的 Html Form 就無法試驗了。因此筆者使用 Fildder 來測試,如下:

image

需注意的是,Request Body 要使用 =值 的方式,值才會傳入 string value 中。

image

5. 呼叫 Delete(int id) 方式

呼叫 Delete 就更簡單了,使用 REST 格式,最後一個參數傳遞 id ,只是將HTTP 的 Method 改為 DELETE 方法即可叫用成功。

image

 

為什麼實際在開發應用時,較不建議使用 WCF Data Services 來當作主要資料來源?

大約在 2012/9/9 時,筆者有寫過一篇 Windows Store App 開發 [解決無法存取 WCF Data Services 的問題] ,當中筆者介紹了如何在 Windows Store App 中設定使用 WCF Data Services 。不過筆者這裡的重點是為什麼使用 Web API 較理想?因為在WCF Data Services 中所傳遞的資料為 XML/SOAP 格式,資料較為肥大,Web API 可使用 JSON 為主要傳輸格式。因為對於現今掌上型裝置、智慧手機等等所使用的 3G 網路有限的平寬而言,使用 JSON 傳輸會較為理想。

 

那如何設定我的 Web API 只傳回 JSON?

有撰寫過 ASP.NET Web API 的讀者可能會發現,每當使用 Chrome 來瀏覽前面的範例網頁時,都會自動變成 XML 格式,如下:

image

這主要原因是因為 ASP.NET Web API 是使用前端瀏覽器送出的 Accept 標頭的格式內容來決定要輸出什麼格式到用戶端瀏覽器,簡單的說,就是它判斷它支援的格式是不是在用戶端要求的 Accept 標頭之中,如果存在便回應支援的格式,如果不支援,就回應 JSON。問題的解法之前保哥已經有寫過,可參考這個連結 如何讓 ASP.NET Web API 無論任何要求都回應 JSON 格式

 

可以使用 ASP.NET MVC Web API 來傳遞圖片嗎?

可以的!而且沒有很困難,如果你的用戶端是瀏覽器,那麼就像 MVC 的 Controller 寫法一樣,差別只是 Web API 要將 byte[] 以HttpMessageResponse 物件裡的 Content 屬性回傳,只是 Content 是 HttpContent 型態的物件,必須再使用 ByteArrayContent 物件將 byte[] 包起來,如下程式是將 Northwind 資料庫中的 Employees 資料表的 Photo 欄位傳回來:

   1:          public HttpResponseMessage Get(int id)
   2:          {
   3:              HttpResponseMessage response = new HttpResponseMessage();
   4:  
   5:              NorthwindEntities context = new NorthwindEntities();
   6:  
   7:              var result = from Emp in context.Employees
   8:                           where Emp.EmployeeID == id
   9:                           select Emp;
  10:  
  11:              var emp = result.FirstOrDefault();
  12:              if (emp != null)
  13:              {
  14:                  int i = 0;
  15:                  IEnumerable<byte> bs_enum = emp.Photo.Skip(78);
  16:                  byte [] bs = new byte[bs_enum.Count()];
  17:                  foreach (var b in bs_enum)
  18:                  {
  19:                      bs[i] = b;
  20:                      i++;
  21:                  }
  22:              response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); 
  23:              }
  24:              return response;
  25:          }

 

 

 

 

只要直接使用瀏覽器,只要 URL 的 {id} 傳入 EmployeesID 即可回傳圖片,如下:

image

不過如果您想在 Win Form 程式裡使用 HttpClient 物件來存取這個圖片的話,您會得到下面這一個錯誤:

image

這是因為 Web API 提供的 XmlFormatter 的 SuportMediaTypes 就沒有提供這個 image/png 型態,所以無法識別 Stream 是圖片的媒體類型。正確完整的解決方式就是必須撰寫一個 PngMediaTypeFormatter 類別,這可以繼承 MediaTypeFormatter 類別來實作即可。比較麻煩的是 MediaTypeFormatter 裡的 ReadFromStreamAsync 方法,在撰寫的時候筆者發現他的引數中有 HttpContent 物件,那麼我就呼叫這個 HttpContent 物件的 ReadAsStreamAsync()  方法即可,在使用 TaskCompletionSource的 SetResult 設定傳回值即可,詳細的PngMediaTypeFormatter 類別程式碼如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Net.Http.Formatting;
   6:  using System.IO;
   7:  using System.Threading.Tasks;
   8:  using System.Net.Http;
   9:  using System.Drawing;
  10:  
  11:  namespace HttpMediaTypeFormatterLib
  12:  {
  13:      public class PngMediaTypeFormatter : MediaTypeFormatter
  14:      {
  15:          public PngMediaTypeFormatter()
  16:          {
  17:              SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("image/png"));
  18:          }
  19:  
  20:          public override bool CanReadType(Type type)
  21:          {
  22:              return true;
  23:          }
  24:  
  25:          public override bool CanWriteType(Type type)
  26:          {
  27:              return (type == typeof(Stream)) || (type == typeof(byte[]));
  28:          }
  29:  
  30:          public  override Task<object> ReadFromStreamAsync(
  31:              Type type,
  32:              Stream readStream,
  33:              HttpContent content,
  34:              IFormatterLogger formatterLogger)
  35:          {
  36:              var taskSource = new TaskCompletionSource<object>();
  37:              try            
  38:              {
  39:                  var imgstream = content.ReadAsStreamAsync();
  40:                  taskSource.SetResult(imgstream.Result);
  41:              }            
  42:              catch (Exception e)
  43:              {                
  44:                  taskSource.SetException(e);
  45:              }            
  46:              return taskSource.Task;
  47:          }
  48:      }
  49:  }

 

 

 

如上程式,您必須包成 Library 讓Win Form 程式的用戶端也可以參考的到,那麼在 HttpContent 的 ReadAsAsync 方法裡傳入要求的 MediaTypeFormatter ,那 Web API 就可以如預期的知道需要傳回的 image/png 型態的資料。程式修改後如下:

   1:      HttpClient client = new HttpClient();
   2:      client.BaseAddress = new Uri("http://localhost:8000/");
   3:      HttpResponseMessage message = client.GetAsync(txtAPIUri.Text).Result;
   4:      
   5:      try
   6:      {
   7:          message.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
   8:          Stream result = message.Content.ReadAsAsync<Stream>(new PngMediaTypeFormatter[] {new PngMediaTypeFormatter()}).Result;
   9:  
  10:          pictureBox1.Image = new Bitmap(result);
  11:      }
  12:      catch(Exception ex)
  13:      {
  14:          MessageBox.Show(ex.Message);
  15:      }            

 

 

 

如上呼叫端的 Win Form 程式中有使用到 HttpClient 類別,如需要詳細的 HttpClient 類別的用法,可以參考筆者先前撰寫的文章 ASP.NET MVC4 - Web API 開發系列 [從無到有,建立 CRUD 的應用程式]。這個 Win Form 程式的執行結果如下:

image

 

接下來就是重頭戲了,我們要使我們自己的智慧手機來存取自己的 Web API ,當然就得自己開發一個 Web API 的服務端。

 
如何自己開發一個 Web API 的服務端呢? 
首先當然是使用 Visual Studio 2012 先建立一個 ASP.NET Web API 的專案了,並加上 EmpAPIController.cs 與在 Models 資料夾底下增加 DAL.cs & DBConn.cs,在這裡筆者為了相容於公司的內部專案作法,所以使用現有的 DAL 來實作這個 Web API,當然並非無法以 EDM 來實作這個部分,若需要使用 EDM 可以參考保哥文章 ASP.NET Web API 無法輸出 Entity Framework 物件的解法 ,所以在這邊,筆者先不使用過於複雜分層的切割,簡單切出檢視、商業邏輯 與 DAL層。因此這裡 Web API 的輸出資料為 VEmployees 的模型,因此在 BLL 資料夾底下的 BNorthwind.cs 會先實作 GetEmployeesList() 方法,傳回所有 Employees 的資料,提供給 EmpAPIController 使用,筆者這個段程式碼的設計如下:

   1:      public class BNorthwind: BusinessBase
   2:      {
   3:          public IEnumerable<VEmployees> GetEmployeesList()
   4:          {
   5:              DataAccess dal = new DataAccess();
   6:              SqlGenerator generator = new SqlGenerator();
   7:              string Sql = generator.GetSelect(typeof(VEmployees), "Employees");
   8:              try
   9:              {
  10:                  DataTable dtEmp  = dal.Query(Sql).Tables[0];
  11:  
  12:                  var result = from emp in dtEmp.AsEnumerable()
  13:                               select new VEmployees()
  14:                               {
  15:                                   EmployeeID = (int)emp["EmployeeID"],
  16:                                   FirstName = (string)emp["FirstName"],
  17:                                   LastName = (string)emp["LastName"],
  18:                                   Title = (string)emp["Title"],
  19:                                   Address = (string)emp["Address"],
  20:                                   City = (string)emp["City"],
  21:                                   Country = (string)emp["Country"]
  22:                               };
  23:  
  24:                  return result;
  25:              }
  26:              catch (Exception ex)
  27:              {
  28:                  throw ex;
  29:              }
  30:          }

 

 

 

程式非常容易了解,只是將 DAL 傳回的 DataTable 資料再轉為 IEnumerable<VEmployees> 形式的資料再回傳。因此,在 EmpAPIController 中的 Get 的程式碼便非常的精簡,只會有兩行,如下:

image

 

在 VEmployees 的設計部分,在 class 的上方建議使用 [DataConstract] 來宣告,而不要使用   [Serializable] 。因為使用 [Serializable] 的話,輸出的 JSON 資料中會夾雜XML的 TAG,如下:

image

這樣的資料在使用 HttpClient 與 HttpResponseMessage 來取得資料時可能不會察覺出有什麼問題,但是如果你是直接以 DataContractJsonSerializer 物件進行反序列化處裡較容易發生問題。

使用 [DataConstract] 的傳回的 JSON 就會是如下:

image

資料也會比較精簡,不會夾雜些其他的 TAG,如果您想要在其他手機平台上順利的存取時,在反序列化時要比較不會出現問題。

使用 [DataConstract] 的模型物件記得在每一個要公開出來的成員加上 [DataMember(Name="MemberName")],在這個範例中,我的 VEmployees 模型類別具備的欄位如下:

   1:      [DataContract]
   2:      public class VEmployees
   3:      {
   4:          [DataMember(Name = "EmployeeID")]
   5:          public Int32 EmployeeID { get; set; }
   6:  
   7:  
   8:          [DataMember(Name = "LastName")]
   9:          public String LastName { get; set; }
  10:  
  11:  
  12:          [DataMember(Name = "FirstName")]
  13:          public String FirstName { get; set; }
  14:  
  15:  
  16:          [DataMember(Name = "Title")]
  17:          public String Title { get; set; }
  18:  
  19:          [DataMember(Name = "TitleOfCourtesy")]
  20:          public String TitleOfCourtesy { get; set; }
  21:  
  22:          [DataMember(Name = "Address")]
  23:          public String Address { get; set; }
  24:  
  25:          [DataMember(Name = "City")]
  26:          public String City { get; set; }
  27:  
  28:  
  29:          [DataMember(Name = "Region")]
  30:          public String Region { get; set; }
  31:  
  32:  
  33:          [DataMember(Name = "PostalCode")]
  34:          public String PostalCode { get; set; }
  35:  
  36:  
  37:          [DataMember(Name = "Country")]
  38:          public String Country { get; set; }
  39:  
  40:  
  41:          [DataMember(Name = "HomePhone")]
  42:          public String HomePhone { get; set; }
  43:  
  44:  
  45:          [DataMember(Name = "Extension")]
  46:          public String Extension { get; set; }
  47:  
  48:  
  49:          [DataMember(Name = "Notes")]
  50:          public String Notes { get; set; }
  51:  
  52:  
  53:          [DataMember(Name = "ReportsTo")]
  54:          public Int32? ReportsTo { get; set; }
  55:  
  56:          [DataMember(Name = "PhotoPath")]
  57:          public String PhotoPath { get; set; }
  58:      }

 

 

 

 

這時候我們已經可以透過從 WinForm 程式來取得資料了。在 Win Form 裡要使用 HttpClient 或是 WebClient 都可以。使用 WebClient 只需撰寫如下程式:


   1:    WebClient client = new WebClient();

   2:    byte[] bs = client.DownloadData(new Uri("http://localhost:11482/api/EmpAPI"));

   3:    MemoryStream ms = new MemoryStream(bs);

   4:  

   5:    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(IEnumerable<VEmployees>));

   6:    IEnumerable<VEmployees> empList = (IEnumerable<VEmployees>) serializer.ReadObject(ms);

   7:  

   8:    dataGridView1.DataSource = empList;

這個 WinForm 的用戶端程式執行結果如下:

image

由於內容有點多,剩下的我們留到下一次了。

下一篇,筆者將介紹各種不同用戶端開發、並存取這個 ASP.NET MVC Web API 的方式,並存取我們自己開發的 Web API ,會介紹的內容下:

一、使用 Windows Phone 8

二、使用 Windows Store App

三、使用 Windows Phone 7.1

四、使用 Windows Form

 

感謝各位。


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^