.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N131014102
出刊日期:2013/10/23
ASP.NET Web API 主要的設計目標是建立一個強固的服務,以HTTP為基礎,是一個很容易設計出REST架構的Framework。搭配Visual Studio 2012開發工具來設計、測試、裝載以及部署以HTTP為基礎的服務(HTTP-based services)。
HTTP定義許多動詞(Verbs,有時也稱HTTP Method) 來處理用戶端的請求,每一種動詞有不一樣的行為。應用在ASP.NET Web API的動詞包含:
- GET:用來取得資源。
- POST:傳送一個Entity到伺服器,由伺服端來處理後續的動作,ASP.NET Web API則應用在資料的新增。
- PUT:在請求傳送一個Entity,取代掉目地URI中既有的Entity。ASP.NET Web API則應用在資料的修改。
- DELETE:刪除請求中指明的Entity。ASP.NET Web API則應用在資料的刪除。
本篇文章介紹如何在Visual Studio 2012設計ASP.NET Web API,以及撰寫用戶端程式碼來存取ASP.NET WEB API。
設計ASP.NET Web API
第一步先建立ASP.NET MVC專案,從Visual Studio 2012開發工具 -「File」-「New」-「Project」,在「New Project」對話盒中選取程式語言,例如本範例選擇「Visual C#」,從「Web」分類中,選取「ASP.NET MVC 4 Web Application」,為專案取一個名稱,然後按下「OK」鍵,請參考下圖所示:

圖 1::ASP.NET MVC 4 Web Application。
建立一個ASP.NET MVC 4應用程式「Web API」範本專案,請參考下圖所示:

