本篇使用ASP.NET Web API來體驗OData各種query。
首先是本篇即將用到的Model。使用的OData版本是4.0。
public class Customer { public int Id { get; set; } public string Name { get; set; } public Gender Gender { get; set; } public DateTimeOffset BirthTime { get; set; } public List<Order> Orders { get; set; } } public enum Gender { Male, Female } public class Order { public int Id { get; set; } public string Name { get; set; } public int Quantity { get; set; } public Origin Origin { get; set; } } public class Origin { public string City { get; set; } public int PostCode { get; set; } }
在WebApiConfig類中配置OData的路由和EDM。
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Web API 路由
config.MapHttpAttributeRoutes(); config.MapODataServiceRoute(routeName: "OData", routePrefix: "odata", model: GetEdmModel()); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } private static IEdmModel GetEdmModel() { var modelBuilder = new ODataConventionModelBuilder(); modelBuilder.EntitySet<Customer>("Customers"); modelBuilder.EntitySet<Order>("Orders"); return modelBuilder.GetEdmModel(); } }
一個類有一個集合導航屬性
Customer: 1,有一個集合導航屬性List<Order> Orders
Order:多,但沒有有關Customer的外鍵和導航屬性
public class CustomersController : ODataController { private static List<Customer> CustomerList = new List<Customer> { new Customer { Id = 11, Name = "Lowest", Gender = Gender.Female, BirthTime = new DateTime(2001, 1, 1), Orders = new List<Order> { new Order { Id = 0 , Quantity = 10, Origin = new Origin() { City = "East", PostCode = 1024 }}, new Order { Id = 1 , Quantity = 50, Origin = new Origin() { City = "West", PostCode = 4096 }} } }, new Customer { Id = 33, Name = "Highest", Gender = Gender.Male, BirthTime = new DateTime(2002, 2, 2), Orders = new List<Order> { new Order { Id = 2 , Quantity = 10, Origin = new Origin() {City = "North", PostCode = 2048 }}, new Order { Id = 3 , Quantity = 5, Origin = new Origin() {City = "South", PostCode = 8192 }} } }, new Customer { Id = 22, Name = "Middle", Gender = Gender.Female, BirthTime = new DateTime(2003, 3, 3) }, new Customer { Id = 3, Name = "NewLow", Gender = Gender.Male, BirthTime = new DateTime(2004, 4, 4) }, }; [EnableQuery(AllowedArithmeticOperators = System.Web.OData.Query.AllowedArithmeticOperators.Add)] public IEnumerable<Customer> Get() { return CustomerList; } /// <summary>
/// Customer有一個類型為List<Order>的集合導航屬性,這里根據Customer的主鍵、Oder的主鍵、Cstomer的導航屬性名稱,從而刪除Customer的某個Order /// DELETE http://localhost:63372/odata/Customers(11)/Orders/$ref?$id=../../Orders(0) /// </summary>
/// <param name="key">Customer的主鍵</param>
/// <param name="relatedKey">Order的主鍵</param>
/// <param name="navigationProperty">Customer的導航屬性</param>
/// <returns></returns>
[HttpDelete] public IHttpActionResult DeleteRef(int key, int relatedKey, string navigationProperty) { //先找到Customer
var customer = CustomerList.Single(c => c.Id == key); //再找到該Customer的Oder
var order = customer.Orders.Single(o => o.Id == relatedKey); if(navigationProperty != "Orders") { return BadRequest(); } customer.Orders.Remove(order); return StatusCode(System.Net.HttpStatusCode.NoContent); } }
//獲取所有
GET http://localhost:63372/odata/Customers
//排序
GET http://localhost:63372/odata/Customers/?$orderby=Id
GET http://localhost:63372/odata/Customers/?$orderby=Name
//排序,跳過,頂部
GET http://localhost:63372/odata/Customers/?$orderby=Id&$skip=1&$top=2
GET http://localhost:63372/odata/Customers/?$orderby=Name&$skip=2&$top=1
//過濾 集合導航屬性,滿足所有條件
//過濾Customer的集合導航屬性Orders,該集合中只要有一個Order的Quantity大於等於10,就返回該Customer
GET http://localhost:63372/odata/Customers/?$filter=Orders/any(order: order/Quantity ge 10)
//過濾集合導航屬性,滿足任一條件
//過濾Customer的集合導航屬性Orders,該集合中所有Order的Quantity大於等於10,就返回該Customer
GET http://localhost:63372/odata/Customers/?$filter=Orders/all(order: order/Quantity ge 10)
//odata不認識的關鍵詞
//$unkown不是odata內置的關鍵詞
//報錯:The query parameter '$unknown' is not supported.
GET http://localhost:63372/odata/Customers/?$orderby=Name&$unknown=12
//不帶$前綴
//unknown不帶$前綴,
//依然返回數據,但unknown直接被忽略,就當不存在
GET http://localhost:63372/odata/Customers/?$orderby=Name&unknown=12
//未知屬性名
//UnknownPropertyName是未知屬性名
//報錯:400 Bad Reqest
GET http://localhost:63372/odata/Customers/?$orderby=UnknownPropertyName
//過濾,按屬性值
GET http://localhost:63372/odata/Customers/?$filter=Name eq 'Lowest'
//過濾,按表達式
GET http://localhost:63372/odata/Customers/?$filter=Id add 2 eq 4
//過濾,使用string的方法
GET http://localhost:63372/odata/Customers/?$filter=length(Name) eq 6
//過濾,使用有關year的方法
GET http://localhost:63372/odata/Customers/?$filter=year(BirthTime) eq 2001
//過濾,使用別名
GET http://localhost:63372/odata/Customers/?$filter=@p1&@p1=year(BirthTime) eq 2001
//過濾,使用乘法
//mul 是不允許的,因為在CustomersController的EnableQuery配置中只允許加法
//報錯:400 Bad Reqest
GET http://localhost:63372/odata/Customers/?$filter=Id mul 2 eq 6
//select
GET http://localhost:63372/odata/Customers/?$select=Name,BirthTime
//expand,把類以及它的導航屬性全部顯示出來
GET http://localhost:63372/odata/Customers/?$expand=Orders
//混合select和expand
GET http://localhost:63372/odata/Customers/?$select=Name&$expand=Orders($select=Name,Quantity)
//混合filter, exapand, 別名
//先根據Customer的Gender屬性過濾,Gender是枚舉,過濾的值或條件交給@p1這個變量,@p1是MyOdataQuerySample.API.Models命名空間下,Gender枚舉中的Femail枚舉值
//再expand到Customer的導航屬性Orders,再排序,根據@p2這個變量,@p2是Order類中Origin屬性下的City屬性
GET http://localhost:63372/odata/Customers/?$filter=Gender eq @p1&$expand=Orders($orderby=@p2)&@p1=MyOdataQuerySample.API.Models.Gender'Female'&@p2=Origin/City
//刪除,刪除某個Customer的Orders集合中的某個Order,使用相對路徑刪除
//刪除編號為11的Customer與編號為0的Order之間的關系
//Customers(11)中的11被API的key參數接受,Orders被API的navigationProperty接受,Orders(0)中的0被API的relatedKey接受
//../../表示相對路徑,第一個..表示http://localhost:63372/odata,第二個..表示Customers
DELETE http://localhost:63372/odata/Customers(11)/Orders/$ref?$id=../../Orders(0)
//接着查詢確認編號為11的Customer是否和編號為0的Order是否有關系
//結果:編號為11的Customer的導航屬性Orders中已經沒有編號為0的Order了
GET http://localhost:63372/odata/Customers/?$expand=Orders
//刪除,刪除某個Customer的Orders集合中的某個Order,使用絕對路徑刪除
DELETE http://localhost:63372/odata/Customers(11)/Orders/$ref?$id=http://localhost:63372/odata/Orders(1)
//接着查詢確認編號為11的Customer是否和編號為1的Order是否有關系
//結果:編號為11的Customer的導航屬性Orders中已經沒有編號為1的Order了
GET http://localhost:63372/odata/Customers/?$expand=Orders
創建自定義的過濾,排序等規則
public class OrdersController : ODataController { private static List<Order> OrderList = new List<Order> { new Order { Id = 11, Name = "Order1", Quantity = 1 }, new Order { Id = 33, Name = "Order3", Quantity = 3 }, new Order { Id = 4, Name = "Order4", Quantity = 100 }, new Order { Id = 22, Name = "Order2", Quantity = 2 }, new Order { Id = 3, Name = "Order0", Quantity = 0 }, }; /// <summary>
/// 我們通常使用[EnableQuery]來使某個action可以接受OData的Query /// 這里提供了另外一種支持OData的Query的方式,把ODataQueryOptions作為參賽 /// </summary>
/// <param name="queryOptions"></param>
/// <returns></returns>
public IQueryable<Order> Get(ODataQueryOptions queryOptions) { //如果odata query中有過濾
if(queryOptions.Filter != null) { queryOptions.Filter.Validator = new RestrictiveFilterByQueryValidator(); } //過濾可以自定義,如果其它自定義呢?使用ODataValidationSettings //設置max top
ODataValidationSettings settings = new ODataValidationSettings() {MaxTop = 9 }; //設置orderby的屬性
settings.AllowedOrderByProperties.Add("Id"); queryOptions.Validate(settings); return queryOptions.ApplyTo(OrderList.AsQueryable()) as IQueryable<Order>; } /// <summary>
/// 自定義過濾查詢的Validator /// </summary>
private class RestrictiveFilterByQueryValidator : FilterQueryValidator { public override void ValidateSingleValuePropertyAccessNode(SingleValuePropertyAccessNode propertyAccessNode, ODataValidationSettings settings) { if(propertyAccessNode.Property.Name == "Quantity") { throw new ODataException("不允許針對Quantity屬性過濾"); } base.ValidateSingleValuePropertyAccessNode(propertyAccessNode, settings); } } }
以上,
● Get方法中的ODataQueryOptions類型也可支持odata query
● 通過ODataQueryOptions的Filter.Validator屬性,我們可以設置自定義繼承FilterQueryValidator的子類,自定義過濾條件
● ODataValidationSettings用來自定義其它規則,比如排序、max top,等等
● 把ODataValidationSettings的實例作為ODataQueryOptions的實例方法Validate的實參
● 最終通過ODataQueryOptions的實例方法ApplyTo,把規則作用到IQueryable<T>類型集合上去
GET http://localhost:63372/odata/Orders
//排序,使用controller中允許的字段
GET http://localhost:63372/odata/Orders/?$orderby=Id
//orderby, skip, top,在設定的規則之內
GET http://localhost:63372/odata/Orders/?$orderby=Id&$skip=1&$top=2
//orderby在規則之內,top在規則之外
//報錯:500 Internal Server Error, 因為top的上限是9
GET http://localhost:63372/odata/Orders/?$orderby=Id&$top=2000
//orderby在規則之外,top在規則之內
//報錯:500 Internal Server Error,因為只允許把Id作為排序字段
GET http://localhost:63372/odata/Orders/?$orderby=Name&$top=2
//filter,在規則之內
//規則不允許對Quantity進行過濾
GET http://localhost:63372/odata/Orders/?$filter=Id ge 10
//filter,在規則之外
//規則不允許對Quantity進行過濾
//報錯:500 Internal Server Error
GET http://localhost:63372/odata/Orders/?$filter=Quantity ge 100
API返回HttpResponseMessage,對返回信息有更多的控制
public class ResponseController : ODataController { private static List<Customer> CustomerList = new List<Customer> { new Customer { Id = 11, Name = "Lowest", BirthTime = new DateTime(2001, 1, 1), Orders = new List<Order> { new Order { Id = 0 , Quantity = 10 }, new Order { Id = 1 , Quantity = 50 } } }, new Customer { Id = 33, Name = "Highest", BirthTime = new DateTime(2002, 2, 2), Orders = new List<Order> { new Order { Id = 2 , Quantity = 10 }, new Order { Id = 3 , Quantity = 5 } } }, new Customer { Id = 22, Name = "Middle", BirthTime = new DateTime(2003, 3, 3) }, new Customer { Id = 3, Name = "NewLow", BirthTime = new DateTime(2004, 4, 4) }, }; /// <summary>
/// 之前的返回類型有IEnumerable, IQueryable, IHttpActionResult /// 這里是HttpResponseMessage,允許忘header里面加字段,方便操作status /// </summary>
/// <returns></returns>
[EnableQuery(AllowedArithmeticOperators =System.Web.OData.Query.AllowedArithmeticOperators.Add)] public HttpResponseMessage Get() { HttpResponseMessage response = Request.CreateResponse<IEnumerable<Customer>>(HttpStatusCode.OK, CustomerList); response.Headers.Add("Sample-Header", "Sample-Value"); return response; } /// <summary>
/// 刪除某個Customer下Orders導航屬性中的某個Order /// </summary>
/// <param name="key">Customer的主鍵</param>
/// <param name="relatedKey">Order的主鍵</param>
/// <returns></returns>
[HttpDelete] [ODataRoute("Response({key})/Orders({relatedKey})/$ref")]//自定義OData路由規則
public HttpResponseMessage DeleteOrdersFromCustomer(int key, int relatedKey) { var customer = CustomerList.Single(c => c.Id == key); var order = customer.Orders.Single(o => o.Id == relatedKey); customer.Orders.Remove(order); HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NoContent); response.Headers.Add("Delete-Ref", "true"); return response; } }
以上,
● 返回類型是HttpResponseMessage,借此可以自定義返回狀態,以及返回Header,等
● 通過[ODataRoute("Response({key})/Orders({relatedKey})/$ref")]設置自定義路由規則
//查看所有
//返回的Headers中有在API中自定義的Sample-Header → Sample-Value
GET http://localhost:63372/odata/Response
//orderby
//返回的Headers中有在API中自定義的Sample-Header → Sample-Value
GET http://localhost:63372/odata/Response/?$orderby=Id
//orderby,skip, top
//返回的Headers中有在API中自定義的Sample-Header → Sample-Value
GET http://localhost:63372/odata/Response/?$orderby=Id&$skip=1&$top=2
//filter+any
//Orders是Customer的導航屬性,order:order有點像lambda表達式,order/Quantity用/表示Order中的Quantity屬性
//返回的Headers中有在API中自定義的Sample-Header → Sample-Value
GET http://localhost:63372/odata/Response/?$filter=Orders/any(order: order/Quantity ge 10)
//filter+all
//返回的Headers中有在API中自定義的Sample-Header → Sample-Value
GET http://localhost:63372/odata/Response/?$filter=Orders/all(order: order/Quantity ge 10)
//刪除某個Customer下Order集合中的某個Order
//Response(11)/Orders/$ref表示關系
//$id=../../Orders(0),用的是相對路徑,相當於http://localhost:63372/odata/Orders(0)
//返回的Headers中有在API中自定義的Delete-Ref → true
DELETE http://localhost:63372/odata/Response(11)/Orders/$ref?$id=../../Orders(0)
