開放數據協議(Open Data Protocol【簡稱OData】)是用於Web的數據訪問協議。OData提供了一種對數據集進行CRUD操作(Create,Read,Update,Delete)的統一方式。
Asp.Net Web API支持該協議的v3 和v4版,甚至可以創建一個和v3終結點並排運行的v4終結點。
該博文演示了如何創建支持CRUD操作的OData v4終結點。
用到的軟件版本
- Web API 2
- OData v4
- VS 2013 Update 5
- EF6
- .Net 4.5.2
創建VS項目
在VS中創建一個新的Asp.Net Web應用項目,命名為“PersonsService”,如下圖:

然后繼續看下圖:

安裝OData Nuget包
打開Nuget包管理器控制台,輸入以下命令:
Install-Package Microsoft.AspNet.Odata
該命令會安裝最新版本的OData Nuget 包。
添加Model類
Model類是一個表示應用中的數據實體的對象。
在解決方案資源管理器中的Models文件夾下,創建一個Person類:
按照慣例,model類應該放在Models文件夾下,但是在你自己的項目中可以不這么做。
下面是我的Person類的代碼:
namespace PersonsService.Models
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public bool Gender { get; set; }
public string UserName { get; set; }
}
}
啟用Entity Framework
這篇博客,我們使用EF的Code First模式來創建數據庫。
Web API OData不要求一定得是EF。只要數據訪問層可以將數據庫實體轉換成model,使用任何數據訪問層都可以。
首先,安裝EF的Nuget包。在包管理器控制台中使用下面的命令:
Install-Package EntityFramework
打開Web.config文件,在configuration元素中添加下面的connectionStrings節點:
<connectionStrings>
<add name="PersonsContext" connectionString="Server=.;Database=PersonsDB;Integrated Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
接下來,在Models文件夾下添加一個PersonsContext類:
using System.Data.Entity;
namespace PersonsService.Models
{
public class PersonsContext:DbContext
{
public PersonsContext()
: base("name=PersonsContext")
{
}
public DbSet<Person> Persons { get; set; }
}
}
在構造函數中,"name=PersonsContext"指定了連接字符串的命名。
配置OData終結點
打開App_Start/WebApiConfig.cs文件,配置下面的新代碼(刪除自動生成的代碼):
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using PersonsService.Models;
namespace PersonsService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//新代碼
ODataModelBuilder builder=new ODataConventionModelBuilder();
builder.EntitySet<Person>("Persons");
config.MapODataServiceRoute(
routeName:"odata",
routePrefix:"odata",
model:builder.GetEdmModel()
);
}
}
}
上面的代碼做了兩件事:
- 創建了一個實體數據模型(Entity Data Model【簡稱EDM】)。
- 添加了一個路由。
EDM是一個抽象的數據模型。EDM用於創建服務元數據文檔。ODataConventionModelBuilder類使用默認的命名規范創建了一個EDM。這種方式需要寫的代碼最少。如果你想更多地控制EDM,那么你可以使用 ODataModelBuilder類來創建EDM類,這樣做就要顯式添加屬性,鍵和導航屬性。
路由(route)會告訴Web API如何將HTTP請求路由到終結點。調用MapODataServiceRoute 擴展方法可以創建一個OData v4路由。
如果你的應用有了多個OData終結點,那么要為每個終結點創建一個單獨的路由,給每個路由一個唯一的路由名和前綴(prefix)。
添加OData控制器
控制器是處理HTTP請求的一個類。在OData應用中,應該為每個實體集創建一個單獨的控制器。而在這篇博客中,我們只要為Person實體創建一個控制器就行了。
在Controllers文件夾下添加一個控制器,如下:

