在這篇文章中我們將討論Web API設計、概念、功能,和對比Web API與WCF。
1. 實現一個Web API項目
我們從一個簡單的執行個體開始討論。我們使用 Visual Studio 2012做為開發環境。我們第一步是基於Web API模版建立一個ASP.NET MVC 4.0項目,如圖1所示: 圖1:基於Web API模板建立一個ASP.NET MVC 4項目
下一步,我們在model檔案夾建立一個簡單的資料模型。在解決方案資源檔夾中右鍵點擊Model檔案夾,選擇 Add -> Class,如圖2所示:
圖2:添加新的資料模型
這隻是一個教程,我定義一個產品資料模型,如表1所示。
public class Product{public int Id{ get; set; }public string Name{ get; set; }public string Category{ get; set; }public decimal Price{ get; set; }}
表1: 定義產品模型
建立產品資料模型後,我們可以在Controllers檔案夾中建立一個新的API控制器,用來處理產品資料模型。預設情況下,一個基於Web API模版的MVC項目可以添加兩個控制器: 一個繼承於Controller類,另一個繼承於ApiController類。在解決方案資源檔夾中右擊Controllers檔案夾,在快顯視窗中選擇“Empty API controller”,添加一個新的controller。如圖3所示:
圖3:添加Empty API controller
Controller代碼如表2所示:
public class ProductsController : ApiController { List<Product> products = new List<Product>(); public IEnumerable<Product> GetAllProducts() { GetProducts(); return products; } private void GetProducts() { products.Add(new Product {Id = 1, Name = "Television", Category="Electronic", Price=82000}); products.Add(new Product { Id = 2, Name = "Refrigerator", Category = "Electronic", Price = 23000 }); products.Add(new Product { Id = 3, Name = "Mobiles", Category = "Electronic", Price = 20000 }); products.Add(new Product { Id = 4, Name = "Laptops", Category = "Electronic", Price = 45000 }); products.Add(new Product { Id = 5, Name = "iPads", Category = "Electronic", Price = 67000 }); products.Add(new Product { Id = 6, Name = "Toys", Category = "Gift Items", Price = 15000 }); } public IEnumerable<Product> GetProducts(int selectedId) { if (products.Count() > 0) { return products.Where(p => p.Id == selectedId); } else { GetProducts(); return products.Where(p => p.Id == selectedId); } }
表2:添加ApiController類到執行個體項目
運行項目並串連到API,通過“/api/Products”,例如: http://localhost:8747/api/Products 。你也可以傳遞 SelectedId 參數去調用GetProducts方法,例如: http://localhost:8747/api/Products?SelectedId=2
2. 傳遞複雜的對象給Web API的方法
在Web API中傳遞一個簡單的對象是容易的,但是大部分情況下,你需要傳遞一個複雜的對象參數。你可以通過[FromUrl]或[FromBody]屬性來傳遞對象。[FromBody]屬性從request body裡面讀取資料。但是,這個屬性在方法的參數列表中只能使用一次。
讓我們用程式碼片段去解釋使用複雜參數的重要性,如表3所示:
public class SampleController : ApiController { /// <summary> /// Get time /// </summary> /// <param name="t"></param> /// <returns></returns> public string GetTime(Time t) { return string.Format("Received Time: {0}:{1}.{2}", t.Hour, t.Minute, t.Second); } } public class Time { public int Hour { get; set; } public int Minute { get; set; } public int Second { get; set; } }
表3: 給方法 傳遞複雜的參數
現在,讓我們使用Hour,Minute和Second參數給GetTime方法傳值。你可以看見它出錯了,返回Null 參考錯誤。如圖4所示:
圖4:當調用GetTime方法時出錯了
雖然我們已經意識到輸入值需要關聯到Time對象,但是API方法不能 正確地 映射輸入值與對象屬性。現在我們看看,是否修改代碼去包含[FromUri]屬效能夠處理URL查詢字串裡的複雜物件。下面的片段中我們在Time對象前申明了[FromUri]屬性:
public string GetTime([FromUri] Time t) { -------------------}
現在我們通過這個方法,傳遞相同的值,將會有不同的結果,如圖5所示:
圖5:調用GetTime方法成功
3. 使用HttpClient API HttpClient是一個可擴充API,可以通過Http方式訪問任何服務或網站。這個HttpClient被引進到WCF Web API, 而且現在 .NET Framework 4.5中的 ASP.NET Web API也有了這個功能。你可以在後台和services(例如:WCF)中使用HttpClient去訪問Web API方法。
這個程式碼片段建立了一個HttpClient對象並且非同步訪問執行個體中的API方法。
// asynchronous accessing of web api method async Task GetData() { StringBuilder result = new StringBuilder(); // Define the httpClient object using (HttpClient client = new HttpClient()) { // Define the base address of the MVC application hosting the web api // accessing the web api from the same solution client.BaseAddress = new Uri(HttpContext.Current.Request.Url.AbsoluteUri); // Define the serialization used json/xml/ etc client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); // Call the method HttpResponseMessage response = client.GetAsync("api/products").Result; if (response.IsSuccessStatusCode) { // Convert the result into business object var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result; foreach (var p in products) { result.Append(string.Format("{0} --- [Price: {1} Category: {2}] ", p.Name, p.Price, p.Category)); } } else { result.Append(string.Format("Error Code:-{0} Error Details: {1}", (int)response.StatusCode, response.ReasonPhrase)); } } data.Text = result.ToString(); }
表4:建立一個HttpClient對象去訪問執行個體中的API方法
4. 用jQuery訪問Web API 在Html5 Web應用中,你可以使用jQuery直接存取Web API。你可以使用getJSON()方法調用API得到結果,然後取出業務對象。jQuery能識別內部欄位關聯的業務對像,例如:data[i].Price。
這個程式碼片段示範了從Product API方法尋找資料並顯示資料到一個表格。
<head> <title></title> <script src="Scripts/jquery-1.8.2.js"></script> <script type="text/javascript"> $.getJSON("api/products", function (data) { //Clear the div displaying the result $("#productView").empty(); //Create a table and append the table body var $table = $('<table border="2">'); var $tbody = $table.append('<tbody />').children('tbody'); //data - return value from the Web API method for (var i = 0; i < data.length; i++) { //add a new row to the table var $trow=$tbody.append('<tr />').children('tr:last'); //add a new column to display Name var $tcol = $trow.append("<td/>").children('td:last') .append(data[i].Name); //add a new column to display Category var $tcol = $trow.append("<td/>").children('td:last') .append(data[i].Category); //add a new column to display Price var $tcol = $trow.append("<td/>").children('td:last') .append(data[i].Price); } //display the table in the div $table.appendTo('#productView'); }); </script></head><body> <div id="productView"></div></body></html>
表5:使用jQuery從Product API方法尋找資料
5. 在jQuery中傳遞參數 另外,從Web API方法尋找資料,你可以使用jQuery去傳遞參數。jQuery支援多個方法傳遞參數。第一個方法是使用單個的參數。下面的程式碼片段示範了如何去傳遞單個參數:
$.getJSON("api/products", { selectedId: '4' }, function (data) { ….});
你也可以在一個集合中傳遞多個參數,示範如下:
$.getJSON("api/products", { selectedId: '4', name: 'TV' }, function (data) { ….});
另外一個方法,你可以直接把參數放在URL中,示範如下:
$.getJSON("api/products?selectedId =4", function (data) { ….});
第一個方法從URL中分開了查詢字元和參數,這個適用於複雜的或多個資料傳遞。第二個方法更適用於一個或兩個參數的情況。
6. Web API代碼架構 ASP.NET Scaffolding是一個ASP.NET的Web應用代碼產生架構。Visual Studio包含預先安裝代碼產生器,用於MVC和Web API項目。Visual Studio自動產生需要的API代碼在指定的資料模型和資料來源中執行多種操作。
基於代碼架構的Entity Framework 驗證Web API項目中代碼架構如何工作,我們可以使用Entify Framework去定義我們的資料庫並且產生Web API方法。首先,我們需要添加一個ADO.NET entity資料模型,用來定義資料。我們的執行個體資料將基於兩張表,Couse和Status,如圖6所示:
圖6:基於Course和Status表添加一個entity資料模型
定義資料模型後,我們可以產生與資料模型一致的Web API controller。右擊Controller,然後點擊Add Controller,將會彈出Add Controller視窗。在Controler name的輸入框,輸入Controller name。從Template下拉式清單中,選擇API controller with read/write actions, using Entity Framework模板,如圖7所示:
圖7:基於資料模型添加一個Controller
下一步,從Model class下拉式清單中選擇一個表,在Data context class下拉式清單中指定資料context。表6展示的是CourseController API controller自動產生的程式碼。它包含了get,put,post,和delete方法,這些方法和model一致。
public class CourseController : ApiController{ private SampleDBEntities db = new SampleDBEntities(); // GET api/Course public IEnumerable<Course> GetCourses() { return db.Courses.AsEnumerable(); }// GET api/Course/5 public Course GetCourse(int id) { Course course = db.Courses.Single(c => c.CourseId == id); if (course == null) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } return course;}// PUT api/Course/5 public HttpResponseMessage PutCourse(int id, Course course) { if (!ModelState.IsValid) {return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);}if (id != course.CourseId){return Request.CreateResponse(HttpStatusCode.BadRequest);}db.Courses.Attach(course);db.ObjectStateManager.ChangeObjectState(course, EntityState.Modified);try{db.SaveChanges();}catch (DbUpdateConcurrencyException ex){return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);}return Request.CreateResponse(HttpStatusCode.OK);}// POST api/Coursepublic HttpResponseMessage PostCourse(Course course){if (ModelState.IsValid){db.Courses.AddObject(course);db.SaveChanges();HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, course); response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = course.CourseId }));return response;}else{return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);}}// DELETE api/Course/5public HttpResponseMessage DeleteCourse(int id){Course course = db.Courses.Single(c => c.CourseId == id);if (course == null) {return Request.CreateResponse(HttpStatusCode.NotFound);}db.Courses.DeleteObject(course);try{db.SaveChanges();}catch (DbUpdateConcurrencyException ex){return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);}return Request.CreateResponse(HttpStatusCode.OK, course);}protected override void Dispose(bool disposing){db.Dispose();base.Dispose(disposing);}}
表6:CourseController API controller產生的程式碼
Web API代碼架構功能減少了開發人員對常用增刪改查功能的操作步聚。
7. ASP.NET Web API路由規則
Web API使用統一的路由選取器(URIs)實現多種行為。在解決方案資源檔夾的App_Start檔案夾有一個WebApiConfig檔案,它為Web API定義了不同的路由機制。這個機制基於http方法、動作和屬性。但是,我們可以定義我們自己的路由機制來支援自訂路徑。
例如,在我們的Web API項目執行個體中,我們使用/api/Products去接收產品詳情。但是,我們想用api/Products/GetAllProducts替換,可以修改預設的路由邏輯,需要在URI中包含{action},代碼片斷如下:
config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{action}/{id}",defaults: new { id = RouteParameter.Optional });
更新規則後,我們可以在Web API方法上使用ActionName屬性去指定action名字,代碼片斷如下:
[ActionName("GetAllProducts")]public IEnumerable<Product> GetAllProducts(){……………………………..}
注意:使用[NonAction]屬性可以指明Web API方法不是一個API action方法。
我們可以使用一個Http方法或Acceptverbs屬性去指定用何種HTTP動作串連Web API方法。下面的代碼片斷使用了HttpGet和HttpPost方法:
[HttpGet]public IEnumerable<Product> GetProducts(int selectedId){………….. }[HttpPost]public void AddProduct(Product p){………….. }
另一種方式,使用Acceptsverbs屬性,如下代碼斷:
[AcceptVerbs("GET","PUT")]public IEnumerable<Product> GetProducts(int selectedId){………….. }
Web API 2提倡儘可能使用屬性。更多詳情請查看檔案: Attribute Routing in Web API v2。
8. 序列化
Web API支援很多序列化方法,包括XML,JSON,和MessagePack。在下面的章節,我們將研究下如何去執行這三個方法,並比較他們。在學習之前,我們修改Web API項目的執行個體代碼去返回大塊的資料,這樣我們可以更好的理解和比較序列化的作用。表7展示如何用GetProducts()方法返回大塊資料。
private void GetProducts() { for (int i = 0; i < 5000; i++) { products.Add(new Product { Id = i, Name = "Product - "+i, Category = "The ASP.NET and Visual Web Developer teams have released the ASP.NET and Web Tools 2012.2 update, which extends the existing ASP.NET runtime and adds new web tooling to Visual Studio 2012. Whether you use Web Forms, MVC, Web API, or any other ASP.NET technology, there is something cool in this update for you.", Price = 1 }); } }
表7:更新GetProducts()方法
9. 使用JSON序列化
Web API使用JSON做為預設的序列化方法。這樣,當你運行Web API項目,自動返回為JSON格式。例如,接收Products詳情,你只需要使用 http://<;<server>>:<<port>>/api/Products去得到JSON格式的結果。
使用XML序列化 在Web API項目中使用XML序列化,你必須修改Global.aspx檔案在Application_Start()方法插入下面兩行代碼:
GlobalConfiguration.Configuration.Formatters.RemoveAt(0);GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
執行XML序列化方法後,你可以使用 http://<;<server>>:<<port>>/api/Products查看詳情。
使用MessagePack序列化 要使用 MessagePack方法,你必須先安裝 MessagePack(http://msgpack.org/)。 MessagePack可通過Nuget添加和管理。你也可以從GitHub下載源碼,並在Visual Studio中產生應用。
當你安裝了MessagePack,把MsgPack.dll引用到MessagePack library。下一步,建立一個自訂的媒體類型格式,這個格式用來序列化Web API資料。表8是GitHub上展示的媒體類型格式 ( https://gist.github.com/filipw/3693339 ) 。
public class MediaTypeFormatterCompatibleMessagePack : MediaTypeFormatter { private readonly str