消除 RESTful 服务的歧义
Posted
技术标签:
【中文标题】消除 RESTful 服务的歧义【英文标题】:Disambiguating RESTful services 【发布时间】:2014-04-13 13:23:51 【问题描述】:我在以前的项目中广泛使用了 WCF。最近,我一直在探索使用 ASP.NET Web API 创建 RESTful 服务。在研究了 RESTful 服务的 DO 和 DONT 并实际尝试之后,我有一个相当直截了当的问题。
假设我有一个 UsersController
(继承 ApiController
),我需要有 3 个 GET 类型的操作方法:
GetUsers()
GetUserById(string id)
GetUserByName(string name)
假设我在 WebApiConfig
中也有以下路由
config.Routes.MapHttpRoute(
name: "Users",
routeTemplate: "api/users/id",
defaults: new controller = "users", id = RouteParameter.Optional
);
http://localhost:<port>/api/users
显然会调用GetUsers()
当我需要调用采用单个参数的两个操作方法中的任何一个时,问题就出现了。
我愿意
http://localhost:<port>/api/users/5c6fe209-821e-475f-920d-1af0f3f52a82
调用GetUserById(string id)
和
http://localhost:<port>/api/users/jdoe
调用GetUserByName(string name)
我期望会发生的情况是,我要么得到一个错误,要么在任何一种情况下都只会调用第一个操作方法。
既然在路由上引入动作去歧义被认为是对纯 RESTful 服务的背离,那么如何让不同的 URL 调用各自的动作方法呢?我搜索了网络,大多数 RESTful 服务示例(纯粹主义者)都停留在第一个操作方法以检索所有内容,而第二个操作方法用于检索单个项目。
【问题讨论】:
【参考方案1】:这可以通过使用更具体的受限路线来实现。下面的示例与您要实现的目标类似:
config.Routes.MapHttpRoute(
name: "ById",
routeTemplate: "api/controller/id",
defaults: new action = "GetById" ,
constraints: new id = @"\d+"
);
config.Routes.MapHttpRoute(
name: "ByName",
routeTemplate: "api/controller/name",
defaults: new action = "GetByName", ,
constraints: new name = @"\w+"
);
config.Routes.MapHttpRoute(
name: "All",
routeTemplate: "api/controller",
defaults: new action = "GetAll",
);
您传递给 MapHttpRoute 的约束对象以正则表达式的形式为不同的 URL 参数指定约束。在此示例中,对于 ById 路由,我们仅在 id 参数是数字时匹配。如果 name 参数是一串字母字符,我们只匹配 ByName。我们在没有参数的情况下匹配 All。请注意,每个正则表达式上的“+”不指定空值。路由按照它们定义的顺序匹配,所以你应该把最不具体的规则放在更具体的规则下面。
在您的情况下,您需要找到或编写一个与您正在使用的 GUID 格式匹配的正则表达式,并限制您的 ById 路由以匹配该表达式。然后,您需要在接受任何字符串的下方定义您的 ByName 路由。因为它在下面,所以只有在输入字符串不是 GUID 时才会调用它。
我还应该补充一点,如果您以前没有使用过 MVC 路由,它们是非常具体的。您会注意到,我的参数名称是 ById 的 id 和 ByName 的 name。重要的是,这些参数与控制器方法上输入参数的确切名称相匹配,因为路由器使用它来构建方法调用。即使您在操作上只有一个参数,如果名称未正确映射,您也会收到错误消息。
【讨论】:
谢谢史蒂夫。想一想,如果你这样去做,你会不会牺牲了 RESTful 服务本来应该有的流畅性? 实际上,如果它变得如此复杂,强制调用者提供动作名称不是更好或更容易吗? 2 个月后...我个人认为这并不复杂。但要回答,使用隐式过滤操作使用服务更简单,但更难编写服务器端。如果您选择其他方法,您只是将复杂性转移给客户端。我要说的是,如果你有一个用户的名字是数字的情况(在这个例子中不太可能,但其他实体可能)你的名字将被解释为 ID,所以需要明确指定过滤方法是可能是最好的方法。 实际上,最 ReST 的方法可能是使用 /users/id 方法,但是在按名称搜索用户时,您将使用 /users/?name=name。这有点假设名称不是唯一的。我想我要说的是,您在 URL 中输入的任何内容都应该唯一标识该实体,而任何不唯一的内容都应该是集合上的查询参数。用户可能是其中两个属性(名称和 ID)是唯一的情况的少见示例之一,在这种情况下,我会与自己争论,直到我不再关心并且该回家了。以上是关于消除 RESTful 服务的歧义的主要内容,如果未能解决你的问题,请参考以下文章