前言
很久沒更新了,之前有很多事情,所以拖了很久,非常抱歉。好了,廢話不多說,下面開始正題。本篇仍然使用上一季的的項目背景(系列地址http://www.cnblogs.com/fzrain/p/3490137.html)來演示OData服務,因此我們可以直接使用之前建好的數據訪問層。但是不是說一定要看到之前的所有內容,我們只是借用數據庫訪問層,對於數據庫的模型構建移步(使用Entity Framework Code First構建數據庫模型)。
有了數據訪問的基礎,我們可以開始構建OData服務了。
第一步:創建空的Web Api項目
打開項目的解決方案,新建web項目,選擇空的web api項目(如上圖所示),記得選擇.Net Framework 4.5。建好項目之后需要添加對“EntityFramework”和“Learning.Data”(我們的數據訪問層)的引用。
第二步:添加OData引用
在默認的情況下,Web Api是無法支持OData的,因此我們需要添加對“Microsoft.ASP.NET Web API 2.1 OData”的引用——打開NuGet如下圖所示:
第三步:配置OData路由
打開“App_Start”文件夾中系統幫我們創建的“WebApiConfig”類,在這里有一個Register方法並注冊路由規則。我們需要配置的OData也是寫在這里,代碼如下:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Routes.MapODataRoute("elearningOData", "OData", GenerateEdmModel()); } private static IEdmModel GenerateEdmModel() { var builder = new ODataConventionModelBuilder(); builder.EntitySet<Course>("Courses"); builder.EntitySet<Enrollment>("Enrollments"); builder.EntitySet<Subject>("Subjects"); builder.EntitySet<Tutor>("Tutors"); return builder.GetEdmModel(); } }
雖然我們只是使用OData服務,但我並沒有移除默認的配置因為這兩種路由規則是可以共存的。
上面為我們的OData服務配置了路由規則以及實體數據模型(EDM)
EDM主要是定義數據類型、導航屬性和方法來適應OData的格式。有2種方式來定義EDM,第一種是基於公約的方式,我們將使用“ODataConventionModelBuilder”類,另一種則是使用“ODataModelBuilder”。
在這里我們將使用“ODataConventionModelBuilder ”因為它會根據我們定義的導航屬性來生成關聯集合的鏈接。相比來說寫的代碼比較少。如果你想在關聯集合間有更多的控制,那么你可以使用“ODataModelBuilder”。
我們在builder對象中添加了4個不同的實體,注意:字符串參數“Courses”定義的實體集合名字必須與Controller的名字保持一致,也就是說我們的controller的名字必須是“CoursesController”。
“MapODataRoute”是一個擴展方法,當我們添加對OData引用時就可以使用了。它主要為OData服務來定義路由規則的:第一個參數指定一個名字,這個名字客戶端是不會用到的;第二個參數是指對應OData終結點的URI前綴(在我們的案列中訪問Courses資源的URI就應該是:http://hostname/odata/Courses)。你可以在同一個應用程序中擁有多個OData終結點,只需要調用“MapODataRoute”方法指定不同的前綴就行了。
第四步:添加第一個只讀的OData控制器
現在我們創建一個Web Api控制器來處理OData URI類似“/odata/Courses”的HTTP請求。右擊controller文件夾->新增->選擇“空的API控制器”模板並命名“CoursesController”。
創建好之后,首先將我們的基類改成“System.Web.Http.OData.EntitySetController”。這個泛型基類需要2個參數:第一個指映射到這個controller對應的實體類型;第二個參數是指這個實體主鍵的類型,下面上代碼:
public class CoursesController : EntitySetController<Course, int> { LearningContext ctx = new LearningContext(); [Queryable(PageSize = 10)] public override IQueryable<Course> Get() { return ctx.Courses.AsQueryable(); } protected override Course GetEntityByKey(int key) { return ctx.Courses.Find(key); } }
“EntitySetController”類定義了很多抽象和可重寫的方法來更新和查詢實體,因此你會發現你可以重寫很多的方法例如:Get(),GetEntityByKey(),CreateEntrty(),PatchEntity(),UpdateEntity(),etc…
正如我前面提到的,我們將創建一個只讀的控制器,這就意味着我們只實現讀取的操作,解釋一下上面代碼的實現:
1.重寫Get()方法並附上[Queryable]特性,這意味着我們允許客戶端發送HTTP的Get到我們的終結點並在URI參數值支持filter,order by,pagination的操作。Queryable特性是一個action過濾器,主要轉換和校驗查詢的URI及相應參數,當客戶端查詢將花費很多時間或者大量數據時候這個特性將會有意想不到的作用(舉個例子:設置PageSize屬性,這樣一次性只會給客戶端返回10條數據)
2.重寫GetEntityByKey(int key)方法將支持客戶端發送HTTP訪問單個資源,形式類似於“/odata/Courses(5)”。注:這里的key代表對應實體的主鍵。
第五步:測試Courses控制器
現在我們開始測試我們的controller,對於所有的請求我們都將accept header指定為“application/json”,因此我們將獲得輕量JSON數據,你也可以去指定accept header為“application/json;odata=verbose”或者“application/atom+xml”來查看結果。
我們演示一下場景:
1.$filter:我們查詢所有時長超過4小時的課程:發送Get請求http://{hostname}/OData/Courses?$filter=Duration gt 4
2.$orderby, $take:我們需要根據課程名排序並獲取前5條記錄:發送Get請求http://{hostname}/OData/Courses?$orderby=Name&$top=5
3.$select:我們僅需要獲取Name和Duration字段的值:發送Get請求http://{hostname}/OData/Courses?$select=Name,Duration
4.$expand:我們需要獲取每個課程對應的主題和講師並根據課程名倒序排列:發送Get請求http://{hostname}/OData/Courses?$expand=CourseTutor,CourseSubject&$orderby=Name desc
通過剛剛4個例子我們可以看到在我們的返回結果中包含了UserName和Password這兩個字段,但個信息是沒必要給客戶端的。,
十分幸運的是我們只需要去配置一下EDM就可以在返回結果中不包含這兩個字段了,具體做法在WebApiConfig類中的GenerateEdmModel()方法里加入如下代碼:
private static IEdmModel GenerateEdmModel() { var builder = new ODataConventionModelBuilder(); builder.EntitySet<Course>("Courses"); builder.EntitySet<Enrollment>("Enrollments"); builder.EntitySet<Subject>("Subjects"); builder.EntitySet<Tutor>("Tutors"); var tutorsEntitySet = builder.EntitySet<Tutor>("Tutors"); tutorsEntitySet.EntityType.Ignore(s => s.UserName); tutorsEntitySet.EntityType.Ignore(s => s.Password); return builder.GetEdmModel(); }