.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N131114201
出刊日期:2013/11/6
Open Data Protocol (OData) 是一個Web協定,以HTTP為基礎,整合Atom Publishing Protocol (AtomPub,XML格式) 和JSON格式,用來查詢與更新資料,由其是適用於設計CRUD(Create、Read、Update與Delete)的動作。
ASP.NET Web API內建OData的支援,若你建立一個ASP.NET Web API範本的專案,預設就可以使用OData相關類別。你也可以在Visual Studio 2012開發工具中,透過NuGet套件管理員,下載及安裝「Microsoft.AspNet.WebApi.OData」套件。本篇文章將介紹如何在ASP.NET Web API專案設計OData服務,以及用戶端如何叫用OData服務。
設計支援OData的ASP.NET Web API服務
第一步先建立ASP.NET MVC專案,從Visual Studio 2012開發工具 -「File」-「New」-「Project」,在「New Project」對話盒中選取程式語言,例如本範例選擇「Visual C#」,從「Web」分類中,選取「ASP.NET MVC 4 Web Application」,為專案取一個名稱,然後按下「OK」鍵,請參考下圖所示:

圖 1:ASP.NET MVC 4 Web Application。
建立一個ASP.NET MVC 4應用程式「Web API」範本專案,請參考下圖所示:

圖 2:建立ASP.NET WEB API專案。
新增實體資料模型
我們所設計的OData服務,其後端資料資料庫存取部分採用Entity Framework Code First方式來設計,先定義一個Employee Entity,從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「Employee」當作名稱,按「Add」按鈕,請參考下圖所示:

圖 3:新增Employee類別。
為Employee類別加入Id、EmployeeName與Age三個屬性:
namespace MyWebAPI.Models {
public class Employee {
public int Id { get; set; }
public string EmployeeName { get; set; }
public int Age { get; set; }
}
}
按相同方式,從「Solution Explorer」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「Class」,使用「EmployeesDB」當作名稱,按「Add」按鈕,請參考下圖所示:

圖 4:新增EmployeesDB類別。
加入以下程式碼,讓EmployeesDB繼承自DbContext類別( 定義在System.Data.Entity命名空間下),類別中包含一個Employees屬性,存放Entity物件:
namespace MyWebAPI.Models {
public class EmployeesDB : DbContext {
public DbSet<Employee> Employees { get; set; }
}
}
接著再新增一個EmployeesInitializer類別,繼承DropCreateDatabaseAlways<T>類別,改寫Seed方法,此方法初始化EmployeessDB資料庫的Employees資料表,新增三筆員工資料,建立三個Employee物件,並將Employee物件新增到DbContext之中,並叫用SaveChanges方法寫回後端資料庫:
public class EmployeesInitializer : DropCreateDatabaseAlways<EmployeesDB> {
protected override void Seed( EmployeesDB context ) {
base.Seed( context );
var emps = new List<Employee>{
new Employee {
EmployeeName = "Mary",
Age =30
},
new Employee {
EmployeeName = "Candy",
Age =25
},
new Employee {
EmployeeName = "Lili",
Age =45
}
};
emps.ForEach( s => context.Employees.Add( s ) );
context.SaveChanges( );
}
}
最後修改專案根路徑下的Web.Config檔案,預設有一個connectionStrings組態區段:
<connectionStrings>
<add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MyWebAPI-20130924172807;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MyWebAPI-20130924172807.mdf" />
</connectionStrings>
指定name為EmployeesDB,此名稱需和繼承自DbContext類別的EmployeesDB類別名稱一致,這樣Enity Framework就會自動識別,讀取組態檔案中的連接字串連接到資料庫:
<connectionStrings>
<add name="EmployeesDB" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=EmployeesDB;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\EmployeesDB.mdf" />
</connectionStrings>
初始化資料庫
接著在Global.asax檔案內Application_Start方法中,加入程式碼,叫用Database類別SetInitializer方法來初始資料庫結構和資料表資料:
protected void Application_Start( ) {
AreaRegistration.RegisterAllAreas( );
Database.SetInitializer<MyWebAPI.Models.EmployeesDB>( new MyWebAPI.Models.EmployeesInitializer( ) );
WebApiConfig.Register( GlobalConfiguration.Configuration );
FilterConfig.RegisterGlobalFilters( GlobalFilters.Filters );
RouteConfig.RegisterRoutes( RouteTable.Routes );
BundleConfig.RegisterBundles( BundleTable.Bundles );
}
建立支援OData的控制器
接下來你需要設計一個支援OData的控制器,此控制器需要繼承ODataController或EntitySetController<TEntity, TKey>類別,控制器的名稱要對應到EntitySet。從Visual Studio 2012開發工具-「Solution Explorer」-專案-「Controllers」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」-「Controller」。設定控制器名稱為「EmployeesController」;在這個階段Template設定為「Empty API controller」,然後按下「Add 」按鈕,請參考下圖所示:

