当我们为每个方法提供多个所需的路由级别时,有没有办法简化?
我有一个假设的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)
{
}
}
但是,让我们说一些愚蠢的原因,电影是由流派存储的。所以你需要genre + 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
我可以将这些图层注入构造函数而不是方法本身。
也许我想根据这些图层以不同的方式初始化控制器,所以我会根据流派和/或年份或类似的东西使用不同的回购。
有解决方法吗?
答案 0 :(得分:2)
你考虑过使用OData吗? Web Api支持OData烘焙,用它可以将您的查询写为url:例如?$ filter =流派eq'rorror'。如果由于某种原因或某些原因,您不希望您的数据作为OData返回但想要OData的查询语法,那么您可以:
使用Linq To QueryString:此lib为您提供了一个IQueryable的扩展方法,用于解析查询字符串并将查询应用于任何IQueryable
将ODataQueryOptions转换为对数据库的查询(有关将查询转换为HQL的示例,请参阅this MSDN article)
答案 1 :(得分:0)
将可选参数移动到查询字符串中是否可行?
e.g。 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)
{
}
}
答案 2 :(得分:0)
如果你的意思是电影按照流派和年份存储,那么我相信你不那么干的解决方案实际上是正确的。这确实让我质疑构建这样的多部分标识符的目的,例如在你的例子中使用的电影,当然年份和类型只是关于电影的元信息,而不是标识符的部分。更一般地说,我真的在争论一个复合标识符是否超过两个,或者至少超过三个部分是否适合任何软件的和平。代理键可以缓解这种情况下的发展痛苦。
同时讨论你对DRY的关注:重复的问题,我认为由于一个对象的主要关键结构很少发生变化,所以这不是一个非常大的问题。更重要的是,因为主键的更改将始终是一个破坏所有向后兼容性的变化。
作为一个噱头,你可以创建一个包含复杂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原则。问题是,复杂类型必须是一个body参数,因此查询字符串将不再漂亮且不再自我解释,并且您必须对路由进行创造,以区分不同的get方法。
迁移到业务层或DDD层,这种复合键作为值对象是一种非常常见的场景,因为查询字符串无关紧要,它实际上是一个非常可行和推荐的解决方案