問題 如何控制 OData 路由 解決方案 為了註冊路由,可以使用 HttpConfigurationExtension 類中 MapODataServiceRoute 的擴展方法。對於單一路由這樣做足以,其餘的處理由實體數據模型來處理。 從 ASP.NET Web API 2.2 開始支持 ODa ...
問題
如何控制 OData 路由
解決方案
為了註冊路由,可以使用 HttpConfigurationExtension 類中 MapODataServiceRoute 的擴展方法。對於單一路由這樣做足以,其餘的處理由實體數據模型來處理。
config.MapODataServiceRoute("OData", "OData", builder.GetEdmModel());
從 ASP.NET Web API 2.2 開始支持 OData 直接聲明路由,在 Action 上使用 ODataRouteAttribute。這和常規的屬性路由一樣,可以通過 ODataRoutePrefixAttribute 在 Controller 級別設置路由首碼.
1 [ODataRoute("Players")] 2 3 public IQueryable<Player> GetAllPlayers() 4 5 { 6 7 // 忽略 8 9 }
工作原理
OData 在 Web API 中路由是通過 ODataRoute 類實現的,其實,他是 HttpRoute 的一個子類。定製路由需要支持 ODataPathRouteConstraint,他是 OData 制定的,是IHttpRouteConstrain 的實現類,是為了確保所有的 OData 屬性或 OData 路由約定在路由匹配後能夠設置到 HttpRequestMessage 上。
ODataPath 類是用來包裝轉換 OData 資源路徑,並以強類型的方式公開段(segment)。按約定,ASP.NET Web API 使用資源路徑(URI 的一部分,例如,/Player),他是基於實體數據模型來映射到相應的 Controller。然後,動詞依據動作選擇在 Controller 中找到相應的 Action。此外,在 Web API 中 OData 可以使用自定義路由,這些路由都會影響動作選擇。
小提示 更多關於 OData 路由約定的信息,請戳這裡
https://www.asp.net/web-api/overview/OData-support-in-aspnet-web-api/OData-routing-conventions
我們可以通過我們自己的 IODataRoutingConvertion 重寫 OData 路由行為、實現自定義 OData Controller、實現動作選擇,如清單 12-3 所示
清單 12-3 IODataRoutingConvention 定義
1 public interface IODataRoutingConvention 2 3 { 4 5 string SelectController(ODataPath ODataPath, HttpRequestMessage request); 6 7 string SelectAction(ODataPath ODataPath, HttpControllerContext controllerContext, 8 9 ILookup<string, HttpActionDescriptor> actionMap); 10 11 }
當我們定義 OData 路由時,可以通過 MapODataServiceRoue 擴展方法來自定義路由約定;其中個的一個重載方法就是 使用 IODataRoutingConvention 的集合作為參數的方法。如果沒有傳值(例如,在使用 MapODataServiceRoute),那麼,Web API 將在內部調用靜態的 ODataRoutingConventions。CreateDefaultWithAttributeRouting 只使用預設的內建路由約定。
在 OData 中屬性路由是 IDataRoutingConvention 的另一個版本-AttributeRoutingCinvention。他會查找所有 ODataRouteAttribute 的用法,使用 Dictionary<ODataPathTemplate,HttpActionDescriptot> 的形式建立適當的映射關係。如果進來的請求與當前 HTTP 請求的 ODataPath 匹配,那麼,與此相關的 HttpActionDescriptor Controller 將會被選翻牌子來處理請求。
屬性路由對於非標準路由來說是不錯的選擇,例如,使用非綁定的 OData 功能或 Action 的時候。嘗試使用集中路由的方式進行路由,就需要自定義路由約定,然而,屬性路由可以使用 ODataRouteAttribute 相關方法,用很簡單的聲明方式直接完成。
需要註意的是屬性路由與常規路由不同,他是預設啟用的。也就是說,除非我們重寫預設的 IODataRoutingConventions,否則,Web API 會調用 ODataRoutingConvents。無論什麼時候使用 MapODataServiceRoute 方法,CreateDefaultWithAttributeRouting 內部都會確保被 Web API OData 使用的 AttributeRoutingConvention 包含在約定的集合中。如清單 12-4 所示,摘錄自 Web API 源碼。
清單 12-4 ODataRoutingConvrention 類,確保 AttributeRoutingConvention 被包含。
1 public static class ODataRoutingConventions 2 3 { 4 5 public static IList<IODataRoutingConvention> CreateDefaultWithAttributeRouting( 6 7 HttpConfiguration configuration, 8 9 IEdmModel model) 10 11 { 12 13 if (configuration == null) 14 15 { 16 17 throw Error.ArgumentNull("configuration"); 18 19 } 20 21 if (model == null) 22 23 { 24 25 throw Error.ArgumentNull("model"); 26 27 } 28 29 IList<IODataRoutingConvention> routingConventions = CreateDefault(); 30 31 AttributeRoutingConvention routingConvention = new AttributeRoutingConvention(model, 32 33 configuration); 34 35 routingConventions.Insert(0, routingConvention); 36 37 return routingConventions; 38 39 } 40 41 public static IList<IODataRoutingConvention> CreateDefault() 42 43 { 44 45 return new List<IODataRoutingConvention>() 46 47 { 48 49 new MetadataRoutingConvention(), 50 51 new EntitySetRoutingConvention(), 52 53 new SingletonRoutingConvention(), 54 55 new EntityRoutingConvention(), 56 57 new NavigationRoutingConvention(), 58 59 new PropertyRoutingConvention(), 60 61 new RefRoutingConvention(), 62 63 new ActionRoutingConvention(), 64 65 new FunctionRoutingConvention(), 66 67 new UnmappedRequestRoutingConvention() 68 69 }; 70 71 } 72 73 }
結果就是,在應用程式啟動,不在需要調用任何其他的方法通知框架掃描所有的路由屬性。事實上,只有這樣,才能在最開始的地方獲取屬性路由並調用 MapODataServiceRoute.
代碼演示
為了介紹 OData Web API 的集中路由,我們只需要在 HttpConfiguration 中調用 MapODataServiceRoute 和傳路由首碼,以及我們的 IEdmModel。一個完整的啟動類列子,使用簡單的 OData 實體和集中路由,如清單 12-5 所示,使用 Player 實體。
清單 12-5. 聲明一個基本的 OData 路由啟動類
1 public class Startup 2 3 { 4 5 public void Configuration(IAppBuilder builder) 6 7 { 8 9 var ODataBuilder = new ODataConventionModelBuilder(); 10 11 ODataBuilder.EntitySet<Player>("Players"); 12 13 var edm = ODataBuilder.GetEdmModel(); 14 15 var config = new HttpConfiguration(); 16 17 config.MapODataServiceRoute("Default OData", "OData", edm); 18 19 builder.UseWebApi(config); 20 21 } 22 23 } 24 25 public class Player 26 27 { 28 29 public int Id { get; set; } 30 31 public string Name { get; set; } 32 33 public string Team { get; set; } 34 35 }
允許我們使用所有預設的內建路由約定,例如,
• myapi.com/OData/Players
• myapi.com/OData/Players(key)
• myapi.com/OData/Players(key)/{navigation property | property}
• myapi.com/OData/Players(key)/{function | action}
註意 預設的 Web API OData 路由約定使用了 key 的概念,不是 ID,所以我們 Controller 的 Action 應該接收一個叫做 Key 的參數。
清單 12-6 展示了 ODataController 使用兩個 Action 方法。這兩個方法都是通過屬性路由的方式聲明瞭 OData 路由。
清單 12-6. OData Controller 使用屬性路由的例子
1 [ODataRoutePrefix("Players")] 2 3 public class PlayersController : ODataController 4 5 { 6 7 private readonly PlayersContext _players = new PlayersContext(); 8 9 [EnableQuery] 10 11 [ODataRoute] 12 13 public IQueryable<Player> GetAllPlayers() 14 15 { 16 17 return _players.AsQueryable(); 18 19 } 20 21 [EnableQuery] 22 23 [ODataRoute("({key})")] 24 25 public SingleResult<Player> GetSinglePlayers(int key) 26 27 { 28 29 return SingleResult.Create(_players.Where(x => x.Id == key).AsQueryable()); 30 31 } 32 33 }