消除 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 服务的歧义的主要内容,如果未能解决你的问题,请参考以下文章

restful 接口 复杂对象入参

简单来说啥是 REST、RESTFul、SOA 和微服务?

一句话读懂RESTful

rest、restful 和基于肥皂的 web 服务有啥区别?

访问 RESTful Web 服务的最佳身份验证机制

gin-gonic Go轻量级restful服务