[WebAPI]實作查詢+新增編輯刪除功能WebAPI 2 + 支援基本OData query + 支援CORS
跟前篇
很類似,但是Routing機制實在方便,值得再花時間重介紹。這次直接順便加入OData(Open Data Protocol)以及動態Linq(Dynamic Linq)。參考Supporting OData Query Options in ASP.NET Web API 2,支援的query options包括:
$expand、$filter、$inlinecount、$orderby、$select、$skip、$top。
然後就開始實務進行
環境:visual studio 2013(無update)
首先新增專案, framework選4.5.1, 以後才支援external authentication
選擇空白專案並且打勾WebAPI選項,不要直接選擇WebAPI專案,那樣的話Visual Studio會加入開發網頁MVC系統相關的元件以及程式碼,都是我們不需要的
然後加入一個ADO.NET ENTITY MODEL去連線資料庫
我們使用的是微軟的AdventureWorks範例資料庫,並且這邊我們加入資料表Contact以及Employee
ps.補充20160226:
edmx加入多個table的時候,容易導致table self referencing,http://stackoverflow.com/questions/19467673/entity-framework-self-referencing-loop-detected
解法有兩種:
1.在Global.asax.cs的app_start()裡面加上下面,執行時才不會有錯誤產生:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
2.如果是比較龐大的專案,則不建議用這種Global的方式去設定Ignore,而是應該採用逐一資料表的partial class去制訂相關MetaData,並在此partial class中,設定需要JSonIgnore的資料表欄位,詳細說明請看Will保哥的ASP.NET Web API 無法輸出 Entity Framework 物件的解法。下面我們將會先用第2點的MetaData去進行。
然後在Models資料夾加入ContactRepository.cs類別, 這個類別將會用EF來存取相關資料表
然後準備加入Employee控制器, 加入之前記得專案全部重新建置一次。然後加入一個WebAPI2 OData Controller,必須先建置一次才會加入成功。此Controller是繼承ODataController而成,但即使現在(20160301)vs2013 update5已經支援ODataV4,我仍覺得此ODataController不甚穩定!!,考慮之後還是決定改繼承基本的API Controller就好。因此加入此OData Controller之後導致順便安裝nuget相關套件之後,馬上就可刪除並重建一個空白的WebAPI Controller!
或可利用本文末提供的packages.config記錄的nuget套件版本,利用nuget的command line,將同樣版本的套件安裝起來。
Install-Package Microsoft.AspNet.WebApi.OData -Version 5.0.0
Install-Package System.Linq.Dynamic -Version 1.0.0
安裝套件成功之後,即使你的Controller繼承的是傳統的api controller,還是可以使用OData套件的穩定部分。
並於ContactRepository加入最基本的query:GetAllContacts,查詢所有聯絡人資料:
AdventureWorksEntities db = new AdventureWorksEntities();
public IQueryable<Contact> GetAllContacts()
{
var contacts = db.Contact.AsQueryable();
return contacts;
}
然後在ContactController這個Class上面加入RoutPrefix, 用來設定使用者要使用此WebAPI的routing網址:
以及在controller引用剛剛新增的GetAllContacts():
[Route("")]
[Queryable(PageSize=20)]
public IQueryable<Contact> GetAllContacts()
{
IQueryable<Contact> contacts = rep.GetAllContacts();
return contacts;
}
再進行更進階的WebAPI查詢方式之前,我們先根據微軟的這篇,測試OData Query:
$expand:除了查詢目標資料的json以外,還可額外查詢到foreign key相關的資料表的資料,是個OData V4限定的指令。雖然這邊控制器繼承的ApiController不支援$expand語法(新版的ODataController才支援$expand),但其實可做到類似的效果,考慮到ODataController現階段不支援資料表欄位DateTime的型態,還是採用較為穩定的ApiController。
不論是ODataController或是APIController的查詢,都需要.edmx產生出來的資料表的類別裡面,有foreign key,才能查到相關連的資料表,而ODataController必須註明$expand語法,在查詢的json結果才會有相關的foreign key資料表,但是在ApiController裡面,只要資料表的類別有註明foreign key資料表,就會自動查詢出來,舉例來說下列自動由edmx產生的Contact.cs類別,在查詢的時候將會一併把Employee的相關資料也查詢出來,因為類別包含有public virtual ICollection<Employee> Employee { get; set; }的定義:
//------------------------------------------------------------------------------
// <auto-generated>
// 這個程式碼是由範本產生。
//
// 對這個檔案進行手動變更可能導致您的應用程式產生未預期的行為。
// 如果重新產生程式碼,將會覆寫對這個檔案的手動變更。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebAPIOData.Models
{
using System;
using System.Collections.Generic;
public partial class Contact
{
public Contact()
{
this.Employee = new HashSet<Employee>();
}
public int ContactID { get; set; }
public bool NameStyle { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Suffix { get; set; }
public string EmailAddress { get; set; }
public int EmailPromotion { get; set; }
public string Phone { get; set; }
public string PasswordHash { get; set; }
public string PasswordSalt { get; set; }
public string AdditionalContactInfo { get; set; }
public System.Guid rowguid { get; set; }
public System.DateTime ModifiedDate { get; set; }
public virtual ICollection<Employee> Employee { get; set; }
}
}
不過這種有foreign key的查詢,仍需要做些設定才能順利進行,如:理論上在postman送出這樣的url字串(http://localhost:45571/api/contact?$top=5&$filter=ContactID eq 1209)就可以查詢出Contact以及相關的Employee資料,但是實際上卻出現了Self table reference的問題如下:
這是因為下列Employee類別有foreign key資料表Contact,而Contact類別也有foreign key參考到Employee,如此一來, 變成無窮循環foreign key。。。
//------------------------------------------------------------------------------
// <auto-generated>
// 這個程式碼是由範本產生。
//
// 對這個檔案進行手動變更可能導致您的應用程式產生未預期的行為。
// 如果重新產生程式碼,將會覆寫對這個檔案的手動變更。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebAPIOData.Models
{
using System;
using System.Collections.Generic;
public partial class Employee
{
public Employee()
{
this.Employee1 = new HashSet<Employee>();
}
public int EmployeeID { get; set; }
public string NationalIDNumber { get; set; }
public int ContactID { get; set; }
public string LoginID { get; set; }
public Nullable<int> ManagerID { get; set; }
public string Title { get; set; }
public System.DateTime BirthDate { get; set; }
public string MaritalStatus { get; set; }
public string Gender { get; set; }
public System.DateTime HireDate { get; set; }
public bool SalariedFlag { get; set; }
public short VacationHours { get; set; }
public short SickLeaveHours { get; set; }
public bool CurrentFlag { get; set; }
public System.Guid rowguid { get; set; }
public System.DateTime ModifiedDate { get; set; }
public virtual Contact Contact { get; set; }
public virtual ICollection<Employee> Employee1 { get; set; }
public virtual Employee Employee2 { get; set; }
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// 這個程式碼是由範本產生。
//
// 對這個檔案進行手動變更可能導致您的應用程式產生未預期的行為。
// 如果重新產生程式碼,將會覆寫對這個檔案的手動變更。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebAPIOData.Models
{
using System;
using System.Collections.Generic;
public partial class Contact
{
public Contact()
{
this.Employee = new HashSet<Employee>();
}
public int ContactID { get; set; }
public bool NameStyle { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Suffix { get; set; }
public string EmailAddress { get; set; }
public int EmailPromotion { get; set; }
public string Phone { get; set; }
public string PasswordHash { get; set; }
public string PasswordSalt { get; set; }
public string AdditionalContactInfo { get; set; }
public System.Guid rowguid { get; set; }
public System.DateTime ModifiedDate { get; set; }
public virtual ICollection<Employee> Employee { get; set; }
}
}
解決方法很簡單,只要建立Contact的MetaData Class就可以把Contact參考到的Employee,利用JsonIgnore屬性把他忽略到,就不會產生self loop問題了,MetaData Class內容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
//name space要故意設定的跟原本的Contact.cs一樣,此MetaData Class才有效喔
namespace WebAPIOData.Models
{
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(ContactMetadata))]
public partial class Contact
{
private class ContactMetadata
{
[JsonIgnore]
public virtual ICollection<Employee> Employee { get; set; }
}
}
}
詳細的MetaData Class說明在此。然後繼續測試其他的odata query語法。
$top:取得前幾筆資料:http://localhost:45571/api/contact?$top=2
$skip:忽略幾筆資料,可以看到ContactID=1的第一筆資料被跳過了:http://localhost:45571/api/contact?$skip=1
$select:只選取JSon結果資料的某某欄位。注意欄位名稱的大小寫有差喔(為了資料顯示只要一筆保持畫面整潔,加上$top=2語法限制只有2筆):
http://localhost:45571/api/contact?$top=2&$select=FirstName,LastName
$orderby:根據欄位作排序。
根據欄位作正向排序asc:
http://localhost:45571/api/contact?$top=2&$select=ContactID,FirstName&$orderby=ContactID
根據欄位作反向排序desc:
http://localhost:45571/api/contact?$top=2&$select=ContactID,FirstName&$orderby=ContactID desc
根據多個欄位作排序:
http://localhost:45571/api/contact?$top=5&$select=FirstName,MiddleName,LastName&$orderby=FirstName,LastName
$inlinecount:測試結果不支援。這是ODataController專用的功能。
$filter:針對欄位作eq(等於), lt(小於), gt(大於), substringof(子字串), day(),month(),year()判斷日期的年月日。。。。。非常多種,全部的用法可參考MSDN,這邊只做幾樣個人覺得常用的測試:
eq(等於):http://localhost:45571/api/contact?$top=5&$select=ContactID,FirstName&$filter=ContactID eq 1 and FirstName eq 'Gustavo'
gt, lt(大於、小於):http://localhost:45571/api/contact?$top=5&$select=ContactID,FirstName&$filter=ContactID gt 1 and ContactID lt 5
startswith(以XX開頭):http://localhost:45571/api/contact?$top=5&$select=ContactID,FirstName&$filter=startswith(FirstName,'C'):
indexof():是否包含此子字串:不存在時,則為-1,字串開頭的第一個字母的位置是0。
indexof(FirstName,'Gus') eq 0:FirstName以Gus開頭的資料抓出來
http://localhost:45571/api/contact?$top=5&$select=ContactID,FirstName&$filter=indexof(FirstName,'Gus') eq 0
indexof(FirstName,'dr') ne -1:FirstName包含dr子字串的抓出來:這個應該算是最常用的語法了,相當於SQL裡面的Like語法
http://localhost:45571/api/contact?$top=5&$select=ContactID,FirstName&$filter=indexof(FirstName,'dr') ne -1
剩下的。。。就有需要再去上面提到的MSDN查即可囉,OData query option的測試到以上這邊結束。
接下來把WebAPI的進階查詢的介紹:
在實務上,查詢的介面下的條件通常都是不固定的,例如:有時候我只想要根據員工姓名查詢某筆資料,有時候可能需要員工編號+員工姓名才能查詢到正確的那筆資料,因此查詢下的條件是不固定的,但是WebAPI查詢資料使用的是EF,因此無法像是純SQL那樣,組成動態SQL去查詢,因此有必要將EF的LINQ查詢,設定為可以動態組合查詢字串去查資料,這裡我們先安裝nuget的套件System.Linq.Dynamic:
並新增一個DLinq.cs類別來動態組合Linq字串:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebAPIOData.Libs
{
public class DLinq
{
//欄位編號
private int ColCount = 0;
//linq字串
public string SQL = "";
//等於或是like
public enum EqualOrLike
{
Equal, Like
};
//加入一個欄位,並設定動態linq字串
public void AddColInfo(string ColName, object ColValue, EqualOrLike eqOrLike)
{
//如果裡面已經有別的欄位條件,就要先加上and
if (SQL != "")
{
SQL += " and ";
}
if (eqOrLike == EqualOrLike.Equal)
{
if (ColValue != null)
{
SQL += ColName + " == " + "@" + ColCount.ToString() + " ";
}
else
{
SQL += " null == @" + ColCount.ToString() + " ";
}
}
else
{
if (ColValue != null)
{
SQL += " " + ColName + ".Contains(@" + ColCount.ToString() + ") ";
}
else
{
SQL += " null == @" + ColCount.ToString() + " ";
}
}
ColCount++;
}
public override string ToString()
{
return SQL;
}
}
}
並加入一個extension method class:Extensions.cs,方便linq字串去引用微軟的System.Linq.Dynamic函式庫:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
//使用動態linq
using System.Linq.Dynamic;
namespace WebAPIOData.Libs
{
public static class Extensions
{
//IQueryable<T>最後回傳的型態T,是根據Client端呼叫WhereDLinq者的型態而定
public static IQueryable<T> WhereDLinq<T>(this IQueryable<T> data, string dLinq, params object[] values)
{
if (dLinq != "")
{
data = data.Where(dLinq, values);
}
else
{
//甚麼都不用做
}
return data;
}
}
}
完成了動態Linq的設定之後就可以開始介紹StaticQuery靜態查詢:
所謂靜態查詢,就是要規定使用者必須輸入某某查詢條件,舉例來說:必須輸入人員代碼ContactID,這種查詢存在的意義,實務上就是要避免使用者一口氣查詢出太多的資料,所以規定使用者必須要輸入一~多個查詢條件。
先在資料存取類別ContactRepository.cs加入函數如下,有把剛剛建立的DLinq類別拿來引用,ContactID就設定為"等於",FirstName, LastName之類的就設定為"Like",這可以用來根據ContactID或是姓名來查詢某個人員:
public IQueryable<Contact> QueryContact(int? ContactID = null, string LastName = null
, string FirstName = null)
{
//設定動態linq字串
DLinq dL = new DLinq();
string abc = dL.ToString();
dL.AddColInfo("ContactID", ContactID, DLinq.EqualOrLike.Equal);
dL.AddColInfo("LastName", LastName, DLinq.EqualOrLike.Like);
dL.AddColInfo("FirstName", FirstName, DLinq.EqualOrLike.Like);
var contacts = db.Contact.WhereDLinq(dL.SQL, ContactID, LastName, FirstName)
.OrderBy("ContactID")
.AsQueryable();
return contacts;
}
透過QueryContact()便可以在Controller裡面實做靜態查詢以及動態查詢。
首先介紹靜態查詢:
在Controller裡面加入method如下:
這樣就可以強制user在呼叫這個api的時候,必須輸入ContactID,並且在查詢不到資料的時候,回傳NotFound 404訊息
[Route("StaticQuery/{ContactID}/")]
[HttpGet]
[Queryable]
public IQueryable QueryContact([FromUri]int ContactID)
{
//簡言之,必傳欄位放在Route("ooxx")裡面,可填可不填欄位放在 Function參數
IQueryable contacts = rep.QueryContact(ContactID, null, null);
if (contacts == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return contacts;
}
並於postman送出url已測試:
http://localhost:45571/api/contact/StaticQuery/10
然後成功得到結果如:
然後介紹動態查詢DynamicQuery:
所謂動態查詢,就是全部查詢條件可填可不填,舉例來說,參數可以全部傳:
http://localhost:45571/api/Contact/DynamicQuery?ContactID=777&LastName=ooxx
有的傳有的不傳也可以:
http://localhost:45571/api/Contact/DynamicQuery?LastName=ooxx
甚麼參數都不傳也可以:
http://localhost:45571/api/Contact/DynamicQuery
在Controller加入此method即可實做動態查詢:
參數中[FromUri]屬性的意思是,必須以?ContactID=ooxx的格式傳送參數
[Route("DynamicQuery/")]
[HttpGet]
[Queryable]
public IQueryable<Contact> QueryContactDynamic([FromUri]int? ContactID = null,
[FromUri]string LastName = null
, [FromUri]string FirstName = null)
{
IQueryable<Contact> contacts = rep.QueryContact(ContactID, LastName, FirstName);
return contacts;
}
然後以下列uri測試動態查詢:
http://localhost:45571/api/Contact/DynamicQuery?ContactID=1206&LastName=Shoop
http://localhost:45571/api/Contact/DynamicQuery?LastName=Shoop
http://localhost:45571/api/Contact/DynamicQuery
並得到以下結果:
得到某一筆人員資料:
得到兩筆員工資料(因為有兩個人都叫做shoop):
得到很多筆資料(因為沒有下任何條件,實務上當然不建議這樣做,因為會效能很差):
然後介紹PostQuery,這是查詢條件中如果有中文的話,最重要的查詢方式:
PostQuery也是動態參數查詢,只是他送出查詢條件的方式是透過http post method。先加入一個類別當作查詢專用的強型別:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebAPIOData.Models.Query
{
public class ContactQuery
{
public int? ContactID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
然後在ContactController加入程式碼如下:
[FromBody]表示使用者送出的參數,都會在Request裡面的body,而不是在網址url裡面
[Route("PostQuery")]
[HttpPost]
public IQueryable<Contact> QueryContactPost([FromBody] ContactQuery contactQuery)
{
IQueryable<Contact> contacts = rep.QueryContact(contactQuery.ContactID,contactQuery.LastName,contactQuery.FirstName);
return contacts;
}
接著執行下列SQL來塞入包含中文姓名的資料:
insert into Person.Contact(NameStyle,Title,FirstName,MiddleName,LastName
,Suffix,EmailAddress,EmailPromotion,Phone,PasswordHash,PasswordSalt,AdditionalContactInfo
,ModifiedDate)
select top 1 NameStyle,Title,N'先生',N'我姓王',N'王'
,Suffix,EmailAddress,EmailPromotion,Phone,PasswordHash,PasswordSalt,AdditionalContactInfo
,GETDATE() from Person.Contact
where ContactID = 1
正規的測試PostQuery的方式應該是新增一個Web表單,不過這裡偷懶一下,就使用postman吧!
下面試透過postman送出post的結果,在postman記得要選擇x-www-form-urlencoded才是正確的模擬。要特別註明一下一點就是,Postman至少要輸入一個參數,讓EmployeeQuery的parser可以解析正確,執行才不會有錯誤。同理,如果user是透過web表單查詢,web表單上至少要設定一個欄位為必填(例如FirstName)方可查詢。除此之外,要查詢中文的時候最建議使用的就是PostQuery,因為如果用前面的Get的方式,中文姓名在網址很可能造成查詢錯誤喔!
下列FirstName以及LastName可以達到像是SQL的Like模糊查詢效果:
因為ContactRepository.cs設定FirstName, LastName為模糊查詢Like的原因,所以才能做到像是SQL的Like模糊查詢的效果:
dL.AddColInfo("LastName", LastName, DLinq.EqualOrLike.Like);
dL.AddColInfo("FirstName", FirstName, DLinq.EqualOrLike.Like);
小小結論:透過動態以及靜態查詢,可自行安排相當彈性的查詢網址。
上面以介紹完畢WebAPI的查詢部分,雖然本文後半段仍有介紹如何利用WebAPI去資料庫作Insert, Update, Delete,但現在插單來先介紹CORS(Cross-Origin-Request),這是WebAPI跨網頁查詢用的很方便的一個nuget套件,首先直接利用nuget直接安裝,搜尋關鍵字打入microsoft.aspnet.webapi.cors即可:
安裝成功之後,在專案路徑App_Start\WebAPIConfig.cs加入這一行以允許專案可使用CORS:
config.EnableCors(); //允許cross origin requests
並於Controller設定要使用CORS(也可逐個method做設定,這邊是直接設定在Controller):
using System.Web.Http.Cors;
//測試階段設定Origin為*, 可讓所有網域做CORS存取此Controller的所有method
[EnableCors(origins: "*", headers: "*", methods: "*")]
//實務階段建議逐個逐個origin設定(多個網域以逗號分隔),以保障安全性
//[EnableCors(origins: "http://mylocalhost:777,http://myserver", headers: "*", methods: "*")]
[RoutePrefix("api/Contact")]
public class ContactController : ApiController
{
}
只是出現了小插曲,按下F5執行的時候,出現組件版本錯誤的訊息,出現在Gloabal.aspx.cs:
這種error是難免會出現,只要安裝額外的nuget套件偶爾都會會碰到,這時候只要回到visual studio,察看下面的錯誤頁籤視窗,會顯示(同一個相依組件的不同版本之間發生衝突),
滑鼠double click這個警告訊息,會出現提示視窗,是否要修正dll參考版本,點選(是)之後,就解決此問題了:
然後就可以開始測試CORS是否可以使用了,傳統的測試方式是建立另外一個Web專案,並加入一個web page且使用jQuery + Ajax的方式去引用這個WebAPI,因為是不同的專案去呼叫WebAPI,自然就是Cross Origin類型的呼叫了,不過這裡偷懶一下,直接使用網站http://test-cors.org/來測試即可:
Remote URL欄位輸入要測試的WebAPI網址,這裡是輸入http://localhost:45571/api/Contact/DynamicQuery?ContactID=1206&LastName=Shoop,然後按下Send Request按鈕。
就會出現Http Status Code=200的成功訊息了:
用fiddler去double check剛剛的request, response,是CORS的Request沒錯:
結果也是的確回傳HttpStatusCode = 200:
然後文章回到WebAPI的Upate, Insert, Delete功能。
下面的新增編輯刪除的範例。。。。。由於並沒有關連到OData Query,所以有點懶的程式碼重寫一次來測了 @@"
接下來介紹Insert:可透過WebAPI新增資料
先新增一個user專門用來新增資料用的人員類別,順便幫EF設定欄位預設值
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace WebAPIDataCenter.Models.Insert
{
public class EmployeeInsert
{
public void SetNullToDefaultValue()
{
string timeString = DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss");
EmployeeID = null;
if(LastName == null)
{
LastName = "LastName not set" + timeString;
}
if(FirstName == null)
{
FirstName = "FirstName not set" + timeString;
}
if(Title == null)
{
Title = "Title not set" + timeString;
}
if(BirthDate == null)
{
BirthDate = Convert.ToDateTime(timeString);
}
if (Address == null)
{
Address = "Address not set " + timeString;
}
if (City == null)
{
City = "City not set " + timeString;
}
if (EmpType == null)
{
EmpType = "EmpType not set " + timeString;
}
}
public int? EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Title { get;set; }
public DateTime? BirthDate { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string EmpType { get; set; }
}
}
然後回到EmployeeRepository.cs新增一個加入資料到資料庫的函數
public void Add(EmployeeInsert inputEmp)
{
//WebAPIEntities db = new WebAPIEntities();
if (inputEmp == null)
{
throw new ArgumentNullException("item");
}
inputEmp.SetNullToDefaultValue(); //給預設值
Employee newEmp = new Employee();
newEmp.FirstName = inputEmp.FirstName;
newEmp.LastName = inputEmp.LastName;
newEmp.Title = inputEmp.Title;
newEmp.BirthDate = inputEmp.BirthDate;
newEmp.Address = inputEmp.Address;
newEmp.City = inputEmp.City;
newEmp.EmpType = inputEmp.EmpType;
db.Employees.Add(newEmp);
db.SaveChanges();
}
然後再Controller加入新增資料的action,新增成功就回傳http status code 201
[Route("Insert")]
[HttpPost]
public HttpResponseMessage InsertNewEmp(Employee emp)
{
if (emp == null)
{
throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = "Employee is not specified" });
}
if (empRepository.CheckEmpExist(emp.LastName, emp.FirstName) == true)
{
return Request.CreateResponse(HttpStatusCode.Conflict, "LastName+FirstName Conflict");
}
empRepository.Add(emp);//如果老是錯在這一行,回去sqlserver資料庫檢查一下是否欄位過長,改完記得update ado.net entity data model
var response = Request.CreateResponse(HttpStatusCode.Created, emp);
string uri = Url.Link("DefaultApi", new { controller = "Employee" });
response.Headers.Location = new Uri(uri + "/Insert");
return response;
}
最後用postman測試,新增成功!
然後實作webapi的update資料
先加入要更新員工資料的複雜型別class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebAPIDataCenter.Models.Update
{
public class EmployeeUpdate
{
public string LastName { get; set; }
public string FirstName { get; set; }
public string City { get; set; }
}
}
然後在Services的empRepository.cs加入更新員工資料的function
public int UpdateEmp(int EmployeeID ,EmployeeUpdate emp)
{
Employee empDb = db.Employees.SingleOrDefault(e => e.EmployeeID == EmployeeID);
int updateCount = 0;
empDb.LastName = emp.LastName;
empDb.FirstName = emp.FirstName;
empDb.City = emp.City;
updateCount = db.SaveChanges();
return updateCount;
}
然後在控制器EmployeeController.cs加入更新員工資料的Action
[Route("Update/")]
[HttpPut]
public HttpResponseMessage UpdateEmployee([FromUri]int EmployeeID, [FromBody] EmployeeUpdate empUp)
{
if (ModelState.IsValid && EmployeeID != 0)
{
int updateCount = 0;
try
{
updateCount = empRepository.UpdateEmp(EmployeeID, empUp);
}
catch
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
然後用postman測試, 根據http 1.1 ,記得方法要選put, 更新資料成功,並回傳代碼200
再補充一點,如果不是用iis express, 而是使用iis7.5的話,倘若很巧的
你有安裝iis的WebDAV,記得從windows新增移除程式把他移除掉
因為WebDAV會導致你Put, Delete失敗!!
詳情請看
How to enable PUT and DELETE verbs on IIS 7
然後實作如何用WebAPI達成刪除員工資料
先於資料存取的類別Services\EmployeeRepository.cs加入刪除的語法
public Boolean DeleteEmp( int EmployeeID)
{
Boolean delOK = false; //是否刪除成功
int delCount = 0; // 刪除成功的筆數
Employee emp = db.Employees.SingleOrDefault(e => e.EmployeeID == EmployeeID);
db.Employees.Remove(emp);
delCount = db.SaveChanges();
if(delCount == 1)
{
delOK = true;
}
else
{
delOK = false;
}
return delOK;
}
然後於Controller加上刪除的ACtion,然後就可以測試了
[Route("Delete/{EmployeeID}/")]
[HttpDelete]
public HttpResponseMessage DeleteEmp([FromUri] int EmployeeID)
{
if (ModelState.IsValid && EmployeeID != 0)
{
try
{
empRepository.DeleteEmp(EmployeeID);
}
catch
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
用postman測試成功!!記得方法要選delete, 回傳http status code 200
補充:
實務上JSon比起XML的佔用大小更小,現在這個手機通訊盛行的年代
能夠把資料傳輸量壓在最低是最好的,所以下面幾行在設定檔設定一下限定使用JSon
就可以讓WebAPI限定只回傳JSon格式,不會因為瀏覽器的不同,造成回傳格式的不同
在WebApiConfig.cs(在App_Start資料夾)加上下面,可以限制回傳是JSon格式
// 預設傳回 JSON 格式.
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(
t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
補充:
如何使用上面建立的WebAPI,請參考
[WebAPI]如何建立WebAPI Client(jQuery)
補充20160223:
後續更新的WebAPI+OData的版本(例如ODataV4),程式碼的寫法有蠻大的不同,如果沒有最新版本的OData的需求,
建議nuget安裝與此示範文章相同版本的套件即可,下面是所有此專案的安裝套件:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.0.0" targetFramework="net451" />
<package id="EntityFramework.zh-Hant" version="6.0.0" targetFramework="net451" />
<package id="Microsoft.AspNet.Cors" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi" version="5.0.0" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.Client.zh-Hant" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.Core.zh-Hant" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.Cors" version="5.1.2" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.OData" version="5.0.0" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.0.0" targetFramework="net451" />
<package id="Microsoft.AspNet.WebApi.WebHost.zh-Hant" version="5.0.0" targetFramework="net451" />
<package id="Microsoft.Data.Edm" version="5.6.0" targetFramework="net451" />
<package id="Microsoft.Data.OData" version="5.6.0" targetFramework="net451" />
<package id="Newtonsoft.Json" version="5.0.6" targetFramework="net451" />
<package id="System.Linq.Dynamic" version="1.0.0" targetFramework="net451" />
<package id="System.Spatial" version="5.6.0" targetFramework="net451" />
</packages>
參考資料:
Build RESTful API's with ASP.NET Web API
How to use “contains” or “like” in a dynamic linq query?
[Web API] 讓 Web API 支援 OData 查詢
Attribute Routing in Web API 2
Create a REST API with Attribute Routing in Web API 2