1

當我們有多個所需級別的每個方法的路由時,是否有一種方法可以簡化?具有多個路由級別的webapi的乾式方法

我有一個假想的WebAPI項目,我用這個問題的一般看看。它給我們來自某些來源的電影。

public class MovieController : ApiController 
{ 
    // GET api/<controller> 
    public IEnumerable<Movie> Get() 
    { 
     return MoviesDB.All(); 
    } 

    // GET api/<controller>/5 
    public Movie Get(int id) 
    { 
     return MoviesDB.ThisSpecificOne(id); 
    } 

    // POST api/<controller> 
    public void Post([FromBody]Movie value) 
    { 
    } 

    // PUT api/<controller>/5 
    public void Put(int id, [FromBody]Movie value) 
    { 
    } 

    // DELETE api/<controller>/5 
    public void Delete(int id) 
    { 
    } 
} 

但讓我們說一些愚蠢的原因電影存儲的流派。所以你需要流派+編號組合。

我假設這是你會怎麼做

config.Routes.MapHttpRoute(
    name: "MoviesWithGenre", 
    routeTemplate: "api/{controller}/{genre}/{id}", 
    defaults: new { id = RouteParameter.Optional } 
); 

public class MovieController : ApiController 
{ 
    // GET api/<controller>/<genre> 
    public IEnumerable<Movie> Get(string genre) 
    { 
     return MoviesDB.All(genre); 
    } 

    // GET api/<controller>/<genre>/5 
    public Movie Get((string genre, int id) 
    { 
     return MoviesDB.ThisSpecificOne(string genre, id); 
    } 

    // POST api/<controller>/<genre> 
    public void Post(string genre, [FromBody]Movie value) 
    { 
    } 

    // PUT api/<controller>/<genre>/5 
    public void Put(string genre, int id, [FromBody]Movie value) 
    { 
    } 

    // DELETE api/<controller>/<genre>/5 
    public void Delete(string genre, int id) 
    { 
    } 
} 

所以現在MySite.Com/api/movie/horror/12345可能會返回一個電影,但我需要添加可選參數的每個方法。現在我發現它們也是按年存儲的。

config.Routes.MapHttpRoute(
    name: "MoviesWithGenreAndYear", 
    routeTemplate: "api/{controller}/{genre}/{year}/{id}", 
    defaults: new { id = RouteParameter.Optional } 
); 

public class MovieController : ApiController 
{ 
    // GET api/<controller>/<genre>/<year> 
    public IEnumerable<Movie> Get(string genre, int year) 
    { 
     return MoviesDB.All(string genre, int year); 
    } 

    // GET api/<controller>/<genre>/<year>/5 
    public Movie Get(string genre, int year, int id) 
    { 
     return MoviesDB.ThisSpecificOne(string genre, int year, id); 
    } 

    // POST api/<controller>/<genre>/<year> 
    public void Post(string genre, int year, [FromBody]Movie value) 
    { 
    } 

    // PUT api/<controller>/<genre>/<year>/5 
    public void Put(string genre, int year, int id, [FromBody]Movie value) 
    { 
    } 

    // DELETE api/<controller>/<genre>/<year>/5 
    public void Delete(string genre, int year, int id) 
    { 
    } 
} 

這一切都可以正常工作,但是每個新層都需要爲每個方法添加一個新參數。這不覺得很DRY

我可以注入這些層到構造函數而不是方法本身。

也許我想根據這些圖層對控制器進行不同的初始化,所以我會根據流派和/或年份或類似情況設置不同的回購。

有沒有解決方案呢?

回答

2

您是否考慮過使用OData? Web Api支持OData烘焙,您可以將查詢編寫爲網址:例如?$ filter =類型eq'恐怖'。如果由於某種原因你,不想作爲OData的數據返回,但想的OData的查詢語法,那麼你可以:

  1. 使用Linq To QueryString:此LIB給你一個擴展方法IQueryable的那解析查詢字符串和查詢適用於任何的IQueryable

