【IT168技術(shù)】當(dāng)今的軟件開發(fā)中,設(shè)計軟件的服務(wù)并將其通過網(wǎng)絡(luò)對外發(fā)布,讓各種客戶端去使用服務(wù)已經(jīng)是十分普遍的做法。就.NET而言,目前提供了Remoting,WebService和WCF服務(wù),這都能開發(fā)出功能十分強(qiáng)大的服務(wù)。然而,越來越多的互聯(lián)網(wǎng)應(yīng)用,希望將服務(wù)只是通過HTTP發(fā)布出去,而不是使用復(fù)雜的SOAP協(xié)議。為了解決這個問題,ASP.NET WebAPI就出現(xiàn)了。
ASP.NET API簡單來說就是對REST協(xié)議進(jìn)行了充分的支持,可以使用HTTP中的GET,POST,PUT和DELETE進(jìn)行服務(wù)的發(fā)布。在本文中,將以例子說明如何使用ASP.NET WEB API去創(chuàng)建一個REST風(fēng)格的Web服務(wù),并且如何編寫客戶端對其進(jìn)行連接,此外在文中還將學(xué)會如何通過HTTP協(xié)議,使用Entity Framework和SQL Server去創(chuàng)建一個CRUD(增刪改)的應(yīng)用程序。本文的閱讀對象為具備一定ASP.NET MVC基礎(chǔ)的用戶。
前期準(zhǔn)備
為了繼續(xù)本文的學(xué)習(xí),首先需要安裝ASP.NET MVC4和JSON.net,ASP.NET Web API是ASP.NET MVC 4 BETA版的一部分,可以從下面這個地址下載得到:http://www.asp.net/mvc/mvc4。
此外,還需要下載Json.NET,它是一個在.NET中高性能的對JSON進(jìn)行解析處理的類庫,下載地址在:http://json.codeplex.com/releases/view/82120, 在本文的學(xué)習(xí)中,將會用到Json.net。
創(chuàng)建ASP.NET MVC4 WEB API應(yīng)用
下面,首先打開VS.NET 2010,然后建立一個新的項(xiàng)目,選擇使用C#語言,然后選擇ASP.NET MVC4 WEB APPLICATION,再選擇使用Web API 模板,如下兩個圖所示:
在建立好工程項(xiàng)目后,會發(fā)現(xiàn)項(xiàng)目的目錄結(jié)構(gòu)跟普通的MVC結(jié)構(gòu)是差不多的,但其中會發(fā)現(xiàn)在Controller文件夾中,會發(fā)現(xiàn)多一個ValuesController的類文件,這個類是Web API的控制器類,它繼承了ApiController類,而不是一般MVC框架中的Controller類,代碼如下:
public class ValuesController : ApiController
{
...
}
ValuesController類會自動生成一個基于REST風(fēng)格的腳手架,以方便用戶進(jìn)行編碼,其中提供了GET,POST,PUT和DELETE方法。下面的表格詳細(xì)列出了每個HTTP方法的含義:
要注意的是,Wep API 本身不會指定以上這些方法中每個方法的具體處理流程,比如,對于服務(wù)端的文件系統(tǒng),可以使用XML數(shù)據(jù)或者自定義數(shù)據(jù)存儲格式,從而在這些方法中使用,主要是根據(jù)目標(biāo)的操作類型和數(shù)據(jù)的存儲形式,以上這些方法可以接收所需要的數(shù)據(jù)類型。
在我們繼續(xù)操作前,先在項(xiàng)目的根目錄下創(chuàng)建一個新的名為API的文件夾,并且將ValuesController.cs這個文件放到其中,這樣做的目的,主要是為了將Web API控制器跟普通的MVC控制器進(jìn)行分離,方便今后的開發(fā)。
利用Entity Framework創(chuàng)建數(shù)據(jù)層
現(xiàn)在,往Models文件夾中,新增一個Entity Framework數(shù)據(jù)實(shí)體。我們使用的是Northwind數(shù)據(jù)庫中的Customers表進(jìn)行操作,命名數(shù)據(jù)實(shí)體為Northwind,如下圖:
之后,選擇customer表,創(chuàng)建一個數(shù)據(jù)實(shí)體customer如下:
Web API控制器的編碼
接下來,我們對已經(jīng)生成了框架的Web控制器進(jìn)行完善其中的代碼,代碼如下:
public class CustomersController : ApiController
{
//Select All
public IEnumerable Get()
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
orderby item.CustomerID
select item;
return data.ToList();
}
//Select By Id
public Customer Get(string id)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == id
select item;
return data.SingleOrDefault();
}
//Insert
public void Post(Customer obj)
{
NorthwindEntities db = new NorthwindEntities();
db.Customers.AddObject(obj);
db.SaveChanges();
}
//Update
public void Put(string id, Customer obj)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == id
select item;
Customer old = data.SingleOrDefault();
old.CompanyName = obj.CompanyName;
old.ContactName = obj.ContactName;
old.Country = obj.Country;
db.SaveChanges();
}
//Delete
public void Delete(string id)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == id
select item;
Customer obj = data.SingleOrDefault();
db.Customers.DeleteObject(obj);
db.SaveChanges();
}
}
其中,Get()方法返回了Customers表中的所有數(shù)據(jù),是以LIST列表的形式返回的。customer表中的主鍵為CustomerId列,并且是字符串類型,因此,另外的一個get帶參數(shù)的方法,是根據(jù)傳入的ID參數(shù)返回了某一個customer的對象實(shí)例。
Post()方法則接收一個Customer對象作為參數(shù),并且將其新增到數(shù)據(jù)表中去,同樣,Put()方法中,接收一個customerid,然后在數(shù)據(jù)表中找出該customer對象,為該customer對象的屬性重新賦值,然后再保存;最后Delete方法根據(jù)傳入的CustomerID參數(shù)刪除數(shù)據(jù)表中的數(shù)據(jù)并保存。
從瀏覽器中訪問WEB API
在通過普通頁面作為客戶端訪問Web API前,首先在瀏覽器中通過輸入地址的方法先測試一下,如下圖:
注意的是,訪問API的方式為:localhost/api/customers,在實(shí)際中將要根據(jù)情況替換合適的端口,默認(rèn)所有的WEB API都是通過/api根目錄的方式訪問的,該路由是在Global.asax下進(jìn)行定義,如下:
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
...
}
因此,現(xiàn)在由于我們的WEB API控制器是customers,因此如何要得到某個顧客的信息,可以如下的方式訪問:
/api/customers/ALFKI
創(chuàng)建自定義JSON格式化器
在通過瀏覽器去訪問WEB API時,默認(rèn)的顯示方式是XML。Web API框架會自動根據(jù)訪問客戶端的不同從而返回不同的格式的數(shù)據(jù)?,F(xiàn)在,大多數(shù)情況下,用戶希望返回的格式數(shù)據(jù)是JSON形式。然而,在本文寫作時,使用默認(rèn)的Web API提供的JSON格式化處理器以及Entity Framework搭配工作時,會出現(xiàn)一些小BUG.The entities of the EF data model have IsReference property of DataContractAttribute set to True.EF EF數(shù)據(jù)模型的實(shí)體將DataContractAttribute中的IsReference屬性設(shè)置為true,如下:
...
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Customer : EntityObject
{
...
}
默認(rèn)情況下,Web API使用的是DataContractJsonSerializer類進(jìn)行JSON序列化。但默認(rèn)的JSON序列化類不能處理這樣的實(shí)體類,并且在運(yùn)行期間拋出如下異常:
The type 'WebAPIDemo.Models.Customer' cannot be serialized to JSON because its IsReference setting is 'True'. The JSON format does not support references because there is no standardized format for representing references. To enable serialization, disable the IsReference setting on the type or an appropriate parent class of the type.
為了克服則個問題,可以創(chuàng)建一個自定義的JSON格式化器。幸運(yùn)的是,有第三方的JSON序列化器給我們選擇使用,比如Json.NET。在本文中,將會簡單介紹使用JSON.NET去完成序列化,完整的代碼可以在附件中下載。
一個自定義的序列化器主要是繼承了MediaTypeFormatter的基類。我們編寫的這個JSON序列化類為JsonNetFormatter,在使用前要確認(rèn)你的應(yīng)用工程中已經(jīng)引用了Json.NET的類庫,如下圖:
下面我們來看下JsonNetFormatter的基礎(chǔ)代碼:
public class JsonNetFormatter : MediaTypeFormatter
{
...
}
可以通過重寫MediaTypeFormatter中的一些方法如下:
protected override bool CanReadType(Type type)
{
...
}
protected override bool CanWriteType(Type type)
{
...
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream,
HttpContentHeaders contentHeaders,
FormatterContext formatterContext)
{
...
}
protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream,
HttpContentHeaders contentHeaders,
FormatterContext formatterContext,
TransportContext transportContext)
{
...
}
具體的代碼在附件中可以詳細(xì)查看。一旦創(chuàng)建了JSON序列化器后,就需要告訴Web API框架去替換使用原先默認(rèn)的JSON序列化框架。可以在global.asx中實(shí)現(xiàn):
protected void Application_Start()
{
HttpConfiguration config = GlobalConfiguration.Configuration;
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new IsoDateTimeConverter());
JsonNetFormatter formatter = new WebAPIDemo.Http.Formatters.JsonNetFormatter(settings);
config.Formatters.Insert(0, formatter);
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
BundleTable.Bundles.RegisterTemplateBundles();
}
其中請注意粗體字部分,這里創(chuàng)建了JSON自定義器的實(shí)例,并且增加到以HttpConfiguration類的配制的序列化配制集合中。
這樣,我們就可以開始在客戶端中調(diào)用WEB API,并使用自定義的JSON解析器進(jìn)行處理。
使用jQuery 調(diào)用WEB API
接下來,我們在Index控制器中新建立一個視圖,如下圖:
接下來就可以根據(jù)實(shí)際需要,決定在頁面中顯示customer表中的多少列,最后的頁面顯示如下:
當(dāng)頁面加載時,使用GET()方法去調(diào)出customer表的所有數(shù)據(jù),而當(dāng)使用INSERT,UPDATE,DELETE功能時,是通過jQuery去調(diào)用web api的。下面我們學(xué)習(xí)下通過jQuery去調(diào)用WEB API。
首先,我們設(shè)計每一行的HTML代碼,如下:
<table id="customerTable" border="0" cellpadding="3">
<tr>
<th>Customer ID</th>
<th>Company Name</th>
<th>Contact Name</th>
<th>Country</th>
<th>Actions</th>
</tr>
<tr>
<td><input type="text" id="txtCustomerId" size="5"/></td>
<td><input type="text" id="txtCompanyName" /></td>
<td><input type="text" id="txtContactName" /></td>
<td><input type="text" id="txtCountry" /></td>
<td><input type="button" name="btnInsert" value="Insert" /></td>
</tr>
</table>
首先要引入jQuery類庫:
<script src="../../Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
然后在jQuery中,通過$.getJSON的方法,調(diào)用WEB API,代碼如下:
$(document).ready(function () {
$.getJSON("api/customers", LoadCustomers);
});
熟悉jQuery的朋友肯定明白,$.getJson方法中第一個參數(shù)是調(diào)用服務(wù)的地址,第二個參數(shù)是回調(diào)方法,這個回調(diào)方法LoadCustomers中,將展示服務(wù)端web api返回的數(shù)據(jù),代碼如下:
function LoadCustomers(data) {
$("#customerTable").find("tr:gt(1)").remove();
$.each(data, function (key, val) {
var tableRow = '<tr>' +
'<td>' + val.CustomerID + '</td>' +
'<td><input type="text" value="' + val.CompanyName + '"/></td>' +
'<td><input type="text" value="' + val.ContactName + '"/></td>' +
'<td><input type="text" value="' + val.Country + '"/></td>' +
'<td><input type="button" name="btnUpdate" value="Update" />
<input type="button" name="btnDelete" value="Delete" /></td>' +
'</tr>';
$('#customerTable').append(tableRow);
});
$("input[name='btnInsert']").click(OnInsert);
$("input[name='btnUpdate']").click(OnUpdate);
$("input[name='btnDelete']").click(OnDelete);
}
在上面的代碼中,首先移除所有表格中的行(除了表頭外),然后通過jQuery中的each方法,遍歷web api返回給前端的數(shù)據(jù),最后展現(xiàn)所有的數(shù)據(jù)行。然后在Insert,update,delete三個按鈕中都綁定了相關(guān)的方法函數(shù),下面先看update的代碼:
function OnUpdate(evt) {
var cell;
var customerId = $(this).parent().parent().children().get(0).innerHTML;
cell = $(this).parent().parent().children().get(1);
var companyName = $(cell).find('input').val();
cell = $(this).parent().parent().children().get(2);
var contactName = $(cell).find('input').val();
cell = $(this).parent().parent().children().get(3);
var country = $(cell).find('input').val();
var data = '{"id":"' + customerId + '", "obj":{"CustomerID":"' + customerId +
'","CompanyName":"' + companyName + '","ContactName":"' +
contactName + '","Country":"' + country + '"}}';
$.ajax({
type: 'PUT',
url: '/api/customers/',
data: data,
contentType: "application/json; charset=utf-8",
dataType: 'json',
success: function (results) {
$.getJSON("api/customers", LoadCustomers);
alert('Customer Updated !');
}
})
}
在上面的代碼中,首先從每行的各個文本框中獲得要更新的值,然后組織成JSON數(shù)據(jù),
其數(shù)據(jù)格式為包含兩項(xiàng),其中一項(xiàng)包含customer的ID,另外一個是新的customer實(shí)體對象,因?yàn)閃EB API的PUT方法需要的是兩個參數(shù)。
然后通過jQuery的$.ajax方法進(jìn)行調(diào)用web api,注意這里的type指定為put方法,并且注意編碼為UTF-8,然后在回調(diào)方法success中,再此使用$.getJSON方法,獲得更新后的最新用戶列表。
而Insert,Delete的方法代碼如下:
function OnInsert(evt) {
var customerId = $("#txtCustomerId").val();
var companyName = $("#txtCompanyName").val();
var contactName = $("#txtContactName").val();
var country = $("#txtCountry").val();
var data = '{"obj":{"CustomerID":"' + customerId + '","CompanyName":"' + companyName +
'","ContactName":"' + contactName + '","Country":"' + country + '"}}';
$.ajax({
type: 'POST',
url: '/api/customers/',
data: data,
contentType: "application/json; charset=utf-8",
dataType: 'json',
success: function (results) {
$("#txtCustomerId").val('');
$("#txtCompanyName").val('');
$("#txtContactName").val('');
$("#txtCountry").val('');
$.getJSON("api/customers", LoadCustomers);
alert('Customer Added !');
}
})
}
function OnDelete(evt) {
var customerId = $(this).parent().parent().children().get(0).innerHTML;
var data = '{"id":"' + customerId + '"}';
var row = $(this).parent().parent();
$.ajax({
type: 'DELETE',
url: '/api/customers/',
data: data,
contentType: "application/json; charset=utf-8",
dataType: 'json',
success: function (results) {
$.getJSON("api/customers", LoadCustomers);
alert('Customer Deleted!');
}
})
}
讀者要注意的是,在實(shí)際應(yīng)用中,可以使用含有GET,PUT,DELETE前綴的方法名,比如
GetXXXX(), PutXXXX(), PostXXXX()都是可以的,XXX是自定義的名稱,WEB API框架依然會調(diào)用對應(yīng)的GET,PUT和POST方法。
最后運(yùn)行后,效果如下圖:
WebForm形式調(diào)用WEB API
盡管ASP.NET WEB API是ASP.NET MVC的其中一部分,但并沒規(guī)定只能在ASP.NET MVC架構(gòu)中使用,可以在WebForm中進(jìn)行調(diào)用,方法如下:
我們繼續(xù)在解決方案中新建一個Web Application,然后在應(yīng)用中增加一個普通的asp.net Web Form頁面,然后將之前的API文件夾復(fù)制到這個新的web項(xiàng)目的根目錄中。
然后和之前的步驟一樣,通過Entitiy Framework建立customer實(shí)體類,然后打開Global.ascx,寫入代碼如下:
protected void Application_Start(object sender, EventArgs e)
{
HttpConfiguration config = GlobalConfiguration.Configuration;
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new IsoDateTimeConverter());
JsonNetFormatter formatter = new WebAPIWebFormDemo.Http.Formatters.JsonNetFormatter(settings);
config.Formatters.Insert(0, formatter);
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
}
注意這里做了兩個事情,首先在WEB API框架中注冊了自定義的JSON解析器,然后是注冊了web api 控制器的路由。
然后將前文說到的使用jQuery 調(diào)用web api的所有代碼都復(fù)制到index.aspx(或default.aspx)中去。然后運(yùn)行應(yīng)用,如下圖:
可以看到瀏覽器的地址欄,這里是使用傳統(tǒng)的web form表單進(jìn)行訪問的。
小結(jié)
本文介紹了ASP.NET MVC 4中新推出的符合REST架構(gòu)的WEB API,并以實(shí)際例子講解了如何通過不同的方式調(diào)用web api返回的結(jié)果。本文的附件是代碼,請在這個地址下載:
http://www.developer.com/imagesvr_ce/6193/Code.zip