圖 5:新增控制器。
修改產生出的程式碼,讓EmployeesController類別繼承ODataController類別,然後在類別之中設計Queryable Action。Queryable Action就是一個動作方法(Action Method),回傳IQueryable<T>型別,比較需要注意的是,需要在方法上方套用[Queryable] Attribute,以開放用戶端進行查詢的動作。參考以下EmployeesController類別Get方法程式碼:
using MyWebAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.OData;
namespace MyWebAPI.Controllers {
public class EmployeesController : ODataController {
EmployeesDB ctx = new EmployeesDB( );
[Queryable]
public IQueryable<Employee> Get( ) {
return ctx.Employees;
}
}
}
設定OData Endpoint
OData使用Service Metadata Document開放其資料模型的結構,Service Metadata Document是一份XML格式的文件,描述EDM中的概念資料模型(conceptual data model)。在ASP.NET Web API中只要建立一個實作IEdmModel介面的實體資料模型類別,就會產生Service Metadata Document。
修改專案中的WebApiConfig.cs檔案,在WebApiConfig類別中Register方法中叫新增一個InitializeOData方法,此方法利用ODataConventionModelBuilder類別自動建立CLR類別與EDM模型之間的對應,設定EntitySet<Employee>的名稱為Employees,然後叫用GetEdmModel( )方法來取得實體資料模型。
最後,還需要設定OData Endpoint,叫用GlobalConfiguration.Configuration.Routes.MapODataRoute方法指定路由名稱為「OData」;使用 routePrefix指定OData Endpoint的前置字串為「odata」,並傳入GetEdmModel( )方法取得的實體資料模型:
namespace MyWebAPI {
public static class WebApiConfig {
public static void Register( HttpConfiguration config ) {
config.Routes.MapHttpRoute(
name: "DefaultApi" ,
routeTemplate: "api/{controller}/{id}" ,
defaults: new { id = RouteParameter.Optional }
);
InitializeOData( );
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing( );
}
private static void InitializeOData( ) {
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder( );
modelBuilder.EntitySet<Employee>( "Employees" );
IEdmModel model = modelBuilder.GetEdmModel( );
GlobalConfiguration.Configuration.Routes.MapODataRoute( routeName: "OData" ,
routePrefix: "odata" ,
model: model );
}
}
}
OData服務測試
OData提供許多OData Query String Options來查詢資料。OData Query String Options都是以「$」號開始,附加在URL後方,為查詢字串的一部分。常用的OData Query Options包含:
- $orderby:排序。
- $top:取前幾筆。
- $skip:跳過前幾筆。
開啟瀏覽器,輸入OData服務所在Endpoint:「http://localhost:25723/OData」,可以看到以下描述服務的XML,請參考下圖所示:

圖 6:描述服務的XML。
我們也可以利用OData Query String Options「$metadata」來查詢Service Metadata Document,例如輸入以下URL:
http://localhost:25723/odata/$metadata
可取得服務所使用的實體物件模型的定義,請參考下圖所示:

圖 7:實體物件模型的定義。
修改瀏覽器URL,附加Employees字串到URL後方:「http://localhost:25723/OData/Employees」,從OData查詢資料庫Employees資料表資料(OData可以回傳XML或JSON格式資料,預設為JOSN格式) ,請參考下圖所示:

