[WebAPI]實作查詢+新增編輯刪除功能WebAPI 2 + 支援基本OData query + 支援CORS

  • 5322
  • 0
  • 2016-03-24

[WebAPI]實作查詢+新增編輯刪除功能WebAPI 2 + 支援基本OData query + 支援CORS

跟前篇

[WebAPI]實作基本查詢功能的WebAPI

很類似,但是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)

[WebAPI]如何建立WebAPI Client(C#)

補充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>

 

參考資料:

[WebAPI]實作基本查詢功能的WebAPI

ASP.NET Web API 參數繫結

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

Creating an OData Endpoint in ASP.NET Web API 2

Dynamic LINQ 讓 LINQ 的世界變的更美好

關於IQueryable<T>特性的小實驗

Enabling Cross-Origin Requests in ASP.NET Web API 2