問題
怎樣用在 Web API 中創建 OData 服務。
解決方案
對於我們來說,在 Web API 中使用 OData最簡單的方式就是使用 ASP.NET 模板來創建Odata Controller。在 Controllers 文件夾上鼠標右鍵->添加->新建項。
顯示一個如圖 12-1 的對話框,在這裡我們可以選擇兩個 “Web API 2 OData” 相關的模板。Vistual Studio將會生成相關的 OData Controller,同時,從 NuGet 上下載 OData 需要的所有程序集。
圖 12-1. 使用模板添加 OData Controller
不過,這個模板僅僅對於 WEB Host (ASP.NET Web API 托管在 ASP.NET Web 應用程序中)是可以用。對於 Web API 托管在其他地方,我們可以通過 NuGet 手動安裝 OData Microsoft.AspNet.OData 來開啟我們的OData 開發之旅。
工作原理
OData 是一種通過 HTTP 公開豐富 API的標准化協議。OData 4.0 已經被 OASIS 國際開放標准聯盟批准,也被認為是 Web 界的 ODBC。
Open Data Protocol(OData )可以創建基於REST 的數據服務,可以是資源,使用 URL 和定義的數據模型,可以通過 Web 客戶端使用簡單的 HTTP 消息來發布和編輯。
OData 4.0
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part1-protocol.html
小提示 OData 主頁 www.odata.org 裡面有所有感興趣的資源,他可以幫助我們了解 Odato 協議。
ASP.NET WWB API 2.2 支持 OData 4.0(Microsoft.AspNet.OData NuGet 包),然而,之前的 Web API 支持的OData 3.0。如果我們要是指定引用 Mircosoft.Aspnet.WebApi.OData NuGet 包,還是可以使用 OData 3.0。
OData Controller 應該繼承自 ODataController 基類,而不是常規的ApiController。ASP.NET Web API 允許我們在一個項目中混合使用 OData 的Controller 和 傳統的 Controller,所以,我們可以在提供 OData Api 的同時提供常規 Api。
Controller 繼承 ODataController 是有框架進行不同配置的。被稱為 ODataActionSelector 的 Odata IHttpAcionSelector 的實現類,是基於 Odata 路由的約定,以及一組特定的媒體類型格式化也是被默認替換的。所有的 OData 格式化程序都是 ODataMediaTypeFormatter 的變種,他可以處理 OData 指定的請求和相應格式,XML 和 JSON。
代碼演示
清單 12-1 展示了一個完成的功能,而且很典型的 ODataController 的 CRUD。在這樣的情況下,會通過ASP.NET 的模板根據 Player 實體和 EF 數據上下文生成 Controller。
清單 12-1 典型的 ODataController
1 2 3 4 5 6 7 8 9namespace
BoiledCode.WebApi.Recipe.ODataDemo.Models
{
public
class
Player
{
public
int
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
Team {
get
;
set
; }
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
using
System.Data.Entity.Infrastructure;
using
System.Linq;
using
System.Net;
using
System.Web.Http;
using
System.Web.OData;
using
BoiledCode.WebApi.Recipe.ODataDemo.Models;
namespace
BoiledCode.WebApi.Recipe.ODataDemo.Controllers
{
/*
The WebApiConfig class may require additional changes to add a route for this controller. Merge these statements into the Register method of the WebApiConfig class as applicable. Note that OData URLs are case sensitive.
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using BoiledCode.WebApi.Recipe.ODataDemo.Models;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Player>("Players");
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
*/
public
class
PlayersController : ODataController
{
private
readonly
ApplicationDbContext db =
new
ApplicationDbContext();
// GET: odata/Players
[EnableQuery]
public
IQueryable<Player> GetPlayers()
{
return
db.Players;
}
// GET: odata/Players(5)
[EnableQuery]
public
SingleResult<Player> GetPlayer([FromODataUri]
int
key)
{
return
SingleResult.Create(db.Players.Where(player => player.Id == key));
}
// PUT: odata/Players(5)
public
IHttpActionResult Put([FromODataUri]
int
key, Delta<Player> patch)
{
Validate(patch.GetEntity());
if
(!ModelState.IsValid)
{
return
BadRequest(ModelState);
}
var
player = db.Players.Find(key);
if
(player ==
null
)
{
return
NotFound();
}
patch.Put(player);
try
{
db.SaveChanges();
}
catch
(DbUpdateConcurrencyException)
{
if
(!PlayerExists(key))
{
return
NotFound();
}
throw
;
}
return
Updated(player);
}
// POST: odata/Players
public
IHttpActionResult Post(Player player)
{
if
(!ModelState.IsValid)
{
return
BadRequest(ModelState);
}
db.Players.Add(player);
db.SaveChanges();
return
Created(player);
}
// PATCH: odata/Players(5)
[AcceptVerbs(
"PATCH"
,
"MERGE"
)]
public
IHttpActionResult Patch([FromODataUri]
int
key, Delta<Player> patch)
{
Validate(patch.GetEntity());
if
(!ModelState.IsValid)
{
return
BadRequest(ModelState);
}
var
player = db.Players.Find(key);
if
(player ==
null
)
{
return
NotFound();
}
patch.Patch(player);
try
{
db.SaveChanges();
}
catch
(DbUpdateConcurrencyException)
{
if
(!PlayerExists(key))
{
return
NotFound();
}
throw
;
}
return
Updated(player);
}
// DELETE: odata/Players(5)
public
IHttpActionResult Delete([FromODataUri]
int
key)
{
var
player = db.Players.Find(key);
if
(player ==
null
)
{
return
NotFound();
}
db.Players.Remove(player);
db.SaveChanges();
return
StatusCode(HttpStatusCode.NoContent);
}
protected
override
void
Dispose(
bool
disposing)
{
if
(disposing)
{
db.Dispose();
}
base
.Dispose(disposing);
}
private
bool
PlayerExists(
int
key)
{
return
db.Players.Count(e => e.Id == key) > 0;
}
}
}
這個控制器和正常的 Controller 非常相似,只有幾個地方是需要強調
OData 查詢語法是通過 EnableQueryAttribute 來啟用的。我們將在 12-3 來繼續討論。
OData 查詢語法不僅可以用在集合上也可以用在單個實體上,用在單個實體上的時候,只要實體使用 SingleResult<T> 就可以。關於這個我們也是在 12-3 來詳細介紹。
從 URI 綁定的時候,需要使用 FromODataUriAttribute,而不是傳統的 Web API FormUriAttribute。
OData Controller 一般是允許部分實體的更新。這個例子上,是通過 HTTP 的 PATCH 和 Delta<T>來實現部分更新。Delta<T> 是一種特殊的類型,可以用於比較兩個實體之間的差異,但是,他僅僅適用於 ODataMediaTypeFormatters 類型。
很顯然,控制器並非萬能的。使用 OData 的最小要求就是為OData 創建一個實體數據模型(EDM)和 設置OData 路由。這些最終操作的都是 Web API HttpConfiguration 的實例。如清單 12-2 所示,我們會在下一次(12-2)來介紹 OData 路由。EDM 是用來為我們的服務定義 URI,以及提供語義描述(元數據)。
清單 12-1. 設置 EDM 和 OData 路由
1 2 3 4 5 6 7 8 9 10
public
void
SettingUpEdmRoyte()
{
var
config =
new
HttpConfiguration();
//配置 Web API
var
builder =
new
ODataConventionModelBuilder();
builder.EntitySet<Player>(
"Players"
);
// 第一個參數:路由名稱,第二個參數:OData 路由前綴
// players 資源可以被 /odata/players 訪問
config.MapODataServiceRoute(
"odata"
,
"odata"
, builder.GetEdmModel());
}
這個 ODataConventionModelBuilder 類可以幫我們創建一個 EDM,我們不需要不必擔心名稱轉換,導航屬性,主鍵。如果我們需要自定義這些默認關系,那麼,我們就需要使用它的基類 ODataModelBuilder,而不是ODataConventionModelBuilder。
EntitySet方法添加實體並設置為 EDM 同時定義指定的 ODataController 來處理相應資源的 HTTP 請求,在我們的例子中就是 PlayersController。