在Controllers文件夾上右鍵添加控制器,接下來選中上圖的選擇,因為還沒有針對OData v4的基架。
默認已經幫我們生成了下面的代碼,基本上我們不需要做什么了,CRUD全都有了,呵呵:
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.OData;
using PersonsService.Models;
namespace PersonsService.Controllers
{
/*
在為此控制器添加路由之前,WebApiConfig 類可能要求你做出其他更改。請適當地將這些語句合並到 WebApiConfig 類的 Register 方法中。請注意 OData URL 區分大小寫。
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Extensions;
using PersonsService.Models;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Person>("Persons");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
*/
public class PersonsController : ODataController
{
private PersonsContext db = new PersonsContext();
// GET: odata/Persons
[EnableQuery]
public IQueryable<Person> GetPersons()
{
return db.Persons;
}
// GET: odata/Persons(5)
[EnableQuery]
public SingleResult<Person> GetPerson([FromODataUri] int key)
{
return SingleResult.Create(db.Persons.Where(person => person.Id == key));
}
// PUT: odata/Persons(5)
public IHttpActionResult Put([FromODataUri] int key, Delta<Person> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Person person = db.Persons.Find(key);
if (person == null)
{
return NotFound();
}
patch.Put(person);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!PersonExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(person);
}
// POST: odata/Persons
public IHttpActionResult Post(Person person)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Persons.Add(person);
db.SaveChanges();
return Created(person);
}
// PATCH: odata/Persons(5)
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<Person> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Person person = db.Persons.Find(key);
if (person == null)
{
return NotFound();
}
patch.Patch(person);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!PersonExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(person);
}
// DELETE: odata/Persons(5)
public IHttpActionResult Delete([FromODataUri] int key)
{
Person person = db.Persons.Find(key);
if (person == null)
{
return NotFound();
}
db.Persons.Remove(person);
db.SaveChanges();
return StatusCode(HttpStatusCode.NoContent);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool PersonExists(int key)
{
return db.Persons.Count(e => e.Id == key) > 0;
}
}
}
該控制器借助EF,使用PersonsContext類來訪問數據庫。注意控制器重寫了Dispose方法來釋放 PersonsContext。
生成數據庫
通過Code First的Migration生成數據庫,然后填充數據。關於如何使用CodeFirst生成數據庫不是本節的重點,所以這里一筆帶過。下面是我生成的數據庫已經填充的數據:

查詢實體集
自動生成的查詢操作如下:
// GET: odata/Persons
[EnableQuery]
public IQueryable<Person> GetPersons()
{
return db.Persons;
}
// GET: odata/Persons(5)
[EnableQuery]
public SingleResult<Person> GetPerson([FromODataUri] int key)
{
return SingleResult.Create(db.Persons.Where(person => person.Id == key));
}
無參數的GetPersons()方法會返回整個Person表的集合。
GetPerson([FromODataUri] int key)方法會返回指定Id的Person。
[EnableQuery]特性允許客戶端使用查詢選項(如$filter,$sort和$page)修改查詢。
操作演示


新增實體
允許客戶端將一個新的Person實體添加到數據庫中:
// POST: odata/Persons
public IHttpActionResult Post(Person person)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Persons.Add(person);
db.SaveChanges();
return Created(person);
}
更新實體
OData支持兩種不同語義更新實體,包括PATCH和PUT。
- PATCH執行一個部分更新,客戶端只識別要更新的屬性。
- PUT會替換整個實體。
PUT的劣勢在於客戶端必須發送實體的所有屬性,包括沒有改變的值。
OData說明書陳述了PATCH是首選。
下面是生成的代碼:
// PATCH: odata/Persons(5)
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<Person> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Person person = db.Persons.Find(key);
if (person == null)
{
return NotFound();
}
patch.Patch(person);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!PersonExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(person);
}
在PATCH中,控制器使用了Delta
刪除實體
允許客戶端從數據庫刪除一個Person:
// DELETE: odata/Persons(5)
public IHttpActionResult Delete([FromODataUri] int key)
{
Person person = db.Persons.Find(key);
if (person == null)
{
return NotFound();
}
db.Persons.Remove(person);
db.SaveChanges();
return StatusCode(HttpStatusCode.NoContent);
}