  2. 變換ODataQueryOptions成一個查詢到你的數據庫(見this MSDN article該轉換查詢到HQL爲例)

+0

我dont't有足夠的信譽來增加超過2個鏈接..你可以在http://www.asp.net/web-api/overview/odata-support-in-aspnet找到OData的支持信息-web-api –

+0

這非常整齊。我upvoted你,但我也有興趣在實際路由URL的參數是不可選的,似乎OData解決方案,當你想限制你的搜索更好的答案。請注意,我沒有製作電影服務,我只是以此爲例。我問,如果你必須給每個呼叫限制在一個特定子集/數據庫等 –

+0

也許有點沉重的解決方案,但你可以實現IHttpControllerSelector和IHttpActionSelector什麼:這兩個給你至極的動作控制哪些控制器獲取給定HttpRequestMessage –

0

是移動日e可選參數到查詢字符串中一個可行的選項?

例如GET api/movie?genre=horror&year=2014

這將簡化您的路線,控制器:

config.Routes.MapHttpRoute(
    name: "Movies", 
    routeTemplate: "api/{controller}/{id}", 
    defaults: new { id = RouteParameter.Optional } 
); 

public class MovieController : ApiController 
{ 
    // GET api/<controller>?genre=<genre>&year=<year> 
    public IEnumerable<Movie> Get(string genre = null, int? year = null) 
    { 
     return MoviesDB.All(string genre, int year); 
    } 

    // GET api/<controller>/5?genre=<genre>&year=<year> 
    public Movie Get(int id, string genre = null, int? year = null) 
    { 
     return MoviesDB.ThisSpecificOne(string genre, int year, id); 
    } 

    // POST api/<controller>?genre=<genre>&year=<year> 
    public void Post([FromBody]Movie value, string genre = null, int? year = null) 
    { 
    } 

    // PUT api/<controller>/5?genre=<genre>&year=<year> 
    public void Put(int id, [FromBody]Movie value, string genre = null, int? year = null) 
    { 
    } 

    // DELETE api/<controller>/5?genre=<genre>&year=<year> 
    public void Delete(int id, string genre = null, int? year = null) 
    { 
    } 
} 
+0

我懷疑,因爲這個問題明確表示「電影流派_stored_。所以你需要流派+ ID組合。」,它的真正含義,他們是主要的鍵(RDBMS)的一部分或其他身份執行的對象。因此,我真的不明白他們是如何可選的。 –

0

如果你真的是按流派和年份存儲的電影,那麼我相信你不會這麼幹的解決方案實際上是正確的。這讓我質疑構建這樣的多部分標識符的目的,例如在你的例子中使用的電影,肯定年和流派只是關於電影的元信息,而不是標識符的一部分。更一般地說,我真的會爭論一個超過兩個或至少超過三個部分的複合標識符是否適合任何軟件的和平。代理鍵可以緩解這種情況下的發展困境。

還討論您的關注對幹:重複的湖,我認爲,作爲一個對象的主鍵結構很少改變,它不是一個真正的大問題。更重要的是,主鍵的改變將永遠是一個打破所有後向兼容性的改變。

爲噱頭,你可以創建一個包含複雜的ID作爲這樣一個新的類:

public class MovieId 
{ 
    public int Id { get; set; } 
    public int Yead { get; set; } 
    public string Genre { get; set; } 
} 

然後使控制器方法是這樣的:

public Movie Get([FromBody]MovieId id) 
    { 
     return MoviesDB.ThisSpecificOne(id); 
    } 

這個工程現在代碼完全符合DRY原則。問題是,複雜類型必須是一個主體參數,所以查詢字符串不會再漂亮和自我解釋了,你必須對路由進行創造性的區分不同的get方法。

移動到業務層或DDD層,這種組合鍵作爲值對象是一種非常常見的情況,由於查詢字符串在那裏沒有問題,所以它實際上是一個非常可行和推薦的解決方案。