圖 2:建立ASP.NET WEB API專案。
路由與Action
建立完ASP.NET WEB API專案之後,在專案的App_Start目錄下,包含一個WebApiConfig.cs檔案,這個檔案中定義了ASP.NET WEB API預設的路由機制,參考程式表列如下:
namespace MyWebAPI {
public static class WebApiConfig {
public static void Register( HttpConfiguration config ) {
config.Routes.MapHttpRoute(
name: "DefaultApi" ,
routeTemplate: "api/{controller}/{id}" ,
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing( );
}
}
}
ASP.NET WEB API路由是利用HttpRouteCollection類別的MapHttpRoute擴充方法來定義。定義時需要描述URI範本(routeTemplate ),以及範本參數的預設值。例如預設有一個名為「DefaultApi」的路由,URI範本為「api/{controller}/{id}」,預設id設定為選擇性的,可以傳遞id參數或省略。
ASP.NET WEB API服務的程式碼設計在一個.NET類別之中(此類別又稱Controller),它是一個繼承自ApiController的類別,當建立ASP.NET Web API範本專案,Controllers目錄中便包含一個ValuesController.cs檔案,定義一個ASP.NET WEB API服務範本程式,ValuesController類別,其中包含Get、Post、Put、Delete等等方法(又稱Action):
namespace MyWebAPI.Controllers {
public class ValuesController : ApiController {
// GET api/values
public IEnumerable<string> Get( ) {
return new string [ ] { "value1" , "value2" };
}
// GET api/values/5
public string Get( int id ) {
return "value";
}
// POST api/values
public void Post( [FromBody]string value ) {
}
// PUT api/values/5
public void Put( int id , [FromBody]string value ) {
}
// DELETE api/values/5
public void Delete( int id ) {
}
}
}
ApiController類別在設計時有一些限制,除了要繼承自ApiController類別之外,類別名稱要以「Controller」結尾。當ASP.NET WEB API接收到用戶端送出的請求,便會檢查請求是否符合路由範本的規則,然後找尋適合的Controller來做後續處理。例如當你執行上步驟建立的專案,在瀏覽器上輸入以下URL:
「http://localhost:8716/api/values」,根據routeTemplate的定義「api/{controller}/{id}」,ASP.NET WEB API便找尋ValuesController類別來做後續處理。
用戶端在發出請求的URL上,不需要指明想要執行的方法(Method,又稱Action),而是根據用戶端發出請求時使用的HTTP Method,叫用ASP.NET WEB API 同名稱(或以HTTP Method開頭)的方法。例如在瀏覽器輸入ASP.NET WEB API所在URL「http://localhost:8716/api/values」,瀏覽器會送出HTTP GET請求到ASP.NET WEB API,因此將會叫用ASP.NET WEB API的Get方法。伺服端則根據用戶端發出的請求標頭來決定要送出XML或JSON資料給用戶端,以下是使用Chrome瀏覽器呼叫時,回傳的XML:

圖 3:以HTTP GET呼叫ASP.NET Web API。
若修改Get方法,將其命名為GetData,也可以照常執行,根據預設的命名原則,只要以HTTP動詞開頭(GET)命名方法,就會自動對應到HTTP GET:
public IEnumerable<string> GetData( ) {
return new string [ ] { "value1" , "value2" };
}
若想要改變為其它名稱不以HTTP動詞開始命名,例如想要將上述GetData方法改名為RetriveData,這時只需要在方法上方套用AcceptVerbs attribute:
[AcceptVerbs( "GET" )]
public IEnumerable<string> RetriveData( ) {
return new string [ ] { "value1" , "value2" };
}
或使用ActionMethodSelectorAttribute ,如HttpGet attribute:
[HttpGet]
public IEnumerable<string> RetriveData( ) {
return new string [ ] { "value1" , "value2" };
}
參數
用戶端若想在發出請求時傳遞參數到伺服端上的ASP.NET WEB API,可以以兩種方式進行:
· Message-URI:在URI上指明參數,通常用來傳遞簡單型別,如bool、int、datetime、string…等。
· Entity-body:參數放在HTTP訊息的body部分,參數可以是複雜型別。
例如在目前的專案執行時,從瀏覽器輸入以下URL:
http://localhost:8716/api/values/0
便會叫用ValuesController,帶一個id參數的Get方法,ASP.NET WEB API會使用parameter bindings功能,將URL上的「0」傳遞到id參數。得到的執行結果如下圖所示:

圖 4:取回一筆資料。
新增模型
現在,讓我們為專案設計模型,以利用Entity Framework進行資料存取。從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「Employee」當作名稱,按「Add」按鈕,請參考下圖所示:

圖 5:新增Employee類別。
為Employee類別加入Id、EmployeeName與Age三個屬性:
namespace MyWebAPI.Models {
public class Employee {
public int Id { get; set; }
public string EmployeeName { get; set; }
public int Age { get; set; }
}
}
按相同方式,從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「EmployeeDB」當作名稱,按「Add」按鈕,請參考下圖所示:

圖 6:新增EmployeeDB類別。
加入以下程式碼,讓EmployeesDB繼承自DbContext類別( 定義在System.Data.Entity命名空間下),以透過Entity Framework Code First技術連接到資料庫。類別中包含一個Employees屬性,存放Entity物件:
namespace MyWebAPI.Models {
public class EmployeesDB : DbContext {
public DbSet<Employee> Employees { get; set; }
}
}
接著再新增一個EmployeesInitializer類別,繼承DropCreateDatabaseAlways<T>類別,改寫Seed方法,此方法用於初始化資料庫資料表的資料:
public class EmployeesInitializer : DropCreateDatabaseAlways<EmployeesDB> {
protected override void Seed( EmployeesDB context ) {
base.Seed( context );
var emps = new List<Employee>{
new Employee {
EmployeeName = "Mary",
Age =30
},
new Employee {
EmployeeName = "Candy",
Age =25
},
new Employee {
EmployeeName = "Lili",
Age =45
}
};
emps.ForEach( s => context.Employees.Add( s ) );
context.SaveChanges( );
}
}
例如範例中,建立三個Employee物件,並將Employee物件新增到DbContext之中,並叫用SaveChanges方法寫回後端資料庫。
最後修改專案根路徑下的Web.Config檔案,預設有一個connectionStrings組態區段:
<connectionStrings>
<add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MyWebAPI-20130924172807;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MyWebAPI-20130924172807.mdf" />
</connectionStrings>
指定name為EmployeesDB,此名稱需和繼承自DbContext類別的EmployeesDB類別名稱一致,這樣Enity Framework就會自動識別,讀取組態檔案中的連接字串連接到資料庫:
<connectionStrings>
<add name="EmployeesDB" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=EmployeesDB;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\EmployeesDB.mdf" />
</connectionStrings>
初始化資料庫
接著,在Global.asax檔案內Application_Start方法中,加入程式碼,叫用Database類別SetInitializer方法來初始資料庫結構和資料表資料:
protected void Application_Start( ) {
AreaRegistration.RegisterAllAreas( );
Database.SetInitializer<MyWebAPI.Models.EmployeesDB>( new MyWebAPI.Models.EmployeesInitializer( ) );
WebApiConfig.Register( GlobalConfiguration.Configuration );
FilterConfig.RegisterGlobalFilters( GlobalFilters.Filters );
RouteConfig.RegisterRoutes( RouteTable.Routes );
BundleConfig.RegisterBundles( BundleTable.Bundles );
}
設計整合Entity Framework的API Controller
在此步驟,我們要建立一個設計整合Entity Framework的API Controller,從Visual Studio 2012開發工具-「Solution Explorer」- 你的專案-「Controllers」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」- 「Controller」。設定控制器名稱為「EmployeesController」;在這個階段Template設定為「API controller with read/write actions, using Entity Framework」,Model Class選擇「Employee」,Data context class選取「EmployeesDB」,然後按下「Add 」按鈕,請參考下圖所示:

圖 7:新增Controller。
當你按下「Add」按鈕之後,Visual Studio 2012會自動產生以下程式碼,利用Entity Framework,完成所有新增、刪除、修改、查詢資料庫資料的Action Method:
namespace MyWebAPI.Controllers {
public class EmployeesController : ApiController {
private EmployeesDB db = new EmployeesDB( );
// GET api/Employees
public IEnumerable<Employee> GetEmployees( ) {
return db.Employees.AsEnumerable( );
}
// GET api/Employees/5
public Employee GetEmployee( int id ) {
Employee employee = db.Employees.Find( id );
if ( employee == null ) {
throw new HttpResponseException( Request.CreateResponse( HttpStatusCode.NotFound ) );
}
return employee;
}
// PUT api/Employees/5
public HttpResponseMessage PutEmployee( int id , Employee employee ) {
if ( !ModelState.IsValid ) {
return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
}
if ( id != employee.Id ) {
return Request.CreateResponse( HttpStatusCode.BadRequest );
}
db.Entry( employee ).State = EntityState.Modified;
try {
db.SaveChanges( );
} catch ( DbUpdateConcurrencyException ex ) {
return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
}
return Request.CreateResponse( HttpStatusCode.OK );
}
// POST api/Employees
public HttpResponseMessage PostEmployee( Employee employee ) {
if ( ModelState.IsValid ) {
db.Employees.Add( employee );
db.SaveChanges( );
HttpResponseMessage response = Request.CreateResponse( HttpStatusCode.Created , employee );
response.Headers.Location = new Uri( Url.Link( "DefaultApi" , new { id = employee.Id } ) );
return response;
}
else {
return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
}
}
// DELETE api/Employees/5
public HttpResponseMessage DeleteEmployee( int id ) {
Employee employee = db.Employees.Find( id );
if ( employee == null ) {
return Request.CreateResponse( HttpStatusCode.NotFound );
}
db.Employees.Remove( employee );
try {
db.SaveChanges( );
} catch ( DbUpdateConcurrencyException ex ) {
return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
}
return Request.CreateResponse( HttpStatusCode.OK , employee );
}
protected override void Dispose( bool disposing ) {
db.Dispose( );
base.Dispose( disposing );
}
}
}
HttpRequestMessage與HttpResponseMessage
ASP.NET WEB API使用HttpRequestMessage類別來表示用戶端傳送過來的HTTP請求;HttpResponseMessage類別則可以用來Action Method回傳的結果,例如回傳HTTP 404狀態碼。你可以利用Request.CreateResponse 或Request.CreateResponse<T>方法來建立HttpResponseMessage。
例如PostEmployee方法建立一個新Employee物件後,需設定location header,並回傳HTTP狀態碼201,因此可利用Request.CreateResponse方法來建立回應。
HttpResponseException
ASP.NET WEB API提供HttpResponseException類別,以丟出例外錯誤給用戶端程式攔截。例如範例中的GetEmployee方法,當找不到對應的Employee物件時,利用HttpResponseException類別丟出例外和HTTP狀態碼。
HTML 用戶端
除了在瀏覽器輸入ASP.NET WEB API所在網址,使用HTTP Get呼叫ASP.NET Web API之外,另一種透過瀏覽器來呼叫的方式是使用HTML FORM,例如我們可以在專案之中加入一個HTML網頁,並加入以HTML標籤,讓使用者以HTTP POST方式傳送請求呼叫ASP.NET WEB API以新增資料到資料庫:
<!DOCTYPE html>
<html xmlns = "
http://www.w3.org/1999/xhtml">
<head>
<title> </title>
</head>
<body>
<form name = "newLocation" action = "api/employees/" method = "post">
<input type = "text" name = "EmployeeName" /> <br />
<input type = "text" name = "Age" /> <br />
<input type = "submit">
</form>
</body>
</html>
當網頁執行時,輸入資料按下「送出查詢」按鈕,就可以送出資料做新增,請參考下圖所示:

圖 8:新增資料。
此時若重新查詢ASP.NET WEB API,資料已被新增到伺服端資料庫了,查詢結果,請參考下圖所示:

圖 9:查詢已新增的資料。
這個網頁產生的POST封包如下:
POST http://localhost:8716/api/employees/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:8716/htmlpage1.html
Accept-Language: zh-Hant-TW,zh-Hant;q=0.8,en-US;q=0.5,en;q=0.3
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Content-Length: 26
DNT: 1
Host: localhost:8716
Pragma: no-cache
EmployeeName=Apple&Age=34
伺服端回應新的location與新建立的資料:
HTTP/1.1 201 Created
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Location: http://localhost:8716/api/employees/3
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVlZWXG5ldG1hZ1xXZWJBUElcTXlXZWJBUElcTXlXZWJBUElcYXBpXGVtcGxveWVlc1w=?=
X-Powered-By: ASP.NET
Date: Thu, 26 Sep 2013 01:54:16 GMT
Content-Length: 41
{"Id":3,"EmployeeName":"Apple","Age":34}
使用HttpClient呼叫ASP.NET WEB API
ASP.NET WEB API提供一組新的用戶端應用程式開發介面,以叫用HTTP服務,相關的命名空間在System.Net.Http.HttpClient命名空間下,透過此命名空間下的HttpClient類別來傳送HTTP請求與回應。HttpClient類別支援Task可以非同步開發模型來叫用服務。
在專案的HomeController類別Index方法中,加入以下程式碼,建立HttpClient物件,BaseAddress指定為ASP.NET WEB API所在網址,然後叫用GetAsync方法,以非同步方式取得服務執行的結果後,從Result屬性取得執行結果:
public ActionResult Index( ) {
HttpClient client = new HttpClient( );
client.BaseAddress = new Uri( "
http://localhost:8716/" );
HttpResponseMessage resp = client.GetAsync( "api/employees" ).Result;
IEnumerable<Employee> data = null;
if ( resp.IsSuccessStatusCode ) {
data = resp.Content.ReadAsAsync<IEnumerable<Employee>>( ).Result;
ViewBag.Result = data.Count<Employee>( );
}
return View( data );
}
範例中使用定義在HttpContentExtensions類別中的ReadAsAsync<T>擴充方法來進行還原序列化,HttpContentExtensions類別的定義位於System.Net.Http.Formatting.dll組件之中,可使用Visual Studio 2012 Nuget管理員,下載Microsoft.AspNet.WebApi.Client套件來取得。
最後利用Count取得回傳的資料筆數,利用ViewBag傳到View:
View
Index View則加入程式如下,從ViewBag取得回傳的資料筆數顯示在畫面上,並利用迴圈將Controller傳至的IEnumerable<MyWebAPI.Models.Employee>中每一個Employee物件的EmployeeName屬性讀出,顯示在無序清單之中:
@using System.Collections.Generic
@model IEnumerable<MyWebAPI.Models.Employee>
<div id="body">
HttpClient執行結果: @ViewBag.Result 筆
<ul>
@foreach (var emp in Model)
{
<li>@emp.EmployeeName</li>
}
</ul>
</div>
此範例執行的結果如下圖所示:

圖 10:呼叫ASP.NET WEB API。
使用AJAX呼叫ASP.NET WEB API - Get
最後我們來探討利用jQeury與AJAX來呼叫ASP.NET WEB API,在HomeController加入以下GetByScript方法,回傳View:
public ActionResult GetByScript( ) {
return View( );
}
將滑鼠游標停留在HomeController GetByScript方法上方按滑鼠右鍵,然後選取快捷選單上的「Add View」選項,並勾選「Use a layout or master page」,請參考下圖所示:

圖 11:產生View。
在新建立的Views\Home\ GetByScript.cshtml中加入以下jQuery程式碼,以HTTP Get呼叫ASP.NET WEB API:
<div id = "body">
<div id = "result"> </div>
</div>
@section scripts{
<script>
$.ajax({
url: '
http://localhost:8716/api/employees',
type: 'GET',
contentType: "application/json;charset=utf-8",
success: function (data) {
var r = "";
for (var i = 0; i < data.length; i++) {
r += "[" +data[i].EmployeeName + "," + data[i].Age +"] " ;
}
$("#result").text(r);
},
error: function (x) {
alert("error");
}
});
</script>
}
此範例執行的結果如下圖所示:

圖 12:以HTTP Get呼叫ASP.NET WEB API。
使用AJAX呼叫ASP.NET WEB API – POST
在HomeController加入以下GetByScript方法,回傳View:
public ActionResult PostByScript( ) {
return View( );
}
將滑鼠游標停留在HomeController PostByScript方法上方按滑鼠右鍵,然後選取快捷選單上的「Add View」選項,並勾選「Use a layout or master page」,按下「Add」按鈕,然後在新建立的Views\Home\ PostByScript.cshtml中加入以下jQuery程式碼,以HTTP POST呼叫ASP.NET WEB API,範例中建立一個emp物件,並將此物件序列化之後,指定在data參數,送到伺服端做新增:
@{
ViewBag.Title = "PostByScript";
}
<h2> PostByScript </h2>
@section scripts{
<script>
var emp = {
EmployeeName: 'Jasper',
Age : 37
};
$.ajax({
url: 'http://localhost:8716/api/employees',
type: 'POST',
data: JSON.stringify(emp),
contentType: "application/json;charset=utf-8",
success: function (data) {
alert("success");
},
error: function (x) {
alert("error");
}
});
</script>
}
此範例執行的結果如下圖所示:

圖 13:以HTTP POST呼叫ASP.NET WEB API
此時若重新查詢資料庫資料便可看到新增的資料:

圖 14:資料已新增到資料庫。
使用AJAX呼叫ASP.NET WEB API - PUT
在HomeController加入以下PutByScript方法,回傳View:
public ActionResult PutByScript( ) {
return View( );
}
將滑鼠游標停留在HomeController PutByScript方法上方按滑鼠右鍵,然後選取快捷選單上的「Add View」選項,並勾選「Use a layout or master page」,按下「Add」按鈕,然後在新建立的Views\Home\ PutByScript.cshtml中加入以下jQuery程式碼,以HTTP PUT呼叫ASP.NET WEB API,範例中建立一個emp物件,指明要修改id為1的Employee資料,並將此物件序列化之後,指定在data參數,送到伺服端做新增:
@{
ViewBag.Title = "PostByScript";
}
<h2> PutByScript </h2>
@section scripts{
<script>
//update
var id =1
var emp = {
Id:id,
EmployeeName: ' new Mary',
Age: 50
};
$.ajax({
url: 'http://localhost:8716/api/employees/' + id,
cache: false,
type: 'PUT',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(emp),
success: function () {
alert('update');
}
});
</script>
}
使用AJAX呼叫ASP.NET WEB API - DELETE
在HomeController加入以下DeleteByScript方法,回傳View:
public ActionResult DeleteByScript( ) {
return View( );
}
將滑鼠游標停留在HomeController DeleteByScript方法上方按滑鼠右鍵,然後選取快捷選單上的「Add View」選項,並勾選「Use a layout or master page」,按下「Add」按鈕,然後在新建立的Views\Home\ DeleteByScript.cshtml中加入以下jQuery程式碼,以HTTP Delete呼叫ASP.NET WEB API,刪除id為1的Employee資料:
@{
ViewBag.Title = "PostByScript";
}
<h2> PutByScript </h2>
@section scripts{
<script>
var id = 1
$.ajax({ type: "DELETE", url: 'http://localhost:8716/api/employees/' +id })
.done(function () {
alert('delete');
});
</script>
}