圖 8:OData服務查詢結果。
回傳的資料包含兩個部分,odata.metadata指明服務Metadata所在的URI為「http://localhost:25723/odata/$metadata#Employees」;value則是查詢回傳的Employee物件。
設計使用OData 的用戶端程式
OData以HTTP為基礎,任何HTTP的用戶端應用程式皆可以進行存取。我們可以利用Visual Studio 2012的「Add Service Reference」功能,在用戶端產生一個本機類別來呼叫ASP.NET Web API。這個本機類別的名稱為Container,繼承自DataServiceContext類別,開放OData服務的資料模型。
我們先建立一個主控台應用程式當用戶端。從Visual Studio 2012開發工具 -「File」-「Add」-「New Project」,在「New Project」對話盒中選取「Visual C#」程式語言,從「Windows」分類中,選取「Console Application」,名稱設定為「ODataClient」;按下「OK」鍵,請參考下圖所示:

圖 9:新增用戶端專案。
從「Solution Explorer」視窗,在「MyWebAPI」專案上方按右鍵,選取「Set As Startup Project」,然後按CTRL + F5執行MyWebAPI專案(即Host)。回到Visual Studio 2012,「Solution Explorer」視窗,在「ODataClient」專案上方按右鍵,從快捷選單選取「Add Service Reference」。在Address輸入「
http://localhost:25723/OData/」,然後按下「Go」按鈕,請參考下圖所示:

圖 10:Add Service Reference。
接著在ODataClient的專案下,就會自動產生一個Reference.cs檔案,其中包含Container類別的定義;以及一個service.edmx檔案,描述服務的實體資料模型,請參考下圖所示:

圖 11:Reference.cs檔案與service.edmx檔案。
Container類別包含許多屬性,用來開放OData服務中的資料,例如以本範例而言,Container類別會有一個System.Data.Services.Client.DataServiceQuery<Employee>型別的Employees屬性,利用此屬性便可以查詢資料庫Employees資料表資料。
接著我們就可以利用Container類別來叫用OData服務,在ODataClient的Program類別Main方法加入以下程式碼,以LINQ語法查詢出所有Employees資料表EmployeeName欄位的內容:
namespace ODataClient {
class Program {
static void Main( string [ ] args ) {
Console.WriteLine( "OData Client is running ... " );
var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
var emps = ( from emp in container.Employees
orderby emp.EmployeeName ascending
select emp
);
foreach ( var emp in emps ) {
Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
}
Console.ReadLine( );
}
}
}
測試時可以設定兩個專案同時執行,從「Solution Explorer」視窗-Solution項目上方按滑鼠右鍵,從快捷選單選取「Properties」,點選「Multiple Startup Projects」,設定兩個專案啟動順序,請參考下圖所示:

圖 12:設定兩個專案同時執行。
按CTRL + F5執行,主控台應用程式將印出以下資料庫資料,請參考下圖所示:

圖 13:用戶端執行結果。
繼承EntitySetController類別
另一個比較簡單設計支援OData控制器的方式是,讓你的控制器繼承EntitySetController<TEntity, TKey>類別,然後改寫它的方法。EntitySetController<TEntity, TKey>類別繼承自ODataController類別,而ODataController類別又繼承自ApiController,請參考下圖所示:

圖 14:EntitySetController<TEntity, TKey>類別繼承關係。
EntitySetController<TEntity, TKey>類別需要兩個泛型參數:第一個參數為Entity (Employee);第二個參數為 Key的型別。修改專案中的EmployeesController類別程式如下,改寫Get方法,透過LINQ查詢出Employees資料表資料,Get方法需要回傳IQueryable,這樣用戶端程式就可以定義查詢,從資料庫查詢符合條件的資料,而不需要將所有Employee資料放到Cache,再從Cache中查詢,因此範例中叫用AsQueryable方法轉型後將查詢結果回傳:
public class EmployeesController : EntitySetController<Employee , int> {
EmployeesDB ctx = new EmployeesDB( );
public override IQueryable<Employee> Get( ) {
return ctx.Employees.AsQueryable( );
}
protected override Employee GetEntityByKey( int key ) {
Employee emp = ctx.Employees.Find( key );
return emp;
}
GetEntityByKey方法則可以傳入Employee的ID(key),根據key查詢特定員工資料後回傳。若有需要進行資料的新增、刪除或修改動作,則可以改寫Post、Delete與Put方法,在此先不討論。
修改組態
修改WebApiConfig.cs檔案中的Register方法,移除config.EnableQuerySupport() 方法的註解,啟用查詢功能:
public static void Register( HttpConfiguration config ) {
config.Routes.MapHttpRoute(
name: "DefaultApi" ,
routeTemplate: "api/{controller}/{id}" ,
defaults: new { id = RouteParameter.Optional }
);
InitializeOData( );
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing( );
}
按CTRL+F5執行程式,開啟瀏覽器,輸入以下URL,叫用第一個GET方法:
http://localhost:25723/OData/Employees,執行結果請參考下圖所示:
![clip_image016[1] clip_image016[1]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image016%5B1%5D_thumb.jpg)
圖 15:用戶端測試結果。
因為服務有叫用config.EnableQuerySupport() 方法啟用查詢功能,因此你可以在URL後方附加Query String Options,例如以下使用$top叫用第一個GET方法取回滿足條件資料的前兩筆回傳:
http://localhost:25723/OData/Employees?$top=2
得到的執行結果如下圖所示:
圖 16:用戶端測試結果。
你也可以使用以下語法,叫用GetEntityByKey方法,查詢出Employee Id為「1」的員工資料:
http://localhost:25723/OData/Employees(1),執行結果請參考下圖所示:

圖 17:用戶端測試結果。
OData Query String Options
若用戶端想要得知LINQ查詢使用的OData Query String Options,可以在LINQ查詢中叫用ToString()方法,例如修改ODataClient的Main方法如下:
namespace ODataClient {
class Program {
static void Main( string [ ] args ) {
Console.WriteLine( "OData Client is running ... " );
var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
var emps = ( from emp in container.Employees
orderby emp.EmployeeName ascending
select emp
);
Console.WriteLine( emps.ToString());
foreach ( var emp in emps ) {
Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
}
Console.ReadLine( );
}
}
}
按CTRL + F5執行,主控台應用程式將印出以下資料庫資料以及OData Query String Options,利用「$orderby」Query Options設定排序欄位為EmployeeName請參考下圖所示:

圖 18:用戶端測試結果。
修改用戶端程式碼,叫用LINQ的Take方法,找詢滿足條件的前兩筆:
namespace ODataClient {
class Program {
static void Main( string [ ] args ) {
Console.WriteLine( "OData Client is running ... " );
var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
var emps = ( from emp in container.Employees
orderby emp.EmployeeName ascending
select emp
).Take(2);
Console.WriteLine( emps.ToString());
foreach ( var emp in emps ) {
Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
}
Console.ReadLine( );
}
}
}
按CTRL + F5執行,主控台應用程式將印出以下資料庫資料以及OData Query String Options,利用「$top」Query Options印出前兩筆資料,請參考下圖所示:

圖 19:用戶端測試結果。
此外我們也可以註冊SendingRequest2事件,來取得發出請求使用的HTTP Method與URL,參考以下程式碼:
static void Main( string [ ] args ) {
Console.WriteLine( "OData Client is running ... " );
var container = new ServiceReference1.Container( new Uri( "http://localhost:25723/OData/" ) );
container.SendingRequest2 += ( s , e ) => {
Console.WriteLine( " Method : {0} \n URL: {1}" ,
e.RequestMessage.Method , e.RequestMessage.Url );
};
var emps = ( from emp in container.Employees
orderby emp.EmployeeName ascending
select emp
).Take( 2 );
foreach ( var emp in emps ) {
Console.WriteLine( emp.Id + " , " + emp.EmployeeName );
}
Console.ReadLine( );
}
按CTRL + F5執行,得到的執行結果如下圖所示:

圖 20:用戶端測試結果。
總結
NET Framework 4.5的ASP.NET Web API雖然提供一個基礎架構來協助建立HTTP服務,但或多或少有客製化的需求,例如錯誤處理、整合OData,或稽核服務的執行等行為。ASP.NET Web API支援OData Query String Options,只要將Action設計成Queryable Action就可以達到這